第一个方法调用比使用相同数据的连续调用花费的时间长 10 倍

First method call takes 10 times longer than consecutive calls with the same data

我正在为我的快速排序执行一些执行时间基准。在对完全相同的输入数据进行的 100 次连续测量中,第一次调用快速排序似乎比所有连续调用花费的时间大约长 10 倍。这是操作系统准备执行程序的结果,还是有其他解释?此外,在计算平均运行时间时丢弃第一次测量是否合理?

下面的条形图说明了执行时间(毫秒)与方法调用次数的关系。每次调用该方法时,它都会处理完全相同的数据。

为了生成这个特定的图形,main 方法调用了 quicksort_timer::time_fpi_quicksort(5, 100),其实现如下所示。

static void time_fpi_quicksort(int size, int runs)
{
    std::vector<int> vector(size);
    for (int i = 0; i < runs; i++)
    {
        vector = utilities::getRandomIntVectorWithConstantSeed(size);
        Timer timer;
        quicksort(vector, ver::FixedPivotInsertion);
    }
}

getRandomIntVectorWithConstantSeed实现如下

   std::vector<int> getRandomIntVectorWithConstantSeed(int size)
   {
      std::vector<int> vector(size);
      srand(6475307);
      for (int i = 0; i < size; i++)
         vector[i] = rand();
      return vector;
   }

CPU 和编译

CPU:Broadwell 2.7 GHz 英特尔酷睿 i5 (5257U)

编译器版本:Apple LLVM 版本 10.0.0 (clang-1000.11.45.5)

编译器选项:-std=c++17 -O2 -march=native

可能是缓存的原因,因为内存需要从 DRAM 中获取并在第一次分配到 CPU 的数据缓存中。这比 CPU 缓存中的负载需要(多)更多的延迟。

然后,当您的指令在流水线中时,它们遵循相同的分支,因为它是来自相同内存源的指令,因为它不需要失效,因为是相同的指针。

如果您实现 4 个功能大致相同的方法,然后在它们之间交换看看会发生什么,那将会很有趣。

是的,这可能是包含排序功能代码(以及计时代码本身)的页面出现页面错误。 10x 还可以包括加速到最大涡轮时钟速度。

不过,缓存是不合理的:您正在定时区域之外编写(微小的)数组,除非编译器以某种方式使用您的 Timer 的构造函数对 init 进行了重新排序。第一次内存分配要慢得多很容易解释,也许第一次必须进行系统调用以获取新页面,但后来调用 new(构造 std::vector)就已经抓取了-空闲列表中的热缓存内存。

训练分支预测器也可能是一个重要因素,但您预计在现代的 TAGE 分支预测器之前需要花费超过 1 运行 Intel CPU,或现代 AMD 中的感知器预测器,"learned" 所有分支的完整模式。但也许他们在第一个 运行.

之后就接近了

请注意,通过在每次调用时使用 srand(),您每次都会生成 相同的 随机数组。 来测试是否分支预测是解释,删除 srand 这样你每次都会得到不同的数组,看看时间是否保持更高。

您使用什么 CPU、编译器版本/选项等?