memset()是否接受大于char的整数?

时间:2008-09-20 17:50:21

标签: c optimization

是否有一个版本的memset()设置一个大于1个字节(char)的值?例如,假设我们有一个memset32()函数,所以使用它我们可以执行以下操作:

int32_t array[10];
memset32(array, 0xDEADBEEF, sizeof(array));

这将在数组的所有元素中设置值0xDEADBEEF。目前在我看来,这只能用循环来完成。

具体来说,我对64位版本的memset()感兴趣。知道这样的事吗?

8 个答案:

答案 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)

写自己的;即使在asm中它也是微不足道的。