不知道大家還記得在學(xué)校的時(shí)候體育測(cè)試時(shí)老師帶的秒表嗎?當(dāng)槍聲想起時(shí),我們開(kāi)始跑步,這時(shí)秒表啟動(dòng),當(dāng)我們跑過(guò)終點(diǎn)后,老師會(huì)按下按扭記錄我們的成績(jī),這就是一個(gè)典型的定時(shí)器的應(yīng)用。今天我們要學(xué)習(xí)的內(nèi)容其實(shí)就是和這個(gè)體育測(cè)驗(yàn)的秒表類似的一個(gè)功能擴(kuò)展,它就是 PHP 的 HRTime 擴(kuò)展。
時(shí)鐘節(jié)拍
首先我們要了解一下什么叫做系統(tǒng)的時(shí)鐘節(jié)拍。當(dāng) Linux 系統(tǒng)啟動(dòng)之后,會(huì)同時(shí)啟動(dòng)一個(gè)時(shí)鐘節(jié)拍器,以納秒為單位進(jìn)行計(jì)時(shí),而我們的 HRTime 擴(kuò)展的真實(shí)名稱是 高精度時(shí)間 擴(kuò)展。也就是說(shuō),它正是基于操作系統(tǒng)的時(shí)鐘節(jié)拍器,能夠以納秒為單位進(jìn)行計(jì)時(shí)。
1秒=1000毫秒=1000000微妙=1000000000納秒,這是秒、毫秒、微秒和納秒的關(guān)系,看出來(lái)它的精度有多高了吧。1秒等于10億納秒,這樣我們就可以獲得一個(gè)非常精確的時(shí)間間隔計(jì)數(shù)。
HRTime 擴(kuò)展直接在 PECL 進(jìn)行下載安裝就可以了,和其他的普通擴(kuò)展沒(méi)有什么區(qū)別。
獲取系統(tǒng)時(shí)鐘節(jié)拍信息 Ticks
我們先來(lái)看看如何獲取操作系統(tǒng)的時(shí)鐘節(jié)拍,也就是這個(gè) Ticks 。關(guān)于它的內(nèi)容在學(xué)習(xí)操作系統(tǒng)的時(shí)候相信已經(jīng)有不少的同學(xué)接觸過(guò)了,這里我們看看使用 HRTime 擴(kuò)展如何獲取。
print_r(hrtime()); // Array // ( // [0] => 3758 // [1] => 407409171 // ) echo hrtime(true), PHP_EOL; // 3758407428932
hrtime() 這個(gè)函數(shù)在 PHP7 之后已經(jīng)集成在默認(rèn) PHP 環(huán)境中了。它不需要 HRTime 擴(kuò)展就可以使用。這個(gè)函數(shù)在沒(méi)有參數(shù)的情況下返回的是一個(gè)數(shù)組,第 0 項(xiàng)是系統(tǒng)啟動(dòng)到現(xiàn)在的秒數(shù),第 1 項(xiàng)就是對(duì)應(yīng)的納秒計(jì)數(shù)。如果給它的參數(shù)設(shè)置一個(gè) true 的話,它將直接返回將秒和納秒拼接起來(lái)的實(shí)際納秒時(shí)間戳。
echo HRTimePerformanceCounter::getFrequency(), PHP_EOL; // 1000000000 echo HRTimePerformanceCounter::getTicks(), PHP_EOL; // 3758428256236 echo HRTimePerformanceCounter::getTicksSince(1212), PHP_EOL; // 3758428257494 $a = HRTimePerformanceCounter::getTicks(); echo HRTimePerformanceCounter::getTicksSince($a), PHP_EOL; // 412
接下來(lái)的這三個(gè)函數(shù)就是 HRTime 擴(kuò)展中的 PerformanceCounter 對(duì)象的靜態(tài)函數(shù)了。PerformanceCounter 對(duì)象的意思是性能計(jì)數(shù)器,getFrequency() 表示的是計(jì)時(shí)器頻率(以滴答Ticks/秒為單位),可以看出,它返回的就是納秒單位,也就是 10億 。getTicks() 返回的是當(dāng)前的時(shí)鐘節(jié)拍時(shí)間,可以看出它和 hrtime(true) 函數(shù)的結(jié)果是一樣的,都是返回的系統(tǒng)啟動(dòng)后的時(shí)鐘節(jié)拍時(shí)間。getTicksSince() 方法則是根據(jù)指定的納秒數(shù)返回時(shí)間間隔,類似于 date_diff() 的感覺(jué),其實(shí)就像我們的 time() – time() 這樣的操作。通過(guò)這個(gè)方法就可以獲得一段代碼兩次運(yùn)行的時(shí)間間隔,而且是以納秒為單位哦。
定時(shí)器功能
接下來(lái)就是我們文章的重點(diǎn)內(nèi)容了,也就是定時(shí)器功能的實(shí)現(xiàn)。上面已經(jīng)說(shuō)過(guò),使用 getTickSince() 其實(shí)也能做到監(jiān)控一段代碼的運(yùn)行時(shí)間間隔,不過(guò)下面將學(xué)習(xí)到的內(nèi)容將更加強(qiáng)大。
$c = new HRTimeStopWatch; $c->start(); for ($i = 0; $i < 1024*1024; $i++); echo 'isRunning: ', $c->isRunning(), PHP_EOL; // isRunning: 1 $c->stop(); echo 'Time NS: ', $c->getLastElapsedTime(HRTimeUnit::NANOSECOND), PHP_EOL; echo 'Time US: ', $c->getLastElapsedTime(HRTimeUnit::MICROSECOND), PHP_EOL; echo 'Time MS: ', $c->getLastElapsedTime(HRTimeUnit::MILLISECOND), PHP_EOL; echo 'Time S: ', $c->getLastElapsedTime(HRTimeUnit::SECOND), PHP_EOL; // Time NS: 6929888 // Time US: 6929.888 // Time MS: 6.929888 // Time S: 0.006929888 echo 'Ticks: ',$c->getLastElapsedTicks(), PHP_EOL; // Ticks: 6929888 echo 'isRunning: ',$c->isRunning(), PHP_EOL; //
我們需要實(shí)例化一個(gè) StopWatch 對(duì)象,然后調(diào)用它的 start() 方法,這樣一個(gè)定時(shí)器就啟動(dòng)了。StopWatch 的英文涵義本身就是定時(shí)器的意思,所以這個(gè)對(duì)象是專門為定時(shí)器的操作所服務(wù)的。通過(guò) isRunning() 方法我們可以判斷當(dāng)前定時(shí)器是否運(yùn)行,其實(shí)就是判斷當(dāng)前是否是在一個(gè) start() 方法之后,如果不在 start() 和 stop() 范圍中,那么它將返回 false 。在測(cè)試代碼中,我們運(yùn)行一個(gè) 1024*1024 的空循環(huán),然后再使用 stop() 方法結(jié)束定時(shí)器。
從代碼中可以看出,getLastElapsedTime() 就是獲得我們上面的那個(gè) start() 到 stop() 之間的代碼運(yùn)行耗時(shí)的時(shí)間間隔信息,它的參數(shù)可以指定為秒、毫秒、微秒、納秒。本身這個(gè)方法的意思就是獲取獲取最后一個(gè)間隔的運(yùn)行時(shí)間。getLastElapsedTicks() 則是獲得最后一次間隔的時(shí)鐘節(jié)拍信息。既然有【最后一次】這四個(gè)字,那么也就說(shuō)明這個(gè)對(duì)象是可以多次調(diào)用的來(lái)分段計(jì)時(shí)的。并且,它還是可以將多段不同的計(jì)時(shí)進(jìn)行匯總,獲得全部的時(shí)間間隔信息的。
// 不在計(jì)時(shí)范圍內(nèi) for ($i = 0; $i < 1024*1024; $i++); $c->start(); for ($i = 0; $i < 1024*1024; $i++); $c->stop(); echo 'Time NS: ', $c->getLastElapsedTime(HRTimeUnit::NANOSECOND), PHP_EOL; echo 'Time US: ', $c->getLastElapsedTime(HRTimeUnit::MICROSECOND), PHP_EOL; echo 'Time MS: ', $c->getLastElapsedTime(HRTimeUnit::MILLISECOND), PHP_EOL; echo 'Time S: ', $c->getLastElapsedTime(HRTimeUnit::SECOND), PHP_EOL; // Time NS: 7154010 // Time US: 7154.01 // Time MS: 7.15401 // Time S: 0.00715401 echo 'All Time NS: ', $c->getElapsedTime(HRTimeUnit::NANOSECOND), PHP_EOL; echo 'All Time US: ', $c->getElapsedTime(HRTimeUnit::MICROSECOND), PHP_EOL; echo 'All Time MS: ', $c->getElapsedTime(HRTimeUnit::MILLISECOND), PHP_EOL; echo 'All Time S: ', $c->getElapsedTime(HRTimeUnit::SECOND), PHP_EOL; // All Time NS: 14083898 // All Time US: 14083.898 // All Time MS: 14.083898 // All Time S: 0.014083898 echo 'All Ticks: ', $c->getElapsedTicks(), PHP_EOL; // All Ticks: 14083898
在這段代碼中,我們?cè)趦啥斡?jì)時(shí)測(cè)試代碼中插入了一個(gè)循環(huán)測(cè)試代碼,它不會(huì)計(jì)入到計(jì)時(shí)數(shù)據(jù)中。接著,我們重新 start() 開(kāi)始一個(gè)新的計(jì)時(shí),在最后,我們通過(guò) getElapsedTime() 和 getElapsedTicks() 兩個(gè)方法獲得總的計(jì)時(shí)時(shí)間,可以看出上面的 6929888 加上這次的 7154010 結(jié)果正好是 14083898 。中間的那一段沒(méi)有在定時(shí)器中的循環(huán)代碼沒(méi)有計(jì)入到總的計(jì)時(shí)時(shí)間中。
推薦學(xué)習(xí):《PHP視頻教程》
總結(jié)
是不是很有意思,它的作用真的和我們的體育老師所用的那個(gè)秒表一模一樣,老師們的秒表也都是可以按多次記錄第1名到最后1名的全部跑步成績(jī),并且最后還有一個(gè)總的時(shí)間,而在代碼中我們也是完全相似的操作。這個(gè)擴(kuò)展對(duì)于精細(xì)的性能調(diào)試非常有用,而且也能夠針對(duì)一些需要這種高精度時(shí)間差的業(yè)務(wù)進(jìn)行相關(guān)的開(kāi)發(fā)。
測(cè)試代碼: https://github.com/zhangyue0503/dev-blog/blob/master/php/202010/source/3.學(xué)習(xí)PHP中的高精度計(jì)時(shí)器HRTime擴(kuò)展.php 參考文檔: https://www.php.net/manual/zh/book.hrtime.php