如何用“int32_t”值快速填充内存?

Posted

技术标签:

【中文标题】如何用“int32_t”值快速填充内存?【英文标题】:How to fill memory fast with a `int32_t` value? 【发布时间】: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,我已经添加了代码。 【参考方案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 );

不要以为你可以变得更快

【讨论】:

不支持。见***.com/questions/387654/… 它可能不受“支持”,但在 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 这样的汇编工具。所以你必须编写汇编代码来进行优化。

【讨论】:

以上是关于如何用“int32_t”值快速填充内存?的主要内容,如果未能解决你的问题,请参考以下文章

15 | 二分查找(上):如何用最省内存的方式实现快速查找功能?

如何用汇编语言读取内存值

如何用sql实现自动填充日期

如何用Redis缓存改善数据库查询性能?

如何用条件填充缺失值?

如何用前一行的值填充空列?