2008-09-20 2 views
26

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

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

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

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

ответ

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

+0

Эта реализация больше, чем я общался с вопросом :) Спасибо! Было бы неплохо, если бы вы объяснили это. Например, я не могу понять, зачем использовать вызов функции для memcpy() вместо назначения. – gnobal 2008-09-21 08:20:31

3

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

+6

-1 для 16-бит. Это `wchar_t`, который является 32-разрядным для любой реализации, которая поддерживает Unicode правильно. Это всего лишь 16 бит в окнах, которые игнорируют стандарт C и хранят UTF-16 в `wchar_t`. – 2010-08-07 18:45:39

5

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

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

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

0

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

+1

пример? У вас есть фрагмент сборки win32? – bobobobo 2009-10-01 18:37:16

+2

Если это так просто, почему бы не опубликовать фрагмент? – MestreLion 2015-02-20 02:24:49

1

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

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

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

Возможно, вы можете установить сборку google stosq для специфики. Это не должно быть несколько строк кода.

9

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

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

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

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

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

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

+0

@Steve Jessop, Пожалуйста, объясните мне 64-битные соображения выравнивания Windows или Linux. – Frank 2015-08-08 08:30:41

5

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

+0

Современные процессоры могут запускать простую петлю достаточно быстро, чтобы насытить шину памяти, делая избыточные команды перемещения блоков/копирования. – 2014-10-28 22:30:11

1

Если вы только таргетинг х86 компилятор, вы можете попробовать что-то вроде (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; 
} 

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

Смежные вопросы