C memset的64位Linux性能问题

C memset的64位Linux性能问题,c,linux,performance,32bit-64bit,C,Linux,Performance,32bit 64bit,我正在调试一个应用程序,当作为64位LinuxELF可执行文件构建时,它的运行速度比作为32位LinuxELF可执行文件构建时要慢得多。使用Rational(IBM)Quantify,我跟踪了大部分性能差异,直到(鼓轮…memset)。奇怪的是,memset在64位可执行文件中花费的时间更长 我甚至可以通过一个小而简单的应用程序看到这一点: #include <stdlib.h> #include <string.h> #define BUFFER_LENGTH 800

我正在调试一个应用程序,当作为64位LinuxELF可执行文件构建时,它的运行速度比作为32位LinuxELF可执行文件构建时要慢得多。使用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));
}
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
  • 通用条款4.1.2
  • 英特尔(R)Core(TM)i7-2600K处理器@3.40GHz/VirtualBox
    (Xeon E5620@2.40GHz/VMWare上的差异更大)

编译示例代码时,编译器会看到固定的块大小(~8MB),并决定使用库版本。试着为更小的块编写代码(内存集只有几个字节)——比较反汇编

虽然我不知道为什么x64版本比较慢。我猜你的时间测量代码有问题

从:

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


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

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

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

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


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

我认为虚拟化是罪魁祸首:我一直在自己运行一些基准测试(批量随机数生成、顺序搜索;以及64位)并且发现,在Linux中,VirtualBox的代码运行速度比在windows下本机慢约2倍。有趣的是,代码不做I/O(除了简单的printf,偶尔在计时之间),并且使用很少的内存(所有数据都可以放入一级缓存),因此人们可能认为可以排除页表管理和TLB开销


这确实是个谜。我注意到VirtualBox向VM报告SSE 4.1和SSE 4.2指令不受支持,即使CPU支持它们,并且使用它们的程序在VM中运行良好(!)。我没有时间进一步调查这个问题,但你真的应该在真正的机器上计时。不幸的是,我的程序无法在32位模式下运行,因此无法在32位模式下测试速度是否减慢。

这是什么体系结构?什么处理器型号?我们可以看到一些反汇编吗?在我的机器上运行的是带gcc 4.4.5的裸机64位Ubuntu,这两个版本的运行时间无法区分。@DavidCitron实际上,
memset()
是一个内部编译器。大多数时候,它是内联的,不会编译成函数调用。我也通过Windows上的VirtualBox在Ubuntu上获得相同的计时。Core i7 870。写入sizeof([unsigned]char)是毫无意义的,根据定义它总是1。处理对齐应该只影响数组边界,这对于8MB数组来说是一个很小的复杂问题。@zvrba:据我所知,它是可测量和一致的:32b:9.487s,64b:10.1347在实际硬件上是一个更大的差异(Xeon W3520,Debian amd64):32b:5.5s,64b:6.5s。请注意-O0/O1/O2/O3几乎没有任何变化。有多少测量值?它们的分布是什么?(我很好奇是否有异常值,它们有多大。)@zvrba每个测量值可能有12个,加上-O的变化。分布stdev<0.1s。
.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