2010-07-09 9 views
11

¿Hay una función (los intrínsecos de SSEx son correctos) que llenará la memoria con un valor especificado de int32_t? Por ejemplo, cuando este valor es igual a 0xAABBCC00 la memoria del resultado debe ser similar:Cómo llenar la memoria rápidamente con un valor `int32_t`?

AABBCC00AABBCC00AABBCC00AABBCC00AABBCC00 
AABBCC00AABBCC00AABBCC00AABBCC00AABBCC00 
AABBCC00AABBCC00AABBCC00AABBCC00AABBCC00 
AABBCC00AABBCC00AABBCC00AABBCC00AABBCC00 
... 

que podría utilizar std::fill o simple para-loop, pero no es lo suficientemente rápido.


Cambio de tamaño de un vector realizado una vez al principio del programa, esto no es un problema. El cuello de botella está llenando la memoria.

código simplificado:

struct X 
{ 
    typedef std::vector<int32_t> int_vec_t; 
    int_vec_t buffer; 

    X() : buffer(5000000) { /* some more action */ } 
    ~X() { /* some code here */ } 

    // the following function is called 25 times per second 
    const int_vec_t& process(int32_t background, const SOME_DATA& data); 
}; 

const X::int_vec_t& X::process(int32_t background, const SOME_DATA& data) 
{ 
    // the following one string takes 30% of total time of #process function 
    std::fill(buffer.begin(), buffer.end(), background); 

    // some processing 
    // ... 

    return buffer; 
} 
+1

¿Por qué no lo codifica usted mismo usando las instrucciones de SSE? Tiene las instrucciones movxxxx para mover la memoria (128 bits a la vez). Es solo un ciclo y se mueve, no debería ser difícil de hacer. –

+1

Admitiré que tengo un poco de curiosidad en cuanto a cuál podría ser su caso de uso para esto, lo que hace que un bucle for no sea lo suficientemente rápido. ¿Estás lidiando con un tremendo bloque de memoria? ¿Su plataforma es mala para la bifurcación? ¿Lo haces con frecuencia en una aplicación de alto rendimiento, como un juego o algo así? –

+0

@Alexandre C., las instrucciones SSE están bien, pero pensé que tal vez ya hay alguna función en WinAPI. No quiero inventar una rueda. –

Respuesta

4

Gracias a todos por sus respuestas. Revisé wj32's solution, pero muestra un tiempo muy similar al std::fill do. Mi solución actual funciona 4 veces más rápido (en Visual Studio 2008) que std::fill con ayuda de la función memcpy:

// fill the first quarter by the usual way 
std::fill(buffer.begin(), buffer.begin() + buffer.size()/4, background); 
// copy the first quarter to the second (very fast) 
memcpy(&buffer[buffer.size()/4], &buffer[0], buffer.size()/4*sizeof(background)); 
// copy the first half to the second (very fast) 
memcpy(&buffer[buffer.size()/2], &buffer[0], buffer.size()/2*sizeof(background)); 

En el código de producción hay que añadir verificación si buffer.size() es divisible por 4 y añadir el manejo apropiado para ese .

9

Esta es la forma en que lo haría (disculpen el Microsoft-dad de la misma):

VOID FillInt32(__out PLONG M, __in LONG Fill, __in ULONG Count) 
{ 
    __m128i f; 

    // Fix mis-alignment. 
    if ((ULONG_PTR)M & 0xf) 
    { 
     switch ((ULONG_PTR)M & 0xf) 
     { 
      case 0x4: if (Count >= 1) { *M++ = Fill; Count--; } 
      case 0x8: if (Count >= 1) { *M++ = Fill; Count--; } 
      case 0xc: if (Count >= 1) { *M++ = Fill; Count--; } 
     } 
    } 

    f.m128i_i32[0] = Fill; 
    f.m128i_i32[1] = Fill; 
    f.m128i_i32[2] = Fill; 
    f.m128i_i32[3] = Fill; 

    while (Count >= 4) 
    { 
     _mm_store_si128((__m128i *)M, f); 
     M += 4; 
     Count -= 4; 
    } 

    // Fill remaining LONGs. 
    switch (Count & 0x3) 
    { 
     case 0x3: *M++ = Fill; 
     case 0x2: *M++ = Fill; 
     case 0x1: *M++ = Fill; 
    } 
} 
+2

Me interesaría comparar esto con Performancewise con std :: fill. –

+6

Lo siento, soy un simple tipo C. No sé nada sobre std, fill o std :: fill. – wj32

+0

, por lo que se usan instrucciones SSE intrínsecas ... pero esto se hace automáticamente por cualquier compilador decente, p. gcc o icpc para usted cuando escribe el vainilla for-loop. Entonces creo que no hay necesidad. –

5

tengo que preguntar: ¿Ha definitivamente perfilado std::fill y demostrado que es el cuello de botella de rendimiento? Supongo que se implementará de una manera bastante eficiente, de modo que el compilador pueda generar automáticamente las instrucciones apropiadas (por ejemplo, -march en gcc).

Si se trata de un cuello de botella, aún es posible obtener un mejor beneficio de un rediseño algorítmico (si es posible) para evitar configurar tanta memoria (aparentemente una y otra vez) que ya no importa qué mecanismo de relleno tu usas.

3

Ha considerado el uso

vector<int32_t> myVector; 
myVector.reserve(sizeIWant); 

y luego usar std :: llenar? ¿O quizás el constructor de un std::vector que toma como argumento el número de elementos que se tienen y el valor para inicializarlos?

+0

Este es un buen punto. Si se agrega a un vector, parte de su sobrecarga puede ser cambiar el tamaño de ese vector, lo que no sucedería cada vez que insertara hasta el final (el vector se expande automáticamente un poco más grande de lo que lo necesita, generalmente) pero lo haría ser lo suficientemente frecuente como para incurrir en un golpe de rendimiento. Use reserve() para preasignar una determinada longitud. –

+1

También puede usar una matriz para estar 100% seguro de que cambiar el tamaño no es el problema. –

+0

Sí, originalmente pensé que estaba tratando de establecer valores en una matriz que hizo con malloc(). Ni siquiera consideré que podría ser el cambio de tamaño del vector ralentizándolo. :) –

0

No estoy seguro de cómo configurar 4 bytes seguidos, pero si desea llenar la memoria con un solo byte una y otra vez, puede usar memset.

void * memset (void * ptr, int value, size_t num); 

bloque de relleno de la memoria

establece el primer num bytes del bloque de memoria apuntada por ptr en el valor especificado (interpretado como una unsigned char).

+2

No quiero llenar la memoria con un byte. Hay cuatro bytes en 'int32_t'. –

0

Suponiendo que tiene una cantidad limitada de valores en su parámetro de fondo (o incluso mejor, solo en), tal vez debería intentar asignar un vector estático y simplemente usar memcpy.

const int32_t sBackground = 1234; 
static vector <int32_t> sInitalizedBuffer(n, sBackground); 

    const X::int_vec_t& X::process(const SOME_DATA& data) 
    { 
     // the following one string takes 30% of total time of #process function 
     std::memcpy((void*) data[0], (void*) sInitalizedBuffer[0], n * sizeof(sBackground)); 

     // some processing 
     // ... 

     return buffer; 
    } 
-2

Puede ser un poco no portátil, pero podría usar una copia de memoria superpuesta. Complete los primeros cuatro bytes con el patrón que desea y use memcpy().

int32* p = (int32*) malloc(size); 
*p = 1234; 
memcpy(p + 4, p, size - 4); 

no creo que se puede conseguir mucho más rápido std

+0

No es compatible. ver http://stackoverflow.com/questions/387654/why-is-there-no-z80-like-ldir-functionality-in-c-c-rtl – EvilTeach

+0

Puede que no sea "compatible" pero funciona en vs2008. Puedo proporcionar la fuente si es necesario. Tampoco puedo encontrar a qué se refiere en la página vinculada. – Jay

+1

La superposición de memcpy's es un error conocido, y recomendarlos es un mal consejo. memmove es la llamada correcta para usar en regiones superpuestas. "No es compatible, pero funciona" es para grabar en lápidas. –

0

Acabo de prueba :: llenar con g ++ con optimizaciones completos (SSE etc ..habilitado):

#include <algorithm> 
#include <inttypes.h> 

int32_t a[5000000]; 

int main(int argc,char *argv[]) 
{ 
    std::fill(a,a+5000000,0xAABBCC00); 
    return a[3]; 
} 

y el bucle interior parecía:

L2: 
    movdqa %xmm0, -16(%eax) 
    addl $16, %eax 
    cmpl %edx, %eax 
    jne L2 

Parece que 0xAABBCC00 x 4 se cargó en xmm0 y se está moviendo de 16 bytes a la vez.

+0

Tengo curiosidad, ¿por qué este código usa comparaciones y saltos condicionales aún más rápido que 'REPNZ STOS' o similar? – Philipp

+0

@Philipp: copia 16 bytes a la vez. Las comparaciones y los saltos condicionales no son necesariamente costosos. Depende mucho del contexto, qué otras instrucciones se están ejecutando. – jalf

0

vs2013 y vs2015 pueden optimizar un for-loop simple a una instrucción rep stos. Es la forma más rápida de llenar un buffer. Se puede especificar el std::fill para su tipo de la siguiente manera:

namespace std { 
    inline void fill(vector<int>::iterator first, vector<int>::iterator last, int value){ 
     for (size_t i = 0; i < last - first; i++) 
      first[i] = value; 
    } 
} 

cierto. Para que el compilador realice la optimización, el subíndice debe acceder al almacenamiento intermedio.

No funcionará en el gcc y clang. Ambos compilarán el código en un bucle de salto condicional. Funciona tan lento como el original std::fill. Y aunque el wchar_t es de 32 bits, el wmemset no tiene un implemento de ensamblaje que le guste memset. Por lo tanto, debe escribir el código de ensamblaje para realizar la optimización.

Cuestiones relacionadas