如何用“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”值快速填充内存?的主要内容,如果未能解决你的问题,请参考以下文章