Pregunta

Resumen:

memcpy parece incapaz de transferir más de 2 GB / seg en mi sistema en una aplicación real o prueba. ¿Qué puedo hacer para conseguir más rápidamente copias de memoria a la memoria?

detalles:

Como parte de una aplicación de captura de datos (usando algún hardware especializado), I necesidad de copiar sobre 3 GB / seg de buffers temporales en la memoria principal. Para los datos de adquirir, proporciono el controlador de hardware con una serie de amortiguadores (2 MB cada una). Los datos de DMA de hardware para cada búfer y, a continuación, notifica mi programa cuando cada búfer está lleno. Mi programa se vacía la memoria intermedia (memcpy a otro, más grande de bloques de RAM), y reposts el tampón de procesado a la tarjeta para ser llenado de nuevo. Estoy teniendo problemas con el establecimiento de memoria mover los datos lo suficientemente rápido. Parece que la copia de memoria a la memoria debe ser lo suficientemente rápido como para apoyar 3GB / seg en el hardware que estoy ejecutando. Lavalys Everest me da un resultado de la copia de referencia de memoria 9337MB / seg, pero no puede llegar a ninguna parte cerca de esas velocidades con establecimiento de memoria, incluso en un programa simple prueba.

Me han aislado el problema de rendimiento mediante la adición / eliminación de la llamada establecimiento de memoria en el código de procesamiento de búfer. Sin el establecimiento de memoria, puedo correr RATE de datos completa sobre 3GB / seg. Con el establecimiento de memoria activada, estoy limitado a alrededor de 550 MB / seg (usando el compilador actual).

Con el fin de establecimiento de memoria de referencia en mi sistema, he escrito un programa de prueba independiente que sólo llamadas memcpy en algunos bloques de datos. (He publicado el código de abajo) me he encontrado esto, tanto en el compilador / IDE que estoy usando (CVI de National Instruments), así como Visual Studio 2010. Si bien actualmente no estoy utilizando Visual Studio, estoy dispuesto para hacer el cambio si se va a producir el rendimiento necesario. Sin embargo, antes de seguir ciegamente, quería asegurarse de que resolvería mis problemas de rendimiento establecimiento de memoria.

Visual C ++ 2010: 1900 MB / s

NI CVI 2009: 550 MB / seg

Si bien no me sorprende que CVI es significativamente más lento que Visual Studio, me sorprende que el rendimiento establecimiento de memoria es tan bajo. Aunque no estoy seguro de si esto es directamente comparable, esto es mucho menor que el ancho de banda de referencia Everest. Si bien no necesito bastante ese nivel de rendimiento, es necesario un mínimo de 3 GB / seg. Sin duda, la implementación de la biblioteca estándar no puede ser esto mucho peor que lo está utilizando Everest!

Lo que, en todo caso, puedo hacer para hacer más rápido establecimiento de memoria en esta situación?


detalles de hardware: núcleo AMD Magny Cours- 4x octal 128 GB DDR3 Windows Server 2003 Enterprise x64

Programa de prueba:

#include <windows.h>
#include <stdio.h>

const size_t NUM_ELEMENTS = 2*1024 * 1024;
const size_t ITERATIONS = 10000;

int main (int argc, char *argv[])
{
    LARGE_INTEGER start, stop, frequency;

    QueryPerformanceFrequency(&frequency);

    unsigned short * src = (unsigned short *) malloc(sizeof(unsigned short) * NUM_ELEMENTS);
    unsigned short * dest = (unsigned short *) malloc(sizeof(unsigned short) * NUM_ELEMENTS);

    for(int ctr = 0; ctr < NUM_ELEMENTS; ctr++)
    {
        src[ctr] = rand();
    }

    QueryPerformanceCounter(&start);

    for(int iter = 0; iter < ITERATIONS; iter++)
        memcpy(dest, src, NUM_ELEMENTS * sizeof(unsigned short));

    QueryPerformanceCounter(&stop);

    __int64 duration = stop.QuadPart - start.QuadPart;

    double duration_d = (double)duration / (double) frequency.QuadPart;

    double bytes_sec = (ITERATIONS * (NUM_ELEMENTS/1024/1024) * sizeof(unsigned short)) / duration_d;

    printf("Duration: %.5lfs for %d iterations, %.3lfMB/sec\n", duration_d, ITERATIONS, bytes_sec);

    free(src);
    free(dest);

    getchar();

    return 0;
}

EDIT: Si usted tiene un extra de cinco minutos, y desea contribuir, puede ejecutar el código anterior en su máquina y después de su tiempo como un comentario

?
¿Fue útil?

Solución

He encontrado una manera de aumentar la velocidad en esta situación. Escribí una versión multi-hilo de establecimiento de memoria, la división del área a ser copiado entre los hilos. Aquí están algunos números prestaciones de escalado para un tamaño de bloque conjunto, utilizando el mismo código de tiempo que se encuentra arriba. No tenía ni idea de que el rendimiento, especialmente para este pequeño tamaño de bloque, escalaría a esto muchas discusiones. Sospecho que esto tiene algo que ver con el gran número de controladores de memoria (16) en esta máquina.

Performance (10000x 4MB block memcpy):

 1 thread :  1826 MB/sec
 2 threads:  3118 MB/sec
 3 threads:  4121 MB/sec
 4 threads: 10020 MB/sec
 5 threads: 12848 MB/sec
 6 threads: 14340 MB/sec
 8 threads: 17892 MB/sec
10 threads: 21781 MB/sec
12 threads: 25721 MB/sec
14 threads: 25318 MB/sec
16 threads: 19965 MB/sec
24 threads: 13158 MB/sec
32 threads: 12497 MB/sec

No entiendo el gran salto de rendimiento entre 3 y 4 hilos. Lo que causaría un salto como este?

He incluido el código de establecimiento de memoria que escribí a continuación para otros que pueden encontrarse con este mismo problema. Tenga en cuenta que no hay ninguna comprobación de errores en este código- esto puede ser necesario añadir para su aplicación.

#define NUM_CPY_THREADS 4

HANDLE hCopyThreads[NUM_CPY_THREADS] = {0};
HANDLE hCopyStartSemaphores[NUM_CPY_THREADS] = {0};
HANDLE hCopyStopSemaphores[NUM_CPY_THREADS] = {0};
typedef struct
{
    int ct;
    void * src, * dest;
    size_t size;
} mt_cpy_t;

mt_cpy_t mtParamters[NUM_CPY_THREADS] = {0};

DWORD WINAPI thread_copy_proc(LPVOID param)
{
    mt_cpy_t * p = (mt_cpy_t * ) param;

    while(1)
    {
        WaitForSingleObject(hCopyStartSemaphores[p->ct], INFINITE);
        memcpy(p->dest, p->src, p->size);
        ReleaseSemaphore(hCopyStopSemaphores[p->ct], 1, NULL);
    }

    return 0;
}

int startCopyThreads()
{
    for(int ctr = 0; ctr < NUM_CPY_THREADS; ctr++)
    {
        hCopyStartSemaphores[ctr] = CreateSemaphore(NULL, 0, 1, NULL);
        hCopyStopSemaphores[ctr] = CreateSemaphore(NULL, 0, 1, NULL);
        mtParamters[ctr].ct = ctr;
        hCopyThreads[ctr] = CreateThread(0, 0, thread_copy_proc, &mtParamters[ctr], 0, NULL); 
    }

    return 0;
}

void * mt_memcpy(void * dest, void * src, size_t bytes)
{
    //set up parameters
    for(int ctr = 0; ctr < NUM_CPY_THREADS; ctr++)
    {
        mtParamters[ctr].dest = (char *) dest + ctr * bytes / NUM_CPY_THREADS;
        mtParamters[ctr].src = (char *) src + ctr * bytes / NUM_CPY_THREADS;
        mtParamters[ctr].size = (ctr + 1) * bytes / NUM_CPY_THREADS - ctr * bytes / NUM_CPY_THREADS;
    }

    //release semaphores to start computation
    for(int ctr = 0; ctr < NUM_CPY_THREADS; ctr++)
        ReleaseSemaphore(hCopyStartSemaphores[ctr], 1, NULL);

    //wait for all threads to finish
    WaitForMultipleObjects(NUM_CPY_THREADS, hCopyStopSemaphores, TRUE, INFINITE);

    return dest;
}

int stopCopyThreads()
{
    for(int ctr = 0; ctr < NUM_CPY_THREADS; ctr++)
    {
        TerminateThread(hCopyThreads[ctr], 0);
        CloseHandle(hCopyStartSemaphores[ctr]);
        CloseHandle(hCopyStopSemaphores[ctr]);
    }
    return 0;
}

Otros consejos

No estoy seguro de si se hace en tiempo de ejecución o si tiene que hacerlo en tiempo de compilación, pero se debe tener SSE o extensiones similares habilitadas como el vector unitario a menudo puede escribir 128 bits a la memoria en comparación con los 64 bits para la CPU.

Trate esta implementación .

Sí, y asegurarse de que ambos el origen y el destino está alineado a 128 bits. Si el origen y el destino no están alineados entre sí respectiva su memcpy () tendrá que hacer un poco de magia grave. :)

Usted tiene algunas barreras para obtener el rendimiento de la memoria requerida:

  1. Ancho de Banda - hay un límite a lo rápido que se puede mover datos desde la memoria de la CPU y de regreso. De acuerdo con este artículo de Wikipedia , 266 MHz RAM DDR3 tiene un límite superior de alrededor de 17 GB / s. Ahora, con un establecimiento de memoria que necesita para reducir a la mitad este de conseguir su máxima velocidad de transferencia ya que los datos se lee y se escribe entonces. A partir de sus resultados de referencia, parece que no se está ejecutando la RAM más rápida posible de su sistema. Si se lo puede permitir, actualizar la placa madre / RAM (y no va a ser barato, Overclockers en el Reino Unido actualmente tienen 3x4GB PC16000 en £ 400)

  2. El sistema operativo - Windows es un sistema operativo multitarea preventiva por lo que cada cierto tiempo el proceso se suspendió para permitir que otros procesos para echar un vistazo y hacer cosas. Esto clobber sus cachés y detener su traslado. En el peor de los casos todo el proceso podría ser almacenado en caché en el disco!

  3. La CPU - los datos que se trasladaron tiene un largo camino por recorrer: RAM -> L2 Cache -> L1 Cache -> CPU -> L1 -> L2 -> RAM. Puede haber incluso una caché L3. Si desea involucrar a la CPU que realmente quiere ser carga L2, mientras que la copia de L1. Por desgracia, los CPU modernos pueden ejecutar a través de un bloque de caché L1 más rápido que el tiempo necesario para cargar la L1. La CPU tiene un controlador de memoria que ayuda mucho en estos casos en los que los datos que fluyen en la CPU de forma secuencial, pero usted todavía va a tener problemas.

Por supuesto, la manera más rápida de hacer algo es no hacerlo. Se pueden escribir los datos capturados en cualquier lugar de la memoria RAM o es el tampón utilizado en un lugar fijo. Si usted puede escribir en cualquier lugar, a continuación, no es necesario el establecimiento de memoria en absoluto. Si es fijo, ¿podría procesar los datos en su lugar y utilizar un sistema de doble tipo de tampón? Es decir, iniciar la captura de datos y cuando está medio lleno, iniciar el procesamiento de la primera mitad de los datos. Cuando el búfer está lleno, comenzar a escribir los datos capturados a la salida y procesar la segunda mitad. Esto requiere que el algoritmo puede procesar los datos más rápido que la tarjeta de captura produce. También se supone que los datos se desecha después de procesar. Efectivamente, esto es un establecimiento de memoria con una transformación como parte del proceso de copia, por lo que tiene:

load -> transform -> save
\--/                 \--/
 capture card        RAM
   buffer

en lugar de:

load -> save -> load -> transform -> save
\-----------/
memcpy from
capture card
buffer to RAM

o conseguir más rápido RAM!

EDIT: Otra opción es procesar los datos entre la fuente de datos y el PC - se puede poner un DSP / FPGA allí en absoluto? hardware a la medida siempre será más rápido que una CPU de propósito general.

Otro pensamiento: Ha pasado un tiempo desde que he hecho ninguna materia gráfica de altas prestaciones, pero ¿podría DMA los datos en la tarjeta gráfica y luego DMA hacia fuera otra vez? Incluso se puede tomar ventaja de CUDA para hacer parte del proceso. Esto llevaría a la CPU fuera del circuito de transferencia de la memoria por completo.

Una cosa a tener en cuenta es que su proceso (y por tanto el rendimiento de memcpy()) se ve afectado por la programación del sistema operativo de tareas - es difícil decir cuánto de un factor de esto está en sus tiempos, bu teta es difícil controlar. La operación de DMA dispositivo no está sujeta a esta, ya que no se está ejecutando en la CPU una vez que se inició. Debido a que su aplicación es una aplicación real en tiempo real, sin embargo, es posible que desee experimentar con la configuración de prioridad de proceso / hilo de Windows si no lo ha hecho. Hemos de tener en cuenta que usted tiene que tener cuidado con esto, ya que puede tener un impacto muy negativo en otros procesos (y la experiencia del usuario en la máquina).

Otra cosa a tener en cuenta es que la virtualización de la memoria del sistema operativo podría tener un impacto aquí - si las páginas de memoria que está copiando a realidad no son respaldados por las páginas de memoria RAM física, la operación memcpy() en fallo en el sistema operativo para obtener que respaldo físico en su lugar. Sus páginas DMA son propensos a ser bloqueado en la memoria física (ya que tienen que ser para la operación de DMA), por lo que la memoria fuente a memcpy() probable es que no es un problema en este sentido. Le recomendamos que utilice la API VirtualAlloc() Win32 para asegurarse de que su memoria de destino para el memcpy() está comprometido (creo VirtualAlloc() es la API adecuado para esto, pero podría ser mejor que yo estoy olvidando - que ha sido un tiempo desde que' he tenido la necesidad de hacer algo como esto).

Por último, ver si se puede usar la técnica explicada por Skizz para evitar la memcpy() por completo -. que es la mejor opción si lo permiten los recursos

En primer lugar, es necesario comprobar que la memoria está alineado el 16 de límite de byte, de lo contrario se obtiene sanciones. Esto es lo más importante.

Si usted no necesita una solución compatible con el estándar, se puede comprobar si las cosas mejoran mediante el uso de alguna extensión específica del compilador como memcpy64 (cheque con su doc ??compilador si hay algo disponible). El hecho es que memcpymust ser capaz de hacer frente a la copia de un solo byte, pero moviéndose 4 u 8 bytes a la vez es mucho más rápido si usted no tiene esta restricción.

Una vez más, es una opción para que usted pueda escribir en línea de montaje código?

Tal vez se puede explicar un poco más sobre cómo se está procesando el área de memoria más grande?

¿Sería posible dentro de su aplicación a pasar simplemente la propiedad de la memoria intermedia, en lugar de copiarlo? Esto eliminaría el problema por completo.

O es usted que usa memcpy para algo más que copiar? Tal vez usted está utilizando el área más grande de la memoria para construir un flujo secuencial de datos de lo que ha capturado? Sobre todo si se está procesando un carácter a la vez, puede ser capaz de cumplir la mitad. Por ejemplo, puede ser posible adaptar el código de procesamiento para acomodar un flujo representado como ‘un conjunto de buffers’, en lugar de ‘un área de memoria continua’.

Se puede escribir una mejor aplicación de memcpy utilizando registros SSE2. La versión en VC2010 hace esto ya. Así que la pregunta es más, si usted es la entrega que la memoria alineados.

Tal vez usted puede hacer mejor que la versión de VC 2010, pero se necesita un poco de comprensión, de cómo hacerlo.

PS:. Usted puede pasar el búfer en el programa de modo de usuario en una llamada invertida, para evitar la copia por completo

Una fuente le recomiendo que lea es la función fast_memcpy de MPlayer. Ten en cuenta también los patrones de utilización esperados, y nota que las CPU modernas tienen instrucciones especiales de tiendas que le permiten informar a la CPU si está o no tendrá que volver a leer los datos le está escribiendo. Siguiendo las instrucciones que indican que no se va a leer los datos de vuelta (y por lo tanto no necesita ser almacenado en caché) puede ser una gran victoria para las grandes operaciones memcpy.

Licenciado bajo: CC-BY-SA con atribución
No afiliado a StackOverflow
scroll top