零内存的速度比使用memset更快?

mae*_*aep 58 c std

我知道这memset(ptr, 0, nbytes)很快,但有更快的方法(至少在x86上)?

我假设memset使用mov,但是当大多数编译器将内存归零时,xor因为它更快,更正确吗?edit1:错了,因为GregS指出只适用于寄存器.我在想什么?

另外我问一个比我更熟悉汇编程序的人看看stdlib,他告诉我x86 memset没有充分利用32位宽的寄存器.然而当时我很累,所以我不太确定我是否理解正确.

edit2:我重新审视了这个问题并进行了一些测试.这是我测试的:

    #include <stdio.h>
    #include <malloc.h>
    #include <string.h>
    #include <sys/time.h>

    #define TIME(body) do {                                                     \
        struct timeval t1, t2; double elapsed;                                  \
        gettimeofday(&t1, NULL);                                                \
        body                                                                    \
        gettimeofday(&t2, NULL);                                                \
        elapsed = (t2.tv_sec - t1.tv_sec) * 1000.0 + (t2.tv_usec - t1.tv_usec) / 1000.0; \
        printf("%s\n --- %f ---\n", #body, elapsed); } while(0)                 \


    #define SIZE 0x1000000

    void zero_1(void* buff, size_t size)
    {
        size_t i;
        char* foo = buff;
        for (i = 0; i < size; i++)
            foo[i] = 0;

    }

    /* I foolishly assume size_t has register width */
    void zero_sizet(void* buff, size_t size)
    {
        size_t i;
        char* bar;
        size_t* foo = buff;
        for (i = 0; i < size / sizeof(size_t); i++)
            foo[i] = 0;

        // fixes bug pointed out by tristopia
        bar = (char*)buff + size - size % sizeof(size_t);
        for (i = 0; i < size % sizeof(size_t); i++)
            bar[i] = 0;
    }

    int main()
    {
        char* buffer = malloc(SIZE);
        TIME(
            memset(buffer, 0, SIZE);
        );
        TIME(
            zero_1(buffer, SIZE);
        );
        TIME(
            zero_sizet(buffer, SIZE);
        );
        return 0;
    }
Run Code Online (Sandbox Code Playgroud)

结果:

除了-O3之外,zero_1是最慢的.zero_sizet是最快的,在-O1,-O2和-O3之间具有大致相同的性能.memset总是比zero_sizet慢.(-O3慢两倍).有趣的是,-O3 zero_1与zero_sizet同样快.然而,反汇编函数的指令大约是指令的四倍(我认为是由循环展开引起的).此外,我尝试进一步优化zero_sizet,但编译器总是超过我,但这并不奇怪.

对于现在的memset胜利,以前的结果被CPU缓存扭曲了.(所有测试都在Linux上运行)需要进一步测试.我接下来会尝试汇编程序:)

edit3:修复了测试代码中的bug,测试结果不受影响

edit4:在重新组装VS2010 C运行时时,我发现memset有一个SSE优化的例程为零.很难打败这个.

小智 32

x86是相当广泛的设备.

对于完全通用的x86目标,具有"rep movsd"的汇编块可能会在时间上将零值爆发到32位内存.尽量确保这项工作的大部分是DWORD对齐的.

对于带有mmx的芯片,带有movq的装配环一次可以达到64位.

您可能能够使用C/C++编译器来使用带有指向long long或_m64的指针的64位写入.目标必须是8字节对齐,以获得最佳性能.

对于带有sse的芯片,movaps很快,但只有当地址是16字节对齐时,所以使用movsb直到对齐,然后使用movaps循环完成清除

Win32有"ZeroMemory()",但我忘记了这是一个宏来memset,还是一个实际的"好"实现.

  • 10 年前的回应,但 ZeroMemory 完全是 memset 的宏:D (4认同)

Ben*_*tto 26

memset通常设计为非常快速的通用设置/归零代码.它处理所有具有不同尺寸和对齐的情况,这会影响您可以用来执行工作的各种指令.根据您所使用的系统(以及您的stdlib来自哪个供应商),底层实现可能是特定于该体系结构的汇编程序,以利用其本机属性.它可能还有内部特殊情况来处理归零的情况(而不是设置其他值).

也就是说,如果你有非常特定的,非常关键的内存归零,那么你自己可以memset通过自己的方式来击败特定的实现.memset并且它在标准库中的朋友总是有趣的目标,一次胜利的编程.:)

  • gcc通常使用`memset()`的内联内置实现.有趣的是,我记得读过有关`memset()`的错误实现,总是将值设置为0 - 这在*年*中没有注意到,因为显然绝大部分时间`memset()`用于设为零! (26认同)
  • 另外:memset在理论上可以有一个0的特殊情况,它在编译时选择(通过内联或作为内部操作),当该参数是文字时.不知道是否有人这样做. (2认同)
  • @Steve Jessop:有趣的想法(特别是它可能是编译时).我记得曾经读过某人的特立独行的memset实现,这个实例对于你实际使用memset的所有内容都有特殊情况. (2认同)
  • *"`memset` 通常被设计为非常快速的通用设置/归零代码......"* - 我认为这不太正确。不能保证 `memset` 会在优化过程中继续存在,因此可能不会发生归零。`memset_s` 提供了这种保证,但 Glibc 的人拒绝提供它。另请参阅 [问题 17879:库缺少 memset_s](https://sourceware.org/bugzilla/show_bug.cgi?id=17879)。 (2认同)

Jen*_*edt 23

现在您的编译器应该为您完成所有工作.至少我知道gcc在优化调用时非常有效memset(尽管更好地检查汇编程序).

然后,memset如果您不需要,请避免:

  • 将calloc用于堆内存
  • ... = { 0 }对堆栈内存使用正确的初始化()

对于非常大的块,mmap如果你有它.这只是从系统"免费"获得零初始化内存.


Spa*_*rky 5

如果我没记错的话(从几年前开始),其中一位高级开发人员正在谈论在PowerPC上快速使用bzero()(规格说我们需要在启动时将几乎所有内存归零).它可能无法很好地转换(如果有的话)到x86,但它可能值得探索.

想法是加载数据缓存行,清除该数据缓存行,然后将清除的数据缓存行写回内存.

对于它的价值,我希望它有所帮助.


bta*_*bta 5

memset 函数被设计得灵活简单,甚至以牺牲速度为代价。在许多实现中,它是一个简单的 while 循环,在给定的字节数上一次复制一个字节的指定值。如果您想要一个更快的 memset(或 memcpy、memmove 等),几乎总是可以自己编写一个。

最简单的定制是执行单字节“设置”操作,直到目标地址对齐为 32 位或 64 位(无论与您的芯片架构匹配),然后开始一次复制完整的 CPU 寄存器。如果您的范围没有以对齐地址结束,您可能必须在末尾执行几个单字节“设置”操作。

根据您的特定 CPU,您可能还有一些流 SIMD 指令可以帮助您。这些通常在对齐地址上工作得更好,因此上述使用对齐地址的技术在这里也很有用。

为了将大块内存清零,您还可以通过将范围分成多个部分并并行处理每个部分(其中部分的数量与核心/硬件线程的数量相同)来获得速度提升。

最重要的是,除非您尝试一下,否则无法判断这些是否有帮助。至少,看看你的编译器针对每种情况发出了什么。看看其他编译器为其标准“memset”发出了什么(它们的实现可能比您的编译器更有效)。


sne*_*rch 5

除非您有特定需求,或者知道您的编译器/ stdlib很烂,否则请坚持使用memset。它是通用的,总体上应该具有不错的性能。同样,编译器可能更容易优化/内联memset(),因为它可以对此提供内在支持。

例如,Visual C ++通常会生成内联版本的memcpy / memset,该内联版本的大小与对库函数的调用一样小,从而避免了push / call / ret开销。当可以在编译时评估size参数时,还有其他可能的优化方法。

就是说,如果您有特定的需求(尺寸总是很小 或* 很大),则可以通过降低到装配级别来提高速度。例如,使用直写操作将大量内存清零而不污染L2缓存。

但这全都取决于-对于普通的东西,请坚持使用memset / memcpy :)