Есть ли memset (), который принимает целые числа больше чем char? - PullRequest
31 голосов
/ 20 сентября 2008

Существует ли версия memset (), которая устанавливает значение больше 1 байта (символ)? Например, допустим, у нас есть функция memset32 (), поэтому, используя ее, мы можем сделать следующее:

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

Это установит значение 0xDEADBEEF во всех элементах массива. В настоящее время мне кажется, что это можно сделать только с помощью цикла.

В частности, меня интересует 64-битная версия memset (). Знаете что-нибудь подобное?

Ответы [ 8 ]

31 голосов
/ 20 сентября 2008
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 () более подходящий код, поэтому он выглядит не так ужасно, но если вы знаете достаточно, чтобы гарантировать, что назначение всегда будет работать, и ваш профилировщик скажет вам, что это быстрее, вы можете заменить memcpy назначением. Цикл for () присутствует в том случае, если объем заполняемой памяти не кратен 64 битам. Если вы знаете, что так будет всегда, вы можете просто отбросить этот цикл.)

10 голосов
/ 21 сентября 2008

Нет стандартной библиотечной функции afaik. Поэтому, если вы пишете переносимый код, вы смотрите на цикл.

Если вы пишете непереносимый код, проверьте документацию по вашему компилятору / платформе, но не задерживайте дыхание, потому что здесь редко можно получить большую помощь. Может быть, кто-то еще добавит примеры платформ, которые что-то предоставляют.

Способ, которым вы будете писать свой собственный, зависит от того, можете ли вы определить в API, что вызывающая сторона гарантирует, что указатель dst будет достаточно выровнен для 64-битной записи на вашей платформе (или платформах, если они переносимы). На любой платформе с 64-битным целочисленным типом, по крайней мере, malloc будет возвращать соответствующим образом выровненные указатели.

Если вам нужно справиться с неприсоединением, то вам нужен что-то вроде ответа лунной тени. Компилятор может встроить / развернуть этот memcpy с размером 8 (и использовать 32- или 64-битные операции выравнивания без выравнивания, если они существуют), поэтому код должен быть довольно ловким, но я предполагаю, что он, вероятно, не будет особенным случаем вся функция для пункта назначения выравнивается. Я бы хотел, чтобы меня исправили, но боюсь, что не буду.

Так что, если вы знаете, что вызывающая сторона всегда выдаст вам dst с достаточным выравниванием для вашей архитектуры и длиной, кратной 8 байтам, тогда выполните простой цикл, записывающий uint64_t (или любой другой 64-битный int в вашем компиляторе) и вы, вероятно, (без обещаний) получите более быстрый код. У вас наверняка будет более короткий код.

В любом случае, если вам небезразлична производительность, профилируйте ее. Если это не достаточно быстро, попробуйте еще раз с большей оптимизацией. Если это все еще не достаточно быстро, задайте вопрос о версии asm для процессоров, на которых это не достаточно быстро. memcpy / memset может значительно увеличить производительность за счет оптимизации для каждой платформы.

7 голосов
/ 11 января 2012

Только для записи, следующее использует 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. (По крайней мере, с помощью MinGW GCC на Windows с моим конкретным оборудованием.) Без ключа -O для массива 400 МБ приведенный выше код примерно в два раза быстрее эквивалентного цикла и занимает на моей машине 417 мс, хотя при оптимизации они оба идут примерно до 300 мс. Это означает, что это занимает примерно столько же наносекунд, сколько байтов, а тактовый цикл составляет около наносекунды. Таким образом, либо на моем компьютере нет функции аппаратного копирования памяти блоков, либо реализация memcpy(..) не использует ее.

6 голосов
/ 20 сентября 2008

Проверьте документацию вашей ОС на наличие локальной версии, затем рассмотрите возможность использования цикла.

Компилятор, вероятно, знает больше об оптимизации доступа к памяти в любой конкретной архитектуре, чем вы, поэтому позвольте ему делать свою работу.

Оберните его в библиотеку и скомпилируйте со всеми оптимизациями, улучшающими скорость, которые допускает компилятор.

4 голосов
/ 20 сентября 2008

wmemset(3) - это широкая (16-битная) версия memset. Я думаю, что это самое близкое, что вы получите в C без цикла.

2 голосов
/ 05 марта 2013

Если вы просто нацелены на компилятор 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;
}

Если вы сделаете это сложным, скорее всего, это будет медленнее, чем проще, оптимизировать код, не говоря уже о том, что его сложнее поддерживать.

1 голос
/ 20 сентября 2008

Вы должны действительно позволить компилятору оптимизировать это для вас, как кто-то другой предложил. В большинстве случаев этот цикл будет незначительным.

Но если это какая-то особая ситуация, и вы не против того, чтобы зависеть от платформы, и действительно хотите избавиться от цикла, вы можете сделать это в сборочном блоке.

//pseudo code
asm
{
    rep stosq ...
}

Возможно, вы можете использовать команду сборки Google Stosq для уточнения. Это не должно быть больше, чем несколько строк кода.

0 голосов
/ 20 сентября 2008

написать свой; это тривиально даже в ассм.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...