I'm trying to measure execution time of some commands in c++ by using the physical clock, but I have run into a problem that the process of reading off the measurement from the physical clock on the computer can take a long time. Here is the code:
#include <string>
#include <cstdlib>
#include <iostream>
#include <math.h>
#include <time.h>
int main()
{
int64_t mtime, mtime2, m_TSsum, m_TSssum, m_TSnum, m_TSmax;
struct timespec t0;
struct timespec t1;
int i,j;
for(j=0;j<10;j++){
m_TSnum=0;m_TSsum=0; m_TSssum=0; m_TSmax=0;
for( i=0; i<10000000; i++) {
clock_gettime(CLOCK_REALTIME,&t0);
clock_gettime(CLOCK_REALTIME,&t1);
mtime = (t0.tv_sec * 1000000000LL + t0.tv_nsec);
mtime2= (t1.tv_sec * 1000000000LL + t1.tv_nsec);
m_TSsum += (mtime2-mtime);
m_TSssum += (mtime2-mtime)*(mtime2-mtime);
if( (mtime2-mtime)> m_TSmax ) { m_TSmax = (mtime2-mtime);}
m_TSnum++;
}
std::cout << "Average "<< (double)(m_TSsum)/m_TSnum
<< " +/- " << floor(sqrt( (m_TSssum/m_TSnum - ( m_TSsum/m_TSnum ) *( m_TSsum/m_TSnum ) ) ) )
<< " ("<< m_TSmax <<")" <<std::endl;
}
}
Next I run it on a dedicated core (or so the sysadmin tells me), to avoid any issues with process being moved to background by scheduler:
$ taskset -c 20 ./a.out
and this is the result I get:
Average 18.0864 +/- 10 (17821)
Average 18.0807 +/- 8 (9116)
Average 18.0802 +/- 8 (8107)
Average 18.078 +/- 6 (7135)
Average 18.0834 +/- 9 (21240)
Average 18.0827 +/- 8 (7900)
Average 18.0822 +/- 8 (9079)
Average 18.086 +/- 8 (8840)
Average 18.0771 +/- 6 (5992)
Average 18.0894 +/- 10 (15625)
So clearly it takes about 18 nanosecond (on this particular server) to call clock_gettime()
, but what I can't understand why the "max" time seems to be between 300 and 1000 times longer?
If we assume that the core is truly dedicated to this process and not used by something else (which may or may not be true; when not running on dedicated core, the average time is the same, but the sd/max are somewhat bigger), what else could cause these "slowdowns" (for the lack of a better name)?
答案 0 :(得分:8)
当您在两次clock_gettime
次呼叫中迭代1000万次时,有许多软件和硬件相关的原因可能会导致您看到异常事件(以及非异常值变化)。这些原因包括:
watch -n1 cat /proc/interrupts
,看看您可能认为是一个空闲系统的动作是如何发生的。clock_gettime
的内部结构,你很可能会发现一些分支在发生一些溢出时会采取不同的路径,或者在更新时从VDSO比赛中的调整因子中读取等等。 / LI>
这甚至都不是一个全面的列表,但至少应该让你尝试一些可能导致异常值的因素。您可以消除或减少某些的影响,但在x86上的现代非实时 2 操作系统中通常无法完全控制。
如果我不得不猜测,基于典型的异常值~8000 ns,这对于上下文切换中断可能太小,您可能会看到处理器频率缩放的影响改变TurboBoost比率。这是一个满口的,但基本上是现代的x86芯片运行在不同的&#34; max turbo&#34;速度取决于活动的核心数量。例如,如果一个核心处于活动状态,我的i7-6700HQ将以3.5 GHz运行,如果分别激活2,3或4个核心,则仅运行3.3,3.2或3.1 GHz。
这意味着即使您的进程永不中断,任何在其他CPU上运行的工作都可能会导致频率转换(例如,因为您从1个活动核心转换为2个活动核心)并且在这种转换期间,CPU在电压稳定的同时空闲数千个周期。您可以找到一些详细的数字和测试in this answer,但结果是在测试的CPU上稳定大约需要20,000个周期,非常符合您观察到的~8000纳秒的异常值。有时你可能会在一段时间内获得两次过渡,使影响加倍,等等。
如果您仍想知道异常值的原因,可以采取以下步骤并观察对异常值行为的影响。
首先,您应该收集更多数据。您应该收集具有合理铲斗尺寸的直方图(例如100 ns,甚至更好的某种类型的几何铲斗尺寸,以便在更短的时间内提供更高的分辨率),而不是仅重新编码超过10,000,000次迭代。这将是一个巨大的帮助,因为您将能够准确地看到时间聚集的位置:完全有可能除了您注意到的6000 - 17000 ns异常值之外还有其他效果&#34; max&# 34;,他们可能有不同的原因。
直方图还可以让您了解离群值频率,您可以将其与可以测量的事物的频率相关联,以查看它们是否匹配。
现在添加直方图代码也可能为定时循环增加更多差异,因为(例如)您将根据时间值访问不同的缓存行,但这是可管理的,尤其是因为记录时间发生在&#34;定时区域之外&#34;。
有了这些,您可以尝试系统地检查我上面提到的问题,看看它们是否是原因。以下是一些想法:
频率调整:在Linux上,您通常可以通过将性能调控器设置为&#34; performance&#34;来禁用子标称频率调整。如果您正在使用/sys/devices/system/cpu/intel_pstate/no_turbo
驱动程序,则可以通过将0
设置为intel_pstate
来禁用超名义(也称为turbo)。如果您有其他驱动程序,也可以操作turbo模式directly via MSR,如果其他所有驱动程序都失败,您也可以在BIOS中执行此操作。在linked question中,当turbo被禁用时,异常值基本消失,所以首先要尝试一下。
假设您确实希望在生产中继续使用turbo,您可以手动将最大turbo比限制为适用于N个核心的某个值(例如,2个核心),然后使其他CPU脱机,以便最多该数量的核心将永远活跃。然后,无论有多少核心处于活动状态,您都可以始终以新的最大涡轮增压运行(当然,在某些情况下,您可能仍会受到功率,电流或热量限制)。
/proc/interrupts
)并查看计数足以解释异常值。如果你发现特定的计时器中断是原因,你可以探索各种&#34; tickless&#34; (又名&#34; NOHZ&#34;)您的内核提供的模式可以减少或消除它们。您也可以通过x86上的HW_INTERRUPTS.RECEIVED
性能计数器直接计算它们。HZ
速率发生(现代内核通常为250 /秒) - 但它将是在大多数空闲系统上很少见,调度程序实际上会决定在繁忙的CPU上调度另一个进程。如果你使基准测试循环变短,通常几乎可以完全避免上下文切换。perf
等各种分析工具检查是否发生这种情况。您可以仔细设计数据包处理代码的核心,以避免诸如缓存未命中之类的异常事件,例如通过预先触摸缓存行,并且您可以尽可能避免使用具有未知复杂性的系统调用。虽然上述某些内容纯粹是出于调查目的,但其中许多内容都可以帮助您确定造成暂停的原因并减轻它们。
我并不知道所有问题的缓解 - 例如SMM,你可能需要专门的硬件或BIOS才能避免。
1 好吧,除非在触发if( (mtime2-mtime)> m_TSmax )
条件的情况下 - 但这应该是罕见的(也许你的编译器已经使它成为无分支,在这种情况下有只有一条执行路径。)
2 实际上并不清楚你可以得到零差异&#34;即使使用硬实时操作系统:某些特定于x86的因素(如SMM模式和DVFS相关档位)似乎也是不可避免的。
答案 1 :(得分:3)
taskset
命令定义了您的进程的亲和性,这意味着您的进程被限制为在指定的CPU核心上运行。它不会以任何方式限制其他进程,这意味着它们中的任何进程都可以随时抢占您的进程(因为所有进程都允许在您为进程选择的CPU核心上运行)。因此,您的最大读取间隔时间(那些5-25 usec)可能代表CPU上的其他进程或中断运行时间以及上下文切换时间。除此之外,您还可以使用CLOCK_REALTIME
进行NTP更正等。要测量时间间隔,您应该使用CLOCK_MONOTONIC
(或特定于Linux的CLOCK_MONOTONIC_RAW
)。
答案 2 :(得分:-2)
这在现代c ++中更容易
#include <chrono>
auto start = std::chrono::steady_clock::now();
.....
auto stop = std::chrono::steady_clock::now();
auto duration = stop - start;
对于非实时操作系统来说,18纳秒非常快。你真的需要比这更准确地测量一些东西吗?根据我的计算,18ns在4GHz CPU上只有72个时钟周期。