【问题标题】:How to fill memory fast with a `int32_t` value?如何用“int32_t”值快速填充内存?
【发布时间】:2011-03-13 20:41:58
【问题描述】:

是否有一个函数(SSEx 内部函数可以)用指定的int32_t 值填充内存?例如,当此值等于 0xAABBCC00 时,结果内存应如下所示:

AABBCC00AABBCC00AABBCC00AABBCC00AABBCC00
AABBCC00AABBCC00AABBCC00AABBCC00AABBCC00
AABBCC00AABBCC00AABBCC00AABBCC00AABBCC00
AABBCC00AABBCC00AABBCC00AABBCC00AABBCC00
...

我可以使用std::fill 或简单的 for 循环,但速度不够快。


在程序开始时只执行一次向量的大小调整,这不是问题。瓶颈正在填满内存。

简化代码:

struct X
{
  typedef std::vector<int32_t> int_vec_t;
  int_vec_t buffer;

  X() : buffer( 5000000 ) { /* some more action */ }
  ~X() { /* some code here */ }

  // the following function is called 25 times per second
  const int_vec_t& process( int32_t background, const SOME_DATA& data );
};

const X::int_vec_t& X::process( int32_t background, const SOME_DATA& data )
{
    // the following one string takes 30% of total time of #process function
    std::fill( buffer.begin(), buffer.end(), background );

    // some processing
    // ...

    return buffer;
}

【问题讨论】:

  • 你为什么不用 SSE 指令自己编码呢?您有 movxxxx 指令来移动内存(一次 128 位)。只是一个循环和动作,应该不难做到。
  • 我承认我有点好奇你的用例可能是什么导致 for 循环不够快。你只是在处理一个巨大的内存块吗?您的平台是否擅长分支?您是否经常在高性能应用(如游戏或其他应用)中执行此操作?
  • @Alexandre C.,SSE 指令没问题,但我认为 WinAPI 中可能已经有一些功能。我不想发明轮子。
  • 您能否发布代码以便我们准确了解您在做什么?这可能有助于我们弄清楚为什么它很慢。另外,我们所说的向量有多大?几百?几十万?
  • @Sean Edwards,我已经添加了代码。

标签: c++ windows winapi visual-c++ memory


【解决方案1】:

我就是这样做的(请原谅它的微软特性):

VOID FillInt32(__out PLONG M, __in LONG Fill, __in ULONG Count)
{
    __m128i f;

    // Fix mis-alignment.
    if ((ULONG_PTR)M & 0xf)
    {
        switch ((ULONG_PTR)M & 0xf)
        {
            case 0x4: if (Count >= 1) { *M++ = Fill; Count--; }
            case 0x8: if (Count >= 1) { *M++ = Fill; Count--; }
            case 0xc: if (Count >= 1) { *M++ = Fill; Count--; }
        }
    }

    f.m128i_i32[0] = Fill;
    f.m128i_i32[1] = Fill;
    f.m128i_i32[2] = Fill;
    f.m128i_i32[3] = Fill;

    while (Count >= 4)
    {
        _mm_store_si128((__m128i *)M, f);
        M += 4;
        Count -= 4;
    }

    // Fill remaining LONGs.
    switch (Count & 0x3)
    {
        case 0x3: *M++ = Fill;
        case 0x2: *M++ = Fill;
        case 0x1: *M++ = Fill;
    }
}

【讨论】:

  • 我很想知道在性能方面如何与 std::fill 进行比较。
  • 对不起,我是一个普通的 C 人。我对 std、fill 或 std::fill 一无所知。
  • 所以你使用内在的 SSE 指令......但这由任何体面的编译器自动完成,例如当您编写 vanilla for 循环时为您提供 gcc 或 icpc。所以我认为没有必要。
【解决方案2】:

我不得不问:您是否明确分析了std::fill 并表明它是性能瓶颈?我猜它会以一种非常有效的方式实现,这样编译器就可以自动生成适当的指令(例如 gcc 上的-march)。

如果它是瓶颈,仍然有可能从算法重新设计(如果可能)中获得更好的收益,以避免设置太多内存(显然是一遍又一遍),这样您使用哪种填充机制就不再重要了使用。

【讨论】:

    【解决方案3】:

    你考虑过使用

    vector<int32_t> myVector;
    myVector.reserve( sizeIWant );
    

    然后使用 std::fill?或者可能是 std::vector 的构造函数,它将持有的项目数和初始化它们的值作为参数?

    【讨论】:

    • 这是一个非常好的观点。如果您要附加到向量,则部分开销可能是调整该向量的大小,这不会在您每次插入到末尾时发生(通常,向量会自动扩展比您需要的大一点)但它会通常足以导致性能下降。使用reserve()预先分配一定的长度。
    • 您也可以使用数组来 100% 确定调整大小不是问题。
    • 是的,最初我以为他试图在他用 malloc() 制作的数组中设置值。我什至没有考虑到这可能是矢量调整大小减慢了他的速度。 :)
    • vector 的大小调整仅在程序开始时执行一次,这不是问题。瓶颈正在填满内存。
    • memcpy 不起作用,因为他实际上需要复制字节序列,而不仅仅是用单个字节填充整个块。
    【解决方案4】:

    不完全确定如何连续设置 4 个字节,但如果你想一次又一次地用一个字节填充内存,你可以使用memset

    void * memset ( void * ptr, int value, size_t num );
    

    填充内存块

    ptr指向的内存块的前num字节设置为指定值(解释为unsigned char)。

    【讨论】:

    • 我不想用一个字节填充内存。 int32_t 中有四个字节。
    【解决方案5】:

    假设您的背景参数中的值数量有限(或者更好,只有 on),也许您应该尝试分配一个静态向量,并简单地使用 memcpy。

    const int32_t sBackground = 1234;
    static vector <int32_t> sInitalizedBuffer(n, sBackground);
    
        const X::int_vec_t& X::process( const SOME_DATA& data )
        {
            // the following one string takes 30% of total time of #process function
            std::memcpy( (void*) data[0], (void*) sInitalizedBuffer[0], n * sizeof(sBackground));
    
            // some processing
            // ...
    
            return buffer;
        }
    

    【讨论】:

      【解决方案6】:

      它可能有点不可移植,但您可以使用重叠的内存副本。 用你想要的模式填充前四个字节并使用 memcpy()。

      int32* p = (int32*) malloc( size );
      *p = 1234;
      memcpy( p + 4, p, size - 4 );
      

      不要以为你可以变得更快

      【讨论】:

      • 它可能不受“支持”,但在 vs2008 中有效。如果需要,我可以提供来源。我也无法在链接页面中找到您所指的内容。
      • 重叠 memcpy 是一个众所周知的错误,推荐它们是不好的建议。 memmove 是用于重叠区域的正确调用。 “不支持,但可以”用于在墓碑上雕刻。
      • 抱歉,您误会了。这不是一个错误。有些人称其为错误,因为他们不理解他们编写的代码的后果。如果这不是所需的行为,则会有使用 memmove() 的明确警告。它使用 CPU 的批量复制操作按设计工作
      • 错了。现代 memcpy 实现使用 SSE/AVX/AVX-512 而不是 rep movsb 同时复制 16/32/64 字节并实现更高的性能
      【解决方案7】:

      我刚刚用 g++ 测试了 std::fill 并进行了全面优化(启用了 SSE 等):

      #include <algorithm>
      #include <inttypes.h>
      
      int32_t a[5000000];
      
      int main(int argc,char *argv[])
      {
          std::fill(a,a+5000000,0xAABBCC00);
          return a[3];
      }
      

      内部循环看起来像:

      L2:
          movdqa  %xmm0, -16(%eax)
          addl    $16, %eax
          cmpl    %edx, %eax
          jne L2
      

      看起来 0xAABBCC00 x 4 已加载到 xmm0 并且一次移动 16 个字节。

      【讨论】:

      • 我很好奇,为什么这段使用比较和条件跳转的代码仍然比REPNZ STOS 或类似代码快?
      • @Philipp:它一次复制 16 个字节。比较和条件跳转不一定很昂贵。这在很大程度上取决于上下文,正在执行的其他指令。
      【解决方案8】:

      感谢大家的回答。我检查了wj32's solution ,但它显示的时间与std::fill 非常相似。借助 memcpy 函数,我当前的解决方案(在 Visual Studio 2008 中)比 std::fill 快 4 倍:

       // fill the first quarter by the usual way
       std::fill(buffer.begin(), buffer.begin() + buffer.size()/4, background);
       // copy the first quarter to the second (very fast)
       memcpy(&buffer[buffer.size()/4], &buffer[0], buffer.size()/4*sizeof(background));
       // copy the first half to the second (very fast)
       memcpy(&buffer[buffer.size()/2], &buffer[0], buffer.size()/2*sizeof(background));
      

      在生产代码中,需要添加检查 buffer.size() 是否可被 4 整除并为此添加适当的处理。

      【讨论】:

        【解决方案9】:

        vs2013 和 vs2015 可以将普通的 for 循环优化为 rep stos 指令。这是填充缓冲区的最快方法。您可以像这样为您的类型指定std::fill

        namespace std {
            inline void fill(vector<int>::iterator first, vector<int>::iterator last, int value){
                for (size_t i = 0; i < last - first; i++)
                    first[i] = value;
            }
        }
        

        顺便说一句。要让编译器进行优化,缓冲区必须由下标运算符访问。

        它不适用于 gcc 和 clang。他们都将代码编译为条件跳转循环。它的运行速度与原来的 std::fill 一样慢。尽管wchar_t 是32 位的,wmemset 没有像memset 这样的汇编工具。所以你必须编写汇编代码来进行优化。

        【讨论】:

          猜你喜欢
          • 1970-01-01
          • 1970-01-01
          • 2021-03-02
          • 1970-01-01
          • 1970-01-01
          • 2011-07-02
          • 1970-01-01
          • 2010-11-09
          • 1970-01-01
          相关资源
          最近更新 更多