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