是否有一个版本的memset()设置一个大于1个字节(char)的值?例如,假设我们有一个memset32()函数,所以使用它我们可以执行以下操作:
int32_t array[10];
memset32(array, 0xDEADBEEF, sizeof(array));
这将在数组的所有元素中设置值0xDEADBEEF。目前在我看来,这只能用循环来完成。
具体来说,我对64位版本的memset()感兴趣。知道这样的事吗?
答案 0 :(得分:31)
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];
}
}
(解释,如注释中所要求的:当你指定一个指针时,编译器假定指针与类型的自然对齐对齐;对于uint64_t,这是8个字节.memcpy()不做这样的假设。一些硬件未对齐的访问是不可能的,因此赋值不是一个合适的解决方案,除非你知道未对齐的访问在硬件上工作很小或没有惩罚,或者知道它们永远不会发生,或者两者兼而有。编译器将替换小的memcpy()和memset()s有更合适的代码,所以它看起来并不那么糟糕;但是如果你确实知道分配将始终有效并且你的探查器告诉你它更快,你可以用一个赋值替换memcpy。如果要填充的内存量不是64位的倍数,则存在for()循环。如果你知道它总是存在,你可以简单地删除该循环。)
答案 1 :(得分:10)
afaik没有标准的库函数。因此,如果您正在编写可移植代码,那么您正在寻找一个循环。
如果您正在编写非可移植代码,请检查您的编译器/平台文档,但不要屏住呼吸,因为这里很少得到很多帮助。也许其他人会参与提供某些功能的平台示例。
您自己编写的方式取决于您是否可以在API中定义调用者保证dst指针与您的平台(或平台,如果是可移植的)上的64位写入充分对齐。在任何具有64位整数类型的平台上,malloc至少会返回适当对齐的指针。
如果你必须应对不对齐,那么你需要像moonshadow的答案。编译器可以内联/展开大小为8的memcpy(并且如果它们存在则使用32位或64位未对齐的写操作),因此代码应该非常糟糕,但我的猜测是它可能不会特殊情况对齐目标的整个功能。我很想得到纠正,但我不会害怕。
因此,如果您知道调用者将始终为您的体系结构提供足够对齐的dst,并且长度为8个字节的倍数,那么请执行一个简单的循环来编写uint64_t(或者任何64位int是在你的编译器中)你可能(没有承诺)最终得到更快的代码。你肯定会有更短的代码。
无论如何,如果您关心性能,请进行分析。如果它不够快,请再次尝试更多优化。如果它仍然不够快,请问一个关于CPU的asm版本的问题,它不够快。 memcpy / memset可以从每个平台的优化中获得巨大的性能提升。
答案 2 :(得分: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
这需要memcpy(..)
的O(lg(num))个应用程序。
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 ms,而在优化时两者都要大约300毫秒。这意味着它需要与字节大约相同的纳秒数,并且时钟周期约为1纳秒。因此,我的机器上没有硬件块内存复制功能,或者memcpy(..)
实现没有利用它。
答案 3 :(得分:6)
检查您的操作系统文档以获取本地版本,然后考虑使用循环。
编译器可能更了解在任何特定体系结构上优化内存访问,所以让它完成工作。
将其作为库包装并使用编译器允许的所有速度提升优化进行编译。
答案 4 :(得分:4)
wmemset(3)
是memset的宽(16位)版本。我认为这是你最接近C的,没有循环。
答案 5 :(得分:2)
如果您只是针对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;
}
如果你让它变得复杂,那么它最终会比简化优化代码更慢,更不用说维护了。
答案 6 :(得分:1)
你应该让编译器像你别人建议的那样为你优化这个。在大多数情况下,这个循环可以忽略不计。
但是如果这种特殊情况并且您不介意特定于平台,并且确实需要摆脱循环,则可以在汇编块中执行此操作。
//pseudo code
asm
{
rep stosq ...
}
您可以使用google stosq assembly命令获取详细信息。它不应该超过几行代码。
答案 7 :(得分:0)