我在64位机器上优化memset的尝试比标准实现花费更多时间.有人可以解释一下原因吗?

adi*_*hit 6 c 64-bit memset

(机器是x86 64位运行SL6)

我试图看看我是否可以在我的64位机器上优化memset.根据我的理解,memset逐字节地设置并设置值.我假设如果我以64位为单位,它会更快.但不知何故需要更多时间.有人可以看看我的代码并提出原因吗?

/* Code */
#include <stdio.h>
#include <time.h>
#include <stdint.h>
#include <string.h>

void memset8(unsigned char *dest, unsigned char val, uint32_t count)
{
    while (count--)
        *dest++ = val;
}
void memset32(uint32_t *dest, uint32_t val, uint32_t count)
{
    while (count--)
        *dest++ = val;
}
void
memset64(uint64_t *dest, uint64_t val, uint32_t count)
{
    while (count--)
        *dest++ = val;
}
#define CYCLES 1000000000
int main()
{
    clock_t start, end;
    double total;
    uint64_t loop;
    uint64_t val;

    /* memset 32 */
    start = clock();
    for (loop = 0; loop < CYCLES; loop++) {
        val = 0xDEADBEEFDEADBEEF;
        memset32((uint32_t*)&val, 0, 2);
    }
    end = clock();
    total = (double)(end-start)/CLOCKS_PER_SEC;
    printf("Timetaken memset32 %g\n", total);

    /* memset 64 */
    start = clock();
    for (loop = 0; loop < CYCLES; loop++) {
        val = 0xDEADBEEFDEADBEEF;
        memset64(&val, 0, 1);
    }
    end = clock();
    total = (double)(end-start)/CLOCKS_PER_SEC;
    printf("Timetaken memset64 %g\n", total);

    /* memset 8 */
    start = clock();
    for (loop = 0; loop < CYCLES; loop++) {
        val = 0xDEADBEEFDEADBEEF;
        memset8((unsigned char*)&val, 0, 8);
    }
    end = clock();
    total = (double)(end-start)/CLOCKS_PER_SEC;
    printf("Timetaken memset8 %g\n", total);

    /* memset */
    start = clock();
    for (loop = 0; loop < CYCLES; loop++) {
        val = 0xDEADBEEFDEADBEEF;
        memset(&val, 0, 8);
    }
    end = clock();
    total = (double)(end-start)/CLOCKS_PER_SEC;
    printf("Timetaken memset %g\n", total);

    printf("-----------------------------------------\n");
}

/*Result*/
Timetaken memset32 12.46
Timetaken memset64 7.57
Timetaken memset8 37.12
Timetaken memset 6.03
-----------------------------------------
Run Code Online (Sandbox Code Playgroud)

看起来标准的memset比我的实现更优化.我试着查看代码,到处都看到memset的实现与我为memset8所做的相同.当我使用memset8时,结果更像我期望的并且与memset非常不同.有人可以建议我做错了什么吗?

Ste*_*non 10

实际的memset实现通常在汇编时手动优化,并使用目标硬件上可用的最宽对齐写入.在x86_64上将至少有16B个商店(movaps例如).它也可以利用预取(最近这种情况不太常见,因为大多数架构都有常规访问模式的良好自动流预取器),流媒体存储或专用指令(历史rep stos上在x86上速度非常慢,但在最近的微体系结构上速度相当快) .你的实现不会做这些事情.系统实现速度更快不应该是非常令人惊讶的.

例如,考虑OS X 10.8中使用的实现(已在10.9中取代).这是适度大小缓冲区的核心循环:

.align 4,0x90
1:  movdqa %xmm0,   (%rdi,%rcx)
    movdqa %xmm0, 16(%rdi,%rcx)
    movdqa %xmm0, 32(%rdi,%rcx)
    movdqa %xmm0, 48(%rdi,%rcx)
    addq   $64,      %rcx
    jne    1b
Run Code Online (Sandbox Code Playgroud)

当以16B /周期在Haswell以前的微体系结构上命中高速缓存时,该循环将使LSU饱和.基于64位存储的实现,例如你memset64不能超过8B /周期(甚至可能不会达到这个,取决于所讨论的微体系结构以及编译器是否展开你的循环).在Haswell上,一个使用AVX存储的实现,或者rep stos可以更快地实现32B /周期.


lda*_*v1s 3

根据我的理解,memset 逐字节进行并设置值。

该设施所做的事情的细节memset取决于实施。依赖这一点通常是一件好事,因为我确信实现者对系统有广泛的了解,并且知道各种技术来使事情尽可能快。

为了更详细地说明一下,让我们看一下:

memset(&val, 0, 8);
Run Code Online (Sandbox Code Playgroud)

当编译器看到这一点时,它会注意到一些事情,例如:

  • 填充值为0
  • 填充的字节数为8

然后根据val或 的位置&val(在寄存器中、在内存中……)选择要使用的正确指令。但是,如果memset需要进行函数调用(如您的实现),那么这些优化都是不可能的。即使它无法做出编译时决策,例如:

memset(&val, x, y); // no way to tell at compile time what x and y will be...
Run Code Online (Sandbox Code Playgroud)

您可以放心,有一个用汇编程序编写的函数调用对于您的平台来说将尽可能快。

  • `memset` *表现*就像是逐字节的。但如今所有有能力的编译器都将其视为内置内部指令,并且经常使用 SSE 或 AVX 指令。 (5认同)