第一个方法调用比使用相同数据的连续调用花费的时间长 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、编译器版本/选项等?
我正在为我的快速排序执行一些执行时间基准。在对完全相同的输入数据进行的 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、编译器版本/选项等?