我如何计算PHP的microtime本身?

时间:2013-07-25 20:04:21

标签: php performance timer

我倾向于使用通常的方法检查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 函数本身的两次调用所花费的时间?

4 个答案:

答案 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); 执行相同操作。

无论我们如何解决问题,我们总是会做以下事情:

  1. 致电$timer_start = microtime(TRUE);
  2. 浪费时间初始化microtime(TRUE),返回值为$timer_start(请参阅“1。”),
  3. 致电microtime(TRUE)别无其他,
  4. 致电microtime(TRUE)并从中减去开始时间(参见“2。”)。
  5. 您需要点“2。”来记住开始时间。您不能以任何方式跳过该变量赋值或解决问题。无论你做什么,你总是在你的计时结果中包含一个变量赋值。更糟糕的是:你永远不会知道在变量赋值期间浪费了多少时间,使你的结果不正确(更好:不是100%正确)。

    为了帮助您理解它...如果问题和我在本答案开头提供的代码,问题可以看作:

    visualization

    解释它: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具有快速切换上下文的特殊功能(preemptioncontext switching)。

在测量过程中,上下文切换将不可避免地污染您的测量值,因此您希望尽可能避免它。在Windows和/或Linux下,我们有尽可能多的方法来缓解这个问题:

  • 关闭尽可能多的并发和/或后台运行的应用程序/守护进程/服务;
  • 分离网络和打印机等外围设备(以避免I / O中断);
  • 使用纯终端登录而不是图形界面;
  • 删除可能会干扰的所有其他内容。

然后,您希望将进程的优先级设置为实时,以便循环不会抢占进程。但这实际上取决于实际使用的内核。有一些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时钟和速度发生巨大变化。