我在C中有一堆排序算法,我想进行基准测试。我担心这样做的好方法。可能影响基准测试性能的因素包括(但不限于):实现的特定编码、编程语言、编译器(和编译器选项)、基准测试机,关键是输入数据和时间测量方法。如何将上述变量对基准结果的影响降至最低?
举几个例子,我考虑了两种不同语言上的多个实现来调整前两个变量。此外,我可以使用不同的编译器在相当普通(和特定)的参数上编译代码。现在我将在我的机器上进行测试,它具有涡轮增压等功能,经常将核心运行的东西送上月球。当然,我会禁用它,进行多次跑步,并可能需要他们的平均完成时间来调整。关于输入数据,我将采用不同的数组大小,从非常小到相对大。我不知道理想情况下增量应该是什么样的,也不知道元素的范围应该是什么。此外,我认为应该允许重复的元素。
我知道算法的理论分析解释了所有这些方法,但至关重要的是,我要用实际的基准来补充我的研究。一旦收集到数据,您将如何解决上述问题,并根据这些变量进行调整?我对我所使用的技术感到满意,但对研究一个主题的严格方法则不太满意。非常感谢。
您不能对抽象算法进行基准测试,只能对它们的特定实现进行基准测试。
选择几个不同的相关编译器和机器(例如Haswell、Ice Lake和/或Zen2,以及Apple M1(如果你能买到的话)和/或AArch64云服务器),并测量你的实际实现。如果你关心像ARM Cortex-A53这样的有序CPU,也可以在其中一个上进行测量。(使用GEM5或类似性能模拟器进行模拟可能值得一试。像Intel Silvermont这样的低功耗实现也可能相关,它的无序窗口要小得多,但管道更短,因此分支预测失误的惩罚更小。)
如果某个算法允许在源代码中进行有用的微优化,或者编译器发现了这一点,那么这就是该算法的真正优势。
使用您在实践中为您关心的用例使用的选项进行编译,例如clang -O3 -march=native
或仅-O2
。
在云服务器上进行基准测试会使您很难/不可能获得空闲系统,除非您为一个巨大的实例支付大量费用,但现代AArch64服务器是相关的,并且可能具有不同的内存带宽与分支预测失误成本与缓存大小和带宽的比率。
(你可能会发现,在你测试的所有或大多数系统上,相同的代码是最快的排序实现
Re:尺寸:是的,各种尺寸都不错。
您通常希望使用随机数据进行测试,可能总是从相同的PRNG种子生成,因此每次都对相同的数据进行排序。
你可能还想测试一些不寻常的案例,比如已经排序或几乎排序的案例,因为对这些案例来说速度极快的算法很有用。
如果您关心对整数以外的东西进行排序,那么您可能希望使用不同大小的结构进行测试,并将int
键作为成员。或者是一个做一些工作的比较函数,如果你想探索排序如何处理一个比一条比较机器指令简单的比较函数。
与微基准标记一样,在阵列预热(页面故障)和CPU频率等方面存在许多陷阱。绩效评估的惯用方法?
取其平均完成时间
您可能希望丢弃高异常值,或取对您有影响的中值。通常这意味着";发生了什么事";如果你在相同的数据上运行相同的代码,通常你可以期望相同的性能。(具有页面粒度的代码/堆栈地址的随机化通常不会影响分支在预测器中是否相互混淆,也不会影响数据缓存冲突未命中,但代码某一部分的微小变化可能会通过重新编译时产生的类似影响来改变其他代码的性能。)
如果你想看看当它有机器时它会如何运行,你不想考虑在其他干扰的地方运行。如果您试图在";真实世界";云服务器条件,或者其他线程在实际程序中做其他工作,这是不同的,你需要想出实际的其他负载,这些负载使用一些共享资源,如L3占用空间和内存带宽。
可能影响基准测试性能的因素包括(但不限于):实现的特定编码、编程语言、编译器(和编译器选项)、基准测试机器以及关键的输入数据和时间测量方法。
让我们从一个非常不同的角度来看待这个问题——如何向人类展示信息。
有了2个变量,你会得到一个很好的二维结果网格,可能是这样的:
A = 1 A = 2
B = 1 4 seconds 2 seconds
B = 2 6 seconds 3 seconds
这很容易显示,人类也很容易理解并从中得出结论(例如,从我愚蠢的示例表中,做出两个非常不同的观察结果是微不足道的——">A=1
是A=2
的两倍快(与B无关)";以及">B=1
比B=2
快(与A无关)")。
有了3个变量,你就会得到一个三维的结果网格,有了N个变量,就得到了一个N维的结果网格。人类与";2维屏幕上的3维数据";更多的维度成为灾难。你可以通过";剥落";维度(例如,您可以显示多个2D网格,而不是试图呈现结果的3D网格);但这对人类没有多大帮助。
您的主要目标是减少变量的数量
减少变量数量:
a) 确定每个变量对你想要观察的内容的重要性(例如"哪种算法"将极其重要,"哪种语言"将不那么重要)。
b) 基于重要性合并变量;逻辑分组";。例如,你可能会得到三个";重要性较低";变量(语言、编译器、编译器选项),并将它们合并为单个";语言+编译器+选项";变量
请注意,忽略变量是非常容易的。例如,您可以将";算法1";在一台计算机上;算法2";在几乎相同的计算机上,但忽略了一个事实,即(即使两个基准都使用相同的语言、编译器、编译器选项和CPU)一台计算机具有更快的RAM芯片,并忽略了";RAM速度";作为一个可能的变量。
您的次要目标是减少每个变量可以具有的值的数量
您不希望有12345678百万行的大型表;而且你不想把余生都花在基准测试上来生成这么大的表。
为了减少每个变量可以具有的值的数量:
a) 找出哪些值对最重要
b) 按重要性顺序选择正确数量的值(忽略/跳过所有其他值)
例如,如果您将三个";重要性较低";变量(语言、编译器、编译器选项)转换为单个变量;那么你可能会决定两种可能性("由GCC用-O3
编译的C"one_answers"由MSVC用-Ox
编译的C++")足够重要,需要担心(对于你打算观察的内容),而所有其他可能性都会被忽略。
如何将上述变量对基准结果的影响降至最低?
一旦收集到数据,您将如何解决上述问题并针对这些变量进行调整?
通过识别变量(作为主要目标的一部分)并明确决定变量可能具有的值(作为次要目标的一部份)。
你已经这么做了。我所描述的是一种做人们无论如何都会无意识/本能地做的事情的正式方法例如,您已经确定";涡轮增压";是一个变量,并且您已经决定";禁用涡轮增压";是你关心的变量的唯一值(但请注意,这可能会产生后果——例如,考虑"没有涡轮增压的单线程合并排序"与"不受涡轮增压关闭影响的并行合并排序")。
我希望通过描述正式的方法,你会对自己已经做出的无意识/本能的决定充满信心,并意识到在提出这个问题之前,你已经走上了正确的道路。