我倾向于使用通常的方法检查PHP内容的速度。
<?php
$timer_start = microtime(TRUE);
/*
some code here that I want to time
*/
$timer_end = microtime(TRUE);
echo($timer_end - $timer_start);
exit();
?>
如何计算PHP的 microtime 函数本身的两次调用所花费的时间?
答案 0 :(得分:9)
答案是: “你不能 - 这意味着:你的计时结果不会100%准确!” 。
事实上,逻辑已经意味着“PHP的微量时间函数本身的两次调用使用了多少时间”并且得到的结果是100%准确的总是会在你的时间安排中包含(至少)一个变量赋值。
所以,这是你最接近的地方:
<?php
// start the timer
$timer_start = microtime(TRUE);
// Call microtime once (we want to calculate time wasted by microtime 2 calls)
microtime(TRUE);
// stop the timer and return the result
echo(((microtime(TRUE) - $timer_start)*1000).' microseconds');
exit();
?>
如果你看一下上面的代码,你会注意到我没有使用$timer_end = microtime(TRUE);
,因为这会浪费时间在变量赋值上,可以通过直截了当地获取{{1}的返回值来避免调用并减去开始时间:microtime
。
这很好,但是没有办法让PHP记住开始时间而不将其分配给变量,这意味着:你将永远做某种(microtime(TRUE) - $timer_start)
你在哪里无法避免初始化$timer_start = microtime(TRUE);
变量 - 浪费时间。浪费的时间包含在我们的最终结果中。没有办法解决这个问题。
解释我的意思:我们可以通过替换
来避免在时序的末尾进行变量赋值$timer_start
与
$timer_end = microtime(TRUE);
echo($timer_end - $timer_start);
但我们不能对echo(microtime(TRUE) - $timer_start);
执行相同操作。
无论我们如何解决问题,我们总是会做以下事情:
$timer_start = microtime(TRUE);
microtime(TRUE)
,返回值为$timer_start
(请参阅“1。”),microtime(TRUE)
别无其他,microtime(TRUE)
并从中减去开始时间(参见“2。”)。您需要点“2。”来记住开始时间。您不能以任何方式跳过该变量赋值或解决问题。无论你做什么,你总是在你的计时结果中包含一个变量赋值。更糟糕的是:你永远不会知道在变量赋值期间浪费了多少时间,使你的结果不正确(更好:不是100%正确)。
为了帮助您理解它...如果问题和我在本答案开头提供的代码,问题可以看作:
解释它:microtime(TRUE)
将知道确切的时间之前它返回它的值 - 所以我们不会得到一个确切的值返回(但非常接近)。然后需要将返回的值分配给变量,以便我们记住它 - 这需要更多的时间与我们实际想要的时间无关。
包装起来:你可以靠近,但你的回报价值永远不会100%正确。如果您已经理解了我上面解释的内容,您现在就会知道,无论您在PHP中的时间安排如何,都将包含浪费在那个“remember-start-time”变量初始化上的时间。
即使您创建的脚本只包含microtime
并且使用外部程序......您会注意到除了将问题转移到外部程序之外别无其他。外部程序将具有可视化显示的相同问题,因为外部程序也必须通过初始化变量来记住起始时间,这意味着外部程序也无法正确计时。
最后,我的问题最终变成了一个脑筋急转弯,以思考使用PHP作为示例的计时程序,循环和函数背后的逻辑。正如我的回答所解释的那样,使用什么编码语言并不重要,你总会遇到同样的问题。
我将由您决定是否值得考虑包含在您的计时程序结果中的“浪费时间”,或者在您的个案中是否可以忽略它。但就“精确”时间而言,不可否认的是,我们的结果中包含的变量赋值总会浪费很少的时间;获得时间的最小时间跨度,但实际上并不属于我们真正想要的时间。因此,结果永远不会100%准确 - 因为你不记得没有使用大脑的东西。从这个角度来看,事情突然变得简单起来......不是吗? ;)
答案 1 :(得分:4)
如果我是你,我会创建一个测试页面。
在该测试页面中,我将测试10次,每次测试需要多少执行时间,然后测试1次微量时间需要10次,这样我将找到一个micrtime()的执行时间。
希望有所帮助
答案 2 :(得分:1)
编辑(2): TL; DR
可以修改以下代码以打印开销。
在特定的情况下,开销主要是由于microtime()
函数本身。
我已经测量microtime()
要求0.27 us,在删除了0.11 us的开销之后。
这应该回答这个问题:使用代码来衡量开销,这就是OP所要求的。
编辑:让怀疑论者投票
这里的要点是测量 dry 运行(空dummy()
函数)确实消除了所有开销,包括执行两个microtime()
调用之类的工件在开始和结束以及for
循环和一切。但很高兴看到不是每个人都能得到它,测量理论确实是一个复杂的问题。对于那些认真对待此事的人,不要对接受的答案感到困惑。更好地阅读互联网上的一些文章,而不仅仅是看StackOverflow。
这个问题已经很老了,但我必须复活并回答,因为我在这看到很多混乱。实际上真正的答案是:
是的,如果您知道如何实际正确地测量代码执行时间,那么
测量这个领域相当复杂,所以我会尽量减少这种说明。
与接受的答案相反,您实际上可以渐近地接近正确的值,误差小于epsilon(意思是,根据需要精确)。
让我们慢慢开始:如何避免多任务系统中的干扰。
您可能知道,您的操作系统内核将处理资源。最值得注意的是,CPU本身。现代CPU具有快速切换上下文的特殊功能(preemption,context switching)。
在测量过程中,上下文切换将不可避免地污染您的测量值,因此您希望尽可能避免它。在Windows和/或Linux下,我们有尽可能多的方法来缓解这个问题:
然后,您希望将进程的优先级设置为实时,以便循环不会抢占进程。但这实际上取决于实际使用的内核。有一些Linux内核专门用RT编译。在Windows上右键单击任务管理器中的应用程序,并将优先级设置为最大值,并将进程限制为第二个核心(第一个是有时操作系统首选的内核活动)。
接下来让我们讨论如何避免分页。
分页是虚拟内存的影响。在执行期间,一些存储器页面可以保存到驻留在磁盘上的页面文件中。下一次访问该页面中的内存地址时,它将以透明方式加载到进程中。 CPU将捕获页面错误异常并激活加载算法。一旦数据加载到RAM中,该过程就会在不知不觉中重新开始,因为已经过了大量的时间(毫秒)。在汇编程序中有如下指令:
MOV dest, src
不会占用1个CPU周期,但如果 dest 或 src 是分页地址,则甚至数十亿个CPU周期。
要避免此问题,您必须预取所有将要使用的数据,以使其尽可能靠近CPU。来自CPU的距离将是:
PAGEFILE&gt; RAM&gt; L2 CPU CACHE&gt; L1 CPU CACHE&gt; CPU寄存器
使用PHP,这意味着您无法解决此问题。在汇编程序中,您将在这方面拥有巨大的优化余地。所以,让我们说我们必须尽可能地在PHP中处理这个问题而不依赖于PHP以外的任何东西。
接下来,我们将讨论降噪。
降噪的想法非常简单。不是测量一次,而是进行多次测量,然后对所有值进行平均。这样就可以消除单个错误的波动,只剩下硬基本错误。
这意味着您将在一个周期和多个实例中进行衡量,然后对累计值进行平均。
接下来,我们将讨论如何消除开销,这显然会导致很多混乱。
如果您测量算法,将不可避免地需要额外的指令来实现它。但你真的不想衡量这些,你只想衡量你想要的 gist 。附加测量是开销,必须从实际测量中删除。在幸运的情况下(这是我们的情况,这里)上面的基本错误仍然存在,而辅助代码正在执行,如果你擅长测量,你甚至会有一个有机会完全删除它,获得非常精确的结果。
现在让我们把它全部包装起来,看看不只是示例代码,而是实现所有这些概念的实际代码。代码中的注释将指向到目前为止所说的内容。
<?php
$dummy = function ()
{
// don't do anything
};
$f = function ()
{
microtime(true);
};
function measure($callback, $repetitions)
{
for($i = 0; $i < 1000; $i++) // prefetch
{
$callback();
}
$us = -microtime(true);
for($i = 0; $i < $repetitions; $i++)
{
$callback();
}
$us += microtime(true);
return $us;
}
$retries = 10;
$repetitions = 100000; // may be higher/lower depending on necessity
while ($retries-- > 0)
{
sleep(5);
$ovrh = measure($dummy, $repetitions); // measure overhead, including function calls and everything...
$time = measure($f, $repetitions);
echo 'ovrh: ' . $ovrh / $repetitions . "\n"; // it's important you only divide in the end,
echo 'time: ' . ($time - $ovrh) / $repetitions . "\n"; // after computing the difference!
}
所以,这应该在一两分钟内运行良好,但我实际上并不知道,因为我从未运行它。如果因为这个原因而存在拼写错误。
现在你要做的就是在没有其他干扰的情况下运行。打印出数字时,暂停允许系统最终从干扰中恢复。您必须多次重试(上面的代码中有10个),因为您必须密切关注它:
如果结果数字总是与您想要的数字相同!
如果数字上下跳动,则表示存在问题且测量不起作用。
闲暇时,您希望根据需要替换f()
中的代码。请记住,dummy()
还必须包含辅助代码。
示例:您有f()
作为辅助代码,您无法移除。
public function f()
{
$v = 3 * 4; // the assignment is auxiliary but cannot be removed
// note that the compiler may optimize the multiplication
// into the resulting number 12
// in that case execution time will be very near to the
// overhead and the difference will be 0 or, because of
// errors, by chance be less than 0!
}
public function dummy()
{
$v = 0; // this is the code you want to measure as overhead
}
在某些情况下,可以编写dummy()
函数,以便正确测量和删除开销。在某些情况下,这不是完全可能的,但解释哪种算法属于这一类别是另一个故事:)
最后记住这一点:
你想测量什么?纯粹理想的执行时间或真实的实际执行时间?
在第二种情况下,它更有趣,更有用,您必须将代码放在生产服务器上,并使用操作系统中的所有干扰和并发运行它。
由于习惯于编写面向对象的代码而不是脚本,我只修复了一个拼写错误和一些错误。现在,上面的脚本应该在没有错误的情况下从Linux命令行运行,也可能在Windows上运行。
我测量了2.7 * 10 ^ -7秒,同时在gnome中运行了YouTube视频,并打开了Chrome和其他一些窗口。这是0.27微秒。打印出的实际结果是:
2.7603149414062E-7
2.7155160903931E-7
2.670693397522E-7
2.7152061462402E-7
2.7000188827515E-7
2.705192565918E-7
2.7431011199951E-7
2.7279138565063E-7
2.6989936828613E-7
2.7417182922363E-7
正如我们所看到的,它相对稳定,因此我们可以假设由于寻呼或零星的I / O中断而没有测量误差。上下文切换和连续网络流量仍有影响,但我认为必须考虑正常情况。没有人会完全孤立地运行程序。这将是一个很好的理论练习,但没有真正的价值。
答案 3 :(得分:0)
我认为您需要一个循环来迭代调用,然后将其除以次数,以便知道每个调用的平均时间,例如
<?php for ($i = 0; $i < 100000; $i++) microtime(TRUE);
另一个循环:
<?php for ($i = 0; $i < 100000; $i++);
将这些文件分别保存到testmicrotime.php和testloop.php中,然后,(linux)执行:
$> time php testmicrotime.php
然后你需要减去用于循环的时间:
$> time php testloop.php
我的用户时间为2.056秒和0.512秒,因此每次通话的时间为1.544 / 100000 = 0.00001544秒或15.44微秒。
当然,要使其成为更可靠的指标,您需要多次运行以获得平均值,这可能会根据您的CPU时钟和速度发生巨大变化。