【问题标题】:Is there memset() that accepts integers larger than char?是否有接受大于 char 的整数的 memset()?
【发布时间】:2010-09-11 15:40:24
【问题描述】:

是否有一个版本的 memset() 可以设置大于 1 字节 (char) 的值?例如,假设我们有一个 memset32() 函数,那么使用它我们可以执行以下操作:

int32_t array[10];
memset32(array, 0xDEADBEEF, sizeof(array));

这将在数组的所有元素中设置值 0xDEADBEEF。目前在我看来,这只能通过循环来完成。

具体来说,我对 memset() 的 64 位版本感兴趣。知道这样的事吗?

【问题讨论】:

    标签: c optimization


    【解决方案1】:

    wmemset(3) 是 memset 的宽(16 位)版本。我认为这是你在 C 中最接近的,没有循环。

    【讨论】:

    • -1 表示 16 位。它是wchar_t,在任何正确支持 Unicode 的实现上都是 32 位的。在windows上只有16位,忽略C标准,将UTF-16存储在wchar_t中。
    【解决方案2】:
    void memset64( void * dest, uint64_t value, uintptr_t size )
    {
      uintptr_t i;
      for( i = 0; i < (size & (~7)); i+=8 )
      {
        memcpy( ((char*)dest) + i, &value, 8 );
      }  
      for( ; i < size; i++ )
      {
        ((char*)dest)[i] = ((char*)&value)[i&7];
      }  
    }
    

    (解释,根据 cmets 的要求:当您分配给指针时,编译器假定指针与类型的自然对齐方式对齐;对于 uint64_t,即 8 个字节。memcpy() 不做这样的假设。在某些硬件未对齐访问是不可能的,因此分配不是一个合适的解决方案,除非您知道未对齐访问在硬件上工作时会受到很小或没有惩罚,或者知道它们永远不会发生,或两者兼而有之。编译器将替换小的 memcpy() 和memset()s 具有更合适的代码,因此它看起来并不那么可怕;但是如果您确实知道足够的知识以保证分配将始终有效并且您的分析器告诉您它更快,您可以用分配替换 memcpy。第二个如果要填充的内存量不是 64 位的倍数,则存在 for() 循环。如果你知道它总是会这样,你可以简单地删除该循环。)

    【讨论】:

    • 这个实现超出了我对这个问题的预期:) 谢谢!如果您解释了实现,那就太好了。例如,我不明白为什么使用对 memcpy() 的函数调用而不是赋值。
    【解决方案3】:

    检查您的操作系统文档以获取本地版本,然后考虑仅使用循环。

    编译器可能比您更了解优化任何特定架构上的内存访问,所以让它来完成这项工作。

    将其打包为一个库,并使用编译器允许的所有速度改进优化对其进行编译。

    【讨论】:

      【解决方案4】:

      自己写;即使在 asm 中也是微不足道的。

      【讨论】:

      • 例子?你有win32程序集sn-p吗?
      • 既然这么简单,何不发个sn-p?
      【解决方案5】:

      您真的应该让编译器按照其他人的建议为您优化它。在大多数情况下,该循环可以忽略不计。

      但是,如果这是一些特殊情况并且您不介意特定于平台,并且确实需要摆脱循环,您可以在组装块中执行此操作。

      //pseudo code
      asm
      {
          rep stosq ...
      }
      

      您可能可以通过 google stosq 汇编命令了解具体信息。它不应该超过几行代码。

      【讨论】:

        【解决方案6】:

        没有标准库函数 afaik。因此,如果您正在编写可移植代码,那么您就是在查看循环。

        如果您正在编写不可移植的代码,请检查您的编译器/平台文档,但不要屏住呼吸,因为在这里很少能得到太多帮助。也许其他人会提供确实提供某些东西的平台示例。

        您自己编写的方式取决于您是否可以在 API 中定义调用方保证 dst 指针将充分对齐,以便在您的平台(或平台,如果可移植)上进行 64 位写入。在任何完全具有 64 位整数类型的平台上,malloc 至少会返回适当对齐的指针。

        如果您必须应对不对齐,那么您需要类似月影的答案。编译器可能会内联/展开大小为 8 的 memcpy(如果存在,则使用 32 位或 64 位未对齐的写入操作),因此代码应该非常简洁,但我猜它可能不会是特殊情况目标的整个功能正在对齐。我很想得到纠正,但恐怕我不会。

        因此,如果您知道调用者将始终为您提供与您的体系结构足够对齐的 dst,并且长度是 8 字节的倍数,那么执行一个简单的循环,写入 uint64_t(或任何 64 位 int在你的编译器中),你可能(没有承诺)最终得到更快的代码。你肯定会有更短的代码。

        无论如何,如果您确实关心性能,请对其进行分析。如果速度不够快,请再次尝试进行更多优化。如果它仍然不够快,请询问有关其不够快的 CPU 的 asm 版本的问题。 memcpy/memset 可以从每个平台的优化中获得巨大的性能提升。

        【讨论】:

        • @Steve Jessop,请向我解释 64 位 Windows 或 Linux 对齐注意事项。
        【解决方案7】:

        仅作记录,以下模式使用memcpy(..)。假设我们想用 20 个整数填充一个数组:

        --------------------
        
        First copy one:
        N-------------------
        
        Then copy it to the neighbour:
        NN------------------
        
        Then copy them to make four:
        NNNN----------------
        
        And so on:
        NNNNNNNN------------
        
        NNNNNNNNNNNNNNNN----
        
        Then copy enough to fill the array:
        NNNNNNNNNNNNNNNNNNNN
        

        这需要 O(lg(num)) 次 memcpy(..) 的应用。

        int *memset_int(int *ptr, int value, size_t num) {
            if (num < 1) return ptr;
            memcpy(ptr, &value, sizeof(int));
            size_t start = 1, step = 1;
            for ( ; start + step <= num; start += step, step *= 2)
                memcpy(ptr + start, ptr, sizeof(int) * step);
        
            if (start < num)
                memcpy(ptr + start, ptr, sizeof(int) * (num - start));
            return ptr;
        }
        

        我认为如果 memcpy(..) 使用一些硬件块内存复制功能进行优化,它可能比循环更快,但事实证明,简单循环比使用 -O2 和 -O3 的循环更快。 (至少在 Windows 上使用 MinGW GCC 和我的特定硬件。)如果没有 -O 开关,在 400 MB 数组上,上面的代码大约是等效循环的两倍,在我的机器上需要 417 毫秒,而优化它们两者都达到约 300 毫秒。这意味着它花费的纳秒数与字节数大致相同,一个时钟周期约为一纳秒。所以要么我的机器上没有硬件块内存复制功能,要么memcpy(..)实现没有利用它。

        【讨论】:

        • 现代处理器运行简单循环的速度足以使内存总线饱和,从而使块移动/复制指令变得多余。
        • @MarkRansom 我希望有一些单指令方式,例如将整个页面设置为全零。因为可以想象,给定的 RAM 芯片设计将允许免费添加该功能。但检查这种情况是否发生比查找技术规范甚至正确的技术术语要容易得多。
        • @evgeniSergeev,您可以要求操作系统将页面重新映射为零,这在某些平台上可能比进行内存访问更快。这也假设操作系统有一些预置零页面交给你。
        【解决方案8】:

        如果您只是针对 x86 编译器,您可以尝试类似(VC++ 示例):

        inline void memset32(void *buf, uint32_t n, int32_t c)
        {
          __asm {
          mov ecx, n
          mov eax, c
          mov edi, buf
          rep stosd
          }
        }
        

        否则只需创建一个简单的循环并相信优化器知道它在做什么,就像这样:

        for(uint32_t i = 0;i < n;i++)
        {
          ((int_32 *)buf)[i] = c;
        }
        

        如果你让它变得复杂,它最终会比优化代码更慢,更不用说更难维护了。

        【讨论】:

        • 我会在其中添加一个cld,以确保您不会意外地向后迭代。另外,值得注意的是 ecx 是 qwords 的数量,而不是字节数,所以原来的问题在这里传递 sizeof array 是错误的。
        猜你喜欢
        • 2020-03-23
        • 1970-01-01
        • 2018-08-22
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多