我倾向于使用通常的方法检查我的 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函数本身的两次调用使用了多少时间?
我倾向于使用通常的方法检查我的 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函数本身的两次调用使用了多少时间?
如果我是你,我会创建一个测试页面..
在该测试页面中,我将测试 10 次执行 2 个微时间所需的执行时间,然后测试 10 次执行 1 个微时间,通过这种方式,我将找到一个 microtime() 的执行时间。
希望能帮助到你
编辑(2): TL;博士
可以修改下面的代码以打印出开销。在那种特定情况下,开销主要是由于microtime()
函数本身。
microtime()
在去除了 0.11 us 的开销后,我测量到需要 0.27 us。
这应该回答这个问题:使用代码来衡量开销,这就是 OP 所要求的。
编辑:怀疑者投票否决
这里的要点是,测量空运行(空dummy()
函数)确实消除了所有开销,包括诸如microtime()
在开始和结束时执行两个调用以及for
循环和所有内容等工件。但很高兴看到不是每个人都明白,测量理论确实是一个复杂的问题。对于那些认真对待此事的人,不要对接受的答案感到困惑。最好在 Internet 上阅读一些文章,而不是仅仅查看 StackOverflow。
这个问题已经很老了,但我必须恢复它并回答,因为我在这里看到了很多困惑。真正的答案是:
是的,你可以,如果你知道如何正确地测量一般的代码执行时间。
这个测量领域非常复杂,所以我会尽量减少这个说明。
与公认的答案相反,您实际上可以渐近地接近正确的值,其误差小于 epsilon(意思是,尽可能精确)。
让我们慢慢开始:如何避免多任务系统中的干扰。
您可能知道,您的操作系统的内核将处理资源。最值得注意的是CPU本身。现代 CPU 具有快速切换上下文的特殊功能(抢占、上下文切换)。
在测量期间,上下文切换将不可避免地污染您的测量,因此您需要尽可能避免它。在 Windows 和/或 Linux 下,我们有不同的方法来尽可能缓解这个问题,即:
然后你想将进程的优先级设置为实时,这样循环不会抢占进程。但这实际上取决于实际使用的内核。有一些 Linux 内核是专门为 RT 而编译的。在 Windows 上,右键单击任务管理器中的应用程序并将优先级设置为最大并将进程限制到第二个内核(操作系统有时首选第一个内核活动)。
接下来让我们讨论如何避免分页。
分页是虚拟内存的效果。在执行过程中,一些内存页面可能会保存到驻留在磁盘上的页面文件中。下一次您访问该页面中的内存地址时,它将透明地加载到进程中。CPU 将捕获页面错误异常并激活加载算法。一旦将数据加载到 RAM 中,该过程就会在不知不觉中完全恢复,因为已经过去了大量的时间(毫秒)。在汇编程序中,一条指令例如:
MOV dest, src
如果dest或src是分页地址,则不会占用 1 个 CPU 周期,甚至数十亿个 CPU 周期。
为避免此问题,您必须预取您将使用的所有数据,使其尽可能靠近 CPU。与 CPU的距离为:
页面文件 > RAM > L2 CPU 缓存 > L1 CPU 缓存 > CPU 寄存器
使用 PHP 这意味着您无法解决这个问题。在汇编程序中,您在这方面会有很大的优化余地。所以,假设我们必须尽可能在 PHP 中处理这个问题,而不依赖 PHP 之外的任何东西。
接下来,我们将讨论降噪。
降噪的想法很简单。不要测量一次,而是进行多次测量,然后对所有值进行平均。这样,单个错误的波动将被消除,仅保留一个硬基错误。
这意味着您将在一个周期内多次测量,然后平均累积值。
接下来,我们将讨论如何消除开销,这显然会导致很多混乱。
如果你测量一个算法,不可避免地需要额外的指令来让它运行。但你并不是真的想衡量那些,你只是想衡量你想要的要点。额外的测量是必须从实际测量中去除的开销。在幸运的情况下(这里是我们的例子),在执行辅助代码时,上述基本错误仍然存在,如果你擅长测量,你甚至有机会完全删除它,获得一个漂亮的精确的结果。
现在让我们总结一下,不仅查看示例代码,还查看实现所有这些概念的实际代码。代码中的注释将指向到目前为止所说的内容。
<?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 中断而导致的测量错误。上下文切换和持续的网络流量仍然会产生影响,但我认为这是必须考虑的正常情况。没有人会完全孤立地运行程序。这将是一个很好的理论练习,但没有实际价值。
我认为您需要一个循环来迭代调用,然后将其除以次数以了解每个调用的平均时间,例如
<?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 时钟和速度而发生巨大变化。