2008-09-20 12 views
26

¿Hay una versión de memset() que establece un valor que es más grande que 1 byte (char)? Por ejemplo, supongamos que tenemos una función memset32(), por lo que el uso que podemos hacer lo siguiente:¿Hay memset() que acepta números enteros más grandes que char?

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

Esto establecerá el valor 0xLOQUESEA en todos los elementos de la matriz. Actualmente me parece que esto solo se puede hacer con un bucle.

Específicamente, estoy interesado en una versión de 64 bits de memset(). ¿Sabes algo así?

Respuesta

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]; 
    } 
} 

(Explicación, conforme a lo solicitado en los comentarios: cuando se asigna a un puntero, el compilador asume que el puntero está alineado a la alineación natural del tipo, por uint64_t, que es de 8 bytes memcpy() no hace. En algunos hardware, los accesos no alineados son imposibles, por lo que la asignación no es una solución adecuada a menos que sepa que los accesos no alineados funcionan en el hardware con una penalización pequeña o nula, o saben que nunca ocurrirán, o ambas cosas.() sy memset() s con un código más adecuado, por lo que no es tan horrible como parece, pero si sabe lo suficiente para garantizar la asignación siempre funcionará y su generador de perfiles le dice que es más rápido, puede reemplazar el memcpy con un asignación. El segundo bucle for() está presente en caso de que la cantidad de memoria que se va a llenar no sea un múltiplo de 64 bits. Si sabe que siempre lo será, simplemente puede soltar ese bucle).

+0

Esta implementación es más de lo que esperaba con la pregunta :) ¡Gracias! Hubiera sido bueno si explicaras la implementación. Por ejemplo, no puedo entender por qué usar una llamada de función a memcpy() en lugar de una asignación. – gnobal

3

wmemset(3) es la versión ancha (16 bits) de memset. Creo que es lo más cercano que vas a obtener en C, sin un bucle.

+6

-1 para 16 bits. Es 'wchar_t' que es de 32 bits en cualquier implementación que admita Unicode correctamente. Solo tiene 16 bits en Windows, que ignora el estándar C y almacena UTF-16 en 'wchar_t'. –

5

Consulte la documentación de su sistema operativo para obtener una versión local, luego considere usar el bucle.

El compilador probablemente sepa más acerca de cómo optimizar el acceso a la memoria en cualquier arquitectura en particular que usted, así que deje que haga el trabajo.

Concluye como una biblioteca y compila con todas las optimizaciones de mejora de velocidad que permite el compilador.

0

escribe el tuyo propio; es trivial incluso en asm.

+1

ejemplo? ¿Tienes un fragmento de ensamblaje win32? – bobobobo

+2

Si es tan trivial, ¿por qué no publicar un fragmento? – MestreLion

1

Realmente debería dejar que el compilador optimice esto para usted como alguien más lo sugirió. En la mayoría de los casos, ese ciclo será insignificante.

Pero si esto es una situación especial y no le importa ser específico de la plataforma, y ​​realmente necesita deshacerse del lazo, puede hacerlo en un bloque de montaje.

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

Puede probablemente google mando instalado stosq para los detalles. No debería ser más que unas pocas líneas de código.

9

No hay una función de biblioteca estándar afaik. Entonces, si estás escribiendo código portátil, estás viendo un bucle.

Si está escribiendo código no portátil, consulte la documentación de su compilador/plataforma, pero no contenga la respiración porque es raro obtener mucha ayuda aquí. Tal vez alguien más se involucrará con ejemplos de plataformas que proporcionan algo.

La forma en que escribiría la suya depende de si puede definir en la API que la persona que llama garantiza que el puntero dst estará lo suficientemente alineado para escrituras de 64 bits en su plataforma (o plataformas si es portátil). En cualquier plataforma que tenga un tipo de entero de 64 bits, malloc al menos devolverá punteros adecuadamente alineados.

Si tiene que lidiar con la falta de alineación, entonces necesita algo así como la respuesta de la sombra de luna. El compilador puede alinear/desenrollar esa memcpy con un tamaño de 8 (y usar operaciones de escritura no alineadas de 32 o 64 bits, si es que existen), por lo que el código debería ser bastante nippy, pero creo que probablemente no sea un caso especial toda la función para el destino está alineado. Me gustaría que me corrijan, pero temo que no lo seré.

Así que si sabe que la persona que llama siempre le dará un dst con suficiente alineación para su arquitectura, y una longitud que es un múltiplo de 8 bytes, entonces haga un bucle simple escribiendo uint64_t (o lo que sea el 64-bit int está en tu compilador) y probablemente (sin promesas) termines con un código más rápido. Seguramente tendrás un código más corto.

Cualquiera que sea el caso, si le importa el rendimiento, perfilelo. Si no es lo suficientemente rápido, inténtelo de nuevo con más optimización. Si todavía no es lo suficientemente rápido, formule una pregunta sobre una versión de ASM para la (s) CPU (s) en las que no es lo suficientemente rápido. memcpy/memset puede obtener aumentos de rendimiento masivos de la optimización por plataforma.

+0

@Steve Jessop, explíqueme las consideraciones de alineación de Windows o Linux de 64 bits. – Frank

5

Para el registro, lo siguiente usa memcpy(..) en el siguiente patrón. Supongamos que queremos llenar una matriz con 20 números enteros:

-------------------- 

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 

Esto toma aplicaciones de 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; 
} 

pensé que podría ser más rápido que un bucle si memcpy(..) se optimizó el uso de algunas funciones de copia de memoria de bloques de hardware, pero resulta que un bucle simple es más rápido que el anterior con O2 y O3. (Al menos usar MinGW GCC en Windows con mi hardware particular). Sin el modificador -O, en una matriz de 400 MB el código anterior es aproximadamente el doble de rápido que un ciclo equivalente, y lleva 417 ms en mi máquina, mientras que con la optimización ambos van a unos 300 ms. Lo que significa que tarda aproximadamente el mismo número de nanosegundos que los bytes, y un ciclo de reloj es de aproximadamente un nanosegundo. Entonces, o bien no hay funcionalidad de copia de memoria de bloque de hardware en mi máquina, o la implementación memcpy(..) no la aprovecha.

+0

Los procesadores modernos pueden ejecutar un bucle simple lo suficientemente rápido como para saturar el bus de memoria, haciendo que las instrucciones de bloqueo/copia de bloques sean redundantes. –

+0

@MarkRansom Esperaba que hubiera alguna forma de instrucción individual para, por ejemplo, establecer una página completa para ceros a todos.Porque es concebible que un diseño dado de un chip RAM permita agregar esa funcionalidad de forma gratuita. Pero es mucho más fácil verificar si eso está sucediendo que encontrar la especificación de la tecnología, o incluso el término técnico correcto para esto. –

+0

@evgeniSergeev, podría pedirle al sistema operativo que vuelva a asignarle la página a ceros, lo que en algunas plataformas puede ser más rápido que hacer accesos a la memoria. También esto supone que el sistema operativo tiene algunas páginas predeterminadas para entregarle. – rsaxvc

1

Si acaba de focalización un compilador x86 usted podría intentar algo así como (VC++ ejemplo):

inline void memset32(void *buf, uint32_t n, int32_t c) 
{ 
    __asm { 
    mov ecx, n 
    mov eax, c 
    mov edi, buf 
    rep stosd 
    } 
} 

De lo contrario sólo hacer un lazo simple y confiar en que el optimizador de saber lo que está haciendo, simplemente algo como:

for(uint32_t i = 0;i < n;i++) 
{ 
    ((int_32 *)buf)[i] = c; 
} 

Si usted lo hace más probable complicados son que va a terminar más lento de lo más simple a optimizar el código, por no mencionar más difícil de mantener.

+1

Lanzaría un 'cld' allí para asegurarme de que no estás retrocediendo por accidente. Además, vale la pena señalar que ecx es el número de qwords, no bytes, por lo que la pregunta original sería incorrecta para pasar 'sizeof array' aquí. – riv

Cuestiones relacionadas