最近我用C++写了一个小程序(老实说,它更多的是C+类),并在Mac和Linux机器上测试了性能。
即使硬件是可比的,性能是如此不同,比我真的有一些奇怪的事情。
首先说明一些细节:
输入:约200 MB压缩数据
程序的操作:它解压缩数据,然后将其加载到内存中,并执行许多数据访问以执行数据之间的连接。2程序是顺序的(没有额外的线程或进程)。
输出:屏幕上显示的一些字符串
在Linux机器中使用GCC 4.8.1编译代码,在Mac机器中使用GCC 4.8.2编译代码。在这两种情况下,编译器都是使用参数调用的:
gcc -c -O3 -fPIC -MD -MF $(patsubst %.o,%.d,$@) //The last three arguments are to create the dependencies between the files
Mac(操作系统=mac mavericks 10.9)机器是一台macbook pro,配备了2.3 GHz的英特尔酷睿I7(四核)256 KB二级缓存、6 MB三级缓存、8 GB的DDR3 1600 Mhz和256 GB的固态硬盘。
Linux机器(内核2.6.32-358)有一个英特尔E5-2620 2.0 GHz(六核)16 MB高速缓存,64 GB的DDR3 1600 Mhz,和一个256 GB的SSD磁盘。两台机器都应该使用桑迪Bridge架构(也许Mac是常春藤桥,但无论如何这应该不会有很大的区别)。
现在,如果我在linux机器上启动程序,那么它需要217毫秒来完成,而如果我在Mac机器上启动它,它需要132毫秒:这使得linux代码慢了1. 6倍!!
现在,我知道这两台机器有不同的操作系统和硬件,但我发现这样的减速太大,不符合这些因素,我觉得背后一定有其他原因。
请注意,这个计时是在所有数据加载到内存之后进行的,我确信在这段时间内程序不会切换到磁盘。因此,我可以排除SSD磁盘的问题。
现在,我真的不知道是什么原因导致了这样的速度下降?内存基本上是相当的,而CPU只是慢了一点。
会不会是GCC在linux上产生了比mac更糟糕的代码?
会不会是Linux操作系统明显比Mac差?
我觉得这两件事都难以置信。有什么帮助吗?
编辑:
我意识到我没有提到我是如何计时的:我使用了加速计时库,我只测量调用主函数所需的时间,比如:
time = now();
function();
duration = now() - time;
print(duration);
**EDIT 2:**经过一些测试后,我们设法用一个简单得多(也很愚蠢)的程序重现了性能上的差异:
#include <stdio.h>
#include <stdlib.h>
#include <stdint.h>
char in1[10000000];
char in2[10000000];
static inline uint64_t rdtscp (void) {
uint64_t low, high;
uint64_t aux;
__asm__ __volatile__ (
".byte 0x0f,0x01,0xf9"
: "=a" (low), "=d" (high), "=c" (aux)
);
return low | (high << 32);
}
int main(int argc, char** argv) {
uint64_t counter = rdtscp();
for(int i = 0; i < 10000000; ++i) {
in1[i] = (char)i * 200;
in2[i] = (char)i * 100;
}
int joins = 0;
for(int j = 0; j < 10000000; ++j) {
int el = in1[j];
for(int m = 0; m < 10000000; m++) {
if (in2[m] == el) {
joins++;
break;
}
}
}
printf("Joins %d Cycles total %ld\n", joins, (rdtscp() - counter));
return 0;
}
请不要看程序的操作。它们没有什么意义。我们试图重现的是一系列访问内存的操作和简单的操作。
我们在Mac上启动了这个程序,输出结果是:
Joins 10000000 Cycles total 589015641
而在linux机器上,它是:
Joins 10000000 Cycles total 838198832
显然linux版本需要更多的CPU周期,这可能是访问内存所需的。现在的问题是:为什么内存访问速度较慢?
一个原因可能是in 1和in 2不适合CPU缓存,这需要一些RAM访问。正如Roy Longbottom所指出的,linux中的内存确实是ECC,这可能是性能较低的原因。如果我们将此与CPU速度略低的差异结合起来,桑迪和ivy bridge之间的差异,那么我们可能有一个很好的解释这种差异。
不管怎样,谢谢大家的提示!
4条答案
按热度按时间ccgok5k51#
这两个系统都遵循System V AMD 64 ABI,因此gcc应该不会有什么不同。不幸的是,系统性能中的随机效应如今相当普遍,因此有时候你可以通过像重新排序链接顺序这样愚蠢的事情来获得显著的性能差异(参见Mytkowicz et al.,'' Producing wrong data without doing any apparently wrong..,http://citeseer.ist.psu.edu/viewdoc/summary?doi=10.1.1.163.8395)。
以下是一些关于如何分析这一点的建议:
1.多做一次试验。我个人至少做11次试验,并比较中位数(以及各个四分位数,但这可能比你关心的要多)。这样可以避免一些随机效应。
1.将所有输出通过管道传输到一个文件中,以最大限度地减少UI影响。
1.检查你的性能计数器。在Linux上,你可以使用
perf
工具。检查major-faults
,这意味着你有页面错误,需要转到磁盘(当然,不太可能在多次运行)。只有这样,你才能排除磁盘造成的影响。不幸的是,OS X没有(据我所知)这样简单的方法来收集性能计数器。1.您可以尝试使用
-mcpu
来强制使用相同的目标指令集。1.比较实际的缓存大小。
dmidecode -t cache
在Linux端执行此操作,但您必须是root用户。您的机器在这方面可能会有相关的差异。1.如果您的程序运行于多个阶段,请尝试分别对它们进行基准测试。
祝你好运!
blpfk2vs2#
从另一个Angular 来看,运行时间的差异只有85毫秒,这是 * 微小 *。
你到底在测量什么?如果是整个程序运行时,包括启动和拆卸(例如使用Unix
time
命令),那么差异很容易是由于所涉及的动态链接器:至少在Linux上,你的程序在实际执行之前会链接到libstdc++
系统。如果MacOS的动态链接器稍微快一点(或者程序在Mac上是静态链接的?),这就很容易解释这种差异。或者甚至可能是写入终端所花费的时间。例如,在Linux上,
gnome-terminal
通常被认为是“慢”的,因为它使用了抗锯齿字体和完全的Unicode支持。如果你使用xterm
,你的程序运行得更快吗?如果你将输出重定向到/dev/null
,会发生什么?axzmvihb3#
实际上,如果您考虑到不同的频率(如果您的程序受CPU限制而不受内存限制,这可能很关键,因为您还没有告诉我们您的代码是做什么的),那么差异将减少到约1.43。
然而,如果其中一个CPU是基于IvyBridge的,则可能会有相当大的差异。架构确实没有发生显著变化,但在对大量应用程序进行基准测试时,有些变化可能并不明显,但在特定应用程序上可能会很关键。在您的案例中,您没有显示任何代码,但由于您处理的是大型内存结构,因此可能与其中一个相关
*自适应填充策略已说明here
*动态预取调节,已提到here。
*下一页预取,已提到here
关于实际的实现没有太多的细节,但是对第一个实现的逆向工程非常令人印象深刻,第二个和第三个名字本身就说明了问题(您可以通过禁用两台计算机上的预取并再次比较来验证这是否是问题所在)。这些功能对于某些占用内存的工作负载可能非常关键(尤其是延迟关键型缓存),但如果不知道您对三级缓存的依赖程度,就很难判断
我还建议您确保不要使用特定于操作系统的库版本或特定于编译器版本的内部函数,Apple的人可能在优化一些基本操作方面做得更好
fkaflof64#
我试图在我的Core 2 Duo PC上通过Linux Ubuntu编译该代码。我无法让rdtscp工作,而是使用了CPU时间计数器。该程序仅使用-O3选项编译。C程序的关键部分和汇编列表如下所示。该PC可以根据需要选择2.4 GHz或1.6 GHz,以产生不同的性能(在1.6和2.4 GHz之间)。1.6和2.4 GHz的结果如下所示。我添加了额外的计数(浮点),以发现发生了什么。然后每秒连接的速度没有什么不同。
每秒联接的结果与CPU MHz成比例,如果与主内存速度相关,则不太可能。将数组和循环计数增加10倍和100倍,每秒产生的联接数相同,这表明可以忽略内存速度。
因此,我们只剩下Turbo Boost下的相对GHz、生成的相同机器代码(注意对齐)以及桑迪Bridge与Ivy Bridge的效果。使用额外的计数器,可以计算执行的汇编指令数--我在计算时迷路了。