memset的64位Linux性能问题

时间:2012-01-23 07:56:28

标签: c linux performance 32bit-64bit

我正在调试一个运行速度相当慢的应用程序,当构建为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));
}
我是这样建造的:
$ gcc -m32 -std=gnu99 -g -O3 ms.c

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

time生成的-m64报告的挂钟时间较长,memset版本,Quantify确认额外时间用于gcc ... -S

到目前为止,我已经在VirtualBox和VMWare中进行了测试(但不是裸机Linux;我意识到我需要接下来这样做)。从一个系统到另一个系统,花费的额外时间似乎有所不同。

这里发生了什么?是否存在我的Google-foo无法发现的众所周知的问题?

编辑:我系统上的反汇编(memset)显示正在调用.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 作为外部函数:

32位:

.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

64位:

{{1}}

系统:

  • CentOS 5.7 2.6.18-274.17.1.el5 x86_64
  • GCC 4.1.2
  • Intel(R)Core(TM)i7-2600K CPU @ 3.40GHz / VirtualBox
    (Xeon E5620 @ 2.40GHz / VMWare上的差异更严重)

3 个答案:

答案 0 :(得分:1)

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

随意查看两个库实现的汇编代码,可以发现x86_64版本显着更复杂。我认为这主要与32位版本仅处理4种可能的对齐情况的事实有关,而64位版本的8种可能的对齐情况。似乎x86_64 memset()循环已经被更广泛地展开,可能是由于不同的编译器优化。

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

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

答案 1 :(得分:1)

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

这确实很神秘。我注意到VirtualBox向VM报告不支持SSE 4.1和SSE 4.2指令,即使CPU支持它们,并且使用它们的程序在VM中运行良好(!)。我没有时间进一步研究这个问题,但你真的应该把它放在一台真机上。不幸的是,我的程序不会运行32位,所以我无法测试32位模式下的减速。

答案 2 :(得分:0)

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

虽然我不知道为什么x64版本会变慢。我想您的时间测量代码存在问题。

来自changelog of gcc 4.3

  

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

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