memset的64位Linux性能问题

我正在debugging一个运行速度比64位Linux ELF可执行文件要慢的应用程序,而不是32位Linux ELF可执行文件。 使用Rational(IBM)Quantify,我跟踪了大部分的性能差异(鼓滚动…) memset 。 奇怪的是, memset在64位可执行文件中花费了很多时间。

我甚至可以用一个简单的小应用程序来看这个:

 #include <stdlib.h> #include <string.h> #define BUFFER_LENGTH 8000000 int main() { unsigned char* buffer = malloc(BUFFER_LENGTH * sizeof(unsigned char)); for(int i = 0; i < 10000; i++) memset(buffer, 0, BUFFER_LENGTH * sizeof(unsigned char)); } 

我build立这样的:
$ gcc -m32 -std=gnu99 -g -O3 ms.c

$ gcc -m64 -std=gnu99 -g -O3 ms.c

time报告的挂钟时间对于-m64版本来说是较长的,Quantify确认额外的时间在memset

到目前为止,我已经在VirtualBox和VMWare(但不是裸机Linux;我意识到我需要做下一步)testing。 花费的额外时间似乎从一个系统到另一个系统有所不同。

这里发生了什么? 我的Google-foo无法发现一个众所周知的问题吗?

编辑:我的系统上的反汇编( gcc ... -S )显示memset被调用作为外部函数:

32位:

 .LBB2: .loc 1 14 0 movl $8000000, 8(%esp) .loc 1 12 0 addl $1, %ebx .loc 1 14 0 movl $0, 4(%esp) movl %esi, (%esp) call memset 

64位:

 .LBB2: .loc 1 14 0 xorl %esi, %esi movl $8000000, %edx movq %rbp, %rdi .LVL1: .loc 1 12 0 addl $1, %ebx .loc 1 14 0 call memset 

系统:

  • CentOS 5.7 2.6.18-274.17.1.el5 x86_64
  • GCC 4.1.2
  • 英特尔(R)酷睿TM i7-2600K CPU @ 3.40GHz / VirtualBox
    (Xeon E5620 @ 2.40GHz / VMWare差异更大)

我相信虚拟化是罪魁祸首:我一直在自己运行一些基准测试(散列随机数生成,顺序搜索,也是64位),发现在VirtualBox的Linux中,代码的运行速度比本机在windows下慢了2倍。 有趣的是,代码没有I / O(除了简单的printf之外, 时间之间 ),并且使用的内存很小(所有数据都适合L1缓存),所以可以认为你可以排除页表管理和TLB开销。

这真的很神秘。 我注意到VirtualBox向虚拟机报告SSE 4.1和SSE 4.2指令不被支持,尽管CPU支持它们,并且使用它们的程序在虚拟机中运行良好(!)。 我没有时间进一步调查这个问题,但你真的应该把它放在真正的机器上。 不幸的是,我的程序不能运行在32位,所以我无法测试在32位模式下的速度降低。

我可以确认,在我的非虚拟Mandriva Linux系统上,x86_64版本稍微慢了一点(约7%)。 在这两种情况下,无论指令集字大小如何,都会调用memset()库函数。

偶然看一下这两个库实现的汇编代码,就会发现x86_64版本要复杂得多。 我认为这主要是因为32位版本只能处理4个可能的对齐情况,而对于64位版本的8个可能的对齐情况。 似乎x86_64 memset()循环已被更广泛地展开,可能是由于不同的编译器优化。

可能导致较慢操作的另一个因素是与使用64位字大小相关的增加的I / O负载。 代码和元数据(指针等)通常在64位应用程序中变得更大。

另外,请记住,大多数发行版中包含的库实现都是针对维护者认为是每个处理器系列当前最低公分母的CPU。 这可能使64位处理器处于劣势,因为32位指令集现在已经稳定了一段时间。

编译示例代码时,编译器会看到固定块大小(〜8MB),并决定使用库版本。 尝试代码更小的块(memset只有几个字节) – 比较反汇编。

虽然我不知道为什么x64版本更慢。 我想你的时间测量代码有一个问题。

从gcc 4.3的更新日志 :

块移动(memcpy)和块集(memset)的代码生成被重写。 GCC现在可以根据正在复制的块的大小以及正在优化的CPU来选择最佳算法(循环,展开循环,带有rep前缀的指令或库调用)。 添加了一个新选项-minline-stringops-dynamic。 使用这个选项,字符串操作的未知大小被扩展,以便通过内嵌代码复制小块,而对于大块则使用库调用。 当库实现能够使用缓存层次结构提示时,这会比-minline-all-stringops产生更快的代码。 选择特定算法的启发式可以通过-mstringop-strategy来覆盖。 最近还内联了不同于0值的memset。

希望这解释了什么编译器设计师试图做(即使这是另一个版本);-)