学习PHP中的高精度计时器HRTime扩展

不知道大家还记得在学校的时候体育测试时老师带的秒表吗?当枪声想起时,我们开始跑步,这时秒表启动,当我们跑过终点后,老师会按下按扭记录我们的成绩,这就是一个典型的定时器的应用。今天我们要学习的内容其实就是和这个体育测验的秒表类似的一个功能扩展,它就是 PHP 的 HRTime 扩展。

时钟节拍

首先我们要了解一下什么叫做系统的时钟节拍。当 Linux 系统启动之后,会同时启动一个时钟节拍器,以纳秒为单位进行计时,而我们的 HRTime 扩展的真实名称是 高精度时间 扩展。也就是说,它正是基于操作系统的时钟节拍器,能够以纳秒为单位进行计时。

1秒=1000毫秒=1000000微妙=1000000000纳秒,这是秒、毫秒、微秒和纳秒的关系,看出来它的精度有多高了吧。1秒等于10亿纳秒,这样我们就可以获得一个非常精确的时间间隔计数。

HRTime 扩展直接在 PECL 进行下载安装就可以了,和其他的普通扩展没有什么区别。

获取系统时钟节拍信息 Ticks

我们先来看看如何获取操作系统的时钟节拍,也就是这个 Ticks 。关于它的内容在学习操作系统的时候相信已经有不少的同学接触过了,这里我们看看使用 HRTime 扩展如何获取。

print_r(hrtime());
// Array
// (
//     [0] => 3758
//     [1] => 407409171
// )

echo hrtime(true), PHP_EOL;
// 3758407428932

hrtime() 这个函数在 PHP7 之后已经集成在默认 PHP 环境中了。它不需要 HRTime 扩展就可以使用。这个函数在没有参数的情况下返回的是一个数组,第 0 项是系统启动到现在的秒数,第 1 项就是对应的纳秒计数。如果给它的参数设置一个 true 的话,它将直接返回将秒和纳秒拼接起来的实际纳秒时间戳。

echo HRTime\PerformanceCounter::getFrequency(), PHP_EOL; // 1000000000
echo HRTime\PerformanceCounter::getTicks(), PHP_EOL; // 3758428256236
echo HRTime\PerformanceCounter::getTicksSince(1212), PHP_EOL; // 3758428257494

$a = HRTime\PerformanceCounter::getTicks();
echo HRTime\PerformanceCounter::getTicksSince($a), PHP_EOL; // 412

接下来的这三个函数就是 HRTime 扩展中的 PerformanceCounter 对象的静态函数了。PerformanceCounter 对象的意思是性能计数器,getFrequency() 表示的是计时器频率(以滴答Ticks/秒为单位),可以看出,它返回的就是纳秒单位,也就是 10亿 。getTicks() 返回的是当前的时钟节拍时间,可以看出它和 hrtime(true) 函数的结果是一样的,都是返回的系统启动后的时钟节拍时间。getTicksSince() 方法则是根据指定的纳秒数返回时间间隔,类似于 date_diff() 的感觉,其实就像我们的 time() - time() 这样的操作。通过这个方法就可以获得一段代码两次运行的时间间隔,而且是以纳秒为单位哦。

定时器功能

接下来就是我们文章的重点内容了,也就是定时器功能的实现。上面已经说过,使用 getTickSince() 其实也能做到监控一段代码的运行时间间隔,不过下面将学习到的内容将更加强大。

$c = new HRTime\StopWatch;

$c->start();
for ($i = 0; $i < 1024*1024; $i++);
echo 'isRunning: ', $c->isRunning(), PHP_EOL; // isRunning: 1
$c->stop();

echo 'Time NS: ', $c->getLastElapsedTime(HRTime\Unit::NANOSECOND), PHP_EOL;
echo 'Time US: ', $c->getLastElapsedTime(HRTime\Unit::MICROSECOND), PHP_EOL;
echo 'Time MS: ', $c->getLastElapsedTime(HRTime\Unit::MILLISECOND), PHP_EOL;
echo 'Time S: ', $c->getLastElapsedTime(HRTime\Unit::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;
// 

我们需要实例化一个 StopWatch 对象,然后调用它的 start() 方法,这样一个定时器就启动了。StopWatch 的英文涵义本身就是定时器的意思,所以这个对象是专门为定时器的操作所服务的。通过 isRunning() 方法我们可以判断当前定时器是否运行,其实就是判断当前是否是在一个 start() 方法之后,如果不在 start() 和 stop() 范围中,那么它将返回 false 。在测试代码中,我们运行一个 1024*1024 的空循环,然后再使用 stop() 方法结束定时器。

从代码中可以看出,getLastElapsedTime() 就是获得我们上面的那个 start() 到 stop() 之间的代码运行耗时的时间间隔信息,它的参数可以指定为秒、毫秒、微秒、纳秒。本身这个方法的意思就是获取获取最后一个间隔的运行时间。getLastElapsedTicks() 则是获得最后一次间隔的时钟节拍信息。既然有【最后一次】这四个字,那么也就说明这个对象是可以多次调用的来分段计时的。并且,它还是可以将多段不同的计时进行汇总,获得全部的时间间隔信息的。

// 不在计时范围内
for ($i = 0; $i < 1024*1024; $i++);

$c->start();
for ($i = 0; $i < 1024*1024; $i++);
$c->stop();

echo 'Time NS: ', $c->getLastElapsedTime(HRTime\Unit::NANOSECOND), PHP_EOL;
echo 'Time US: ', $c->getLastElapsedTime(HRTime\Unit::MICROSECOND), PHP_EOL;
echo 'Time MS: ', $c->getLastElapsedTime(HRTime\Unit::MILLISECOND), PHP_EOL;
echo 'Time S: ', $c->getLastElapsedTime(HRTime\Unit::SECOND), PHP_EOL;
// Time NS: 7154010
// Time US: 7154.01
// Time MS: 7.15401
// Time S: 0.00715401

echo 'All Time NS: ', $c->getElapsedTime(HRTime\Unit::NANOSECOND), PHP_EOL;
echo 'All Time US: ', $c->getElapsedTime(HRTime\Unit::MICROSECOND), PHP_EOL;
echo 'All Time MS: ', $c->getElapsedTime(HRTime\Unit::MILLISECOND), PHP_EOL;
echo 'All Time S: ', $c->getElapsedTime(HRTime\Unit::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

在这段代码中,我们在两段计时测试代码中插入了一个循环测试代码,它不会计入到计时数据中。接着,我们重新 start() 开始一个新的计时,在最后,我们通过 getElapsedTime() 和 getElapsedTicks() 两个方法获得总的计时时间,可以看出上面的 6929888 加上这次的 7154010 结果正好是 14083898 。中间的那一段没有在定时器中的循环代码没有计入到总的计时时间中。

总结

是不是很有意思,它的作用真的和我们的体育老师所用的那个秒表一模一样,老师们的秒表也都是可以按多次记录第1名到最后1名的全部跑步成绩,并且最后还有一个总的时间,而在代码中我们也是完全相似的操作。这个扩展对于精细的性能调试非常有用,而且也能够针对一些需要这种高精度时间差的业务进行相关的开发。

测试代码:

https://github.com/zhangyue0503/dev-blog/blob/master/php/202010/source/3.学习PHP中的高精度计时器HRTime扩展.php

参考文档:

https://www.php.net/manual/zh/book.hrtime.php

https://www.cnblogs.com/chezxiaoqiang/archive/2012/03/23/2674386.html

各自媒体平台均可搜索【硬核项目经理】

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 216,843评论 6 502
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 92,538评论 3 392
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 163,187评论 0 353
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 58,264评论 1 292
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 67,289评论 6 390
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 51,231评论 1 299
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 40,116评论 3 418
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,945评论 0 275
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 45,367评论 1 313
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,581评论 2 333
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,754评论 1 348
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,458评论 5 344
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 41,068评论 3 327
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,692评论 0 22
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,842评论 1 269
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 47,797评论 2 369
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,654评论 2 354

推荐阅读更多精彩内容