chip cache c performance caching cpu-cache

chip - cpu cache levels



Estimación del tamaño de la caché en su sistema (2)

  1. necesitas acceso directo a la memoria

    No estoy hablando de transferencia de DMA por esto. La memoria debe ser accedida por la CPU por supuesto (de lo contrario no está midiendo CACHE ) pero tan directamente como puede ser ... entonces las mediciones probablemente no serán muy precisas en Windows / Linux porque los servicios y otros procesos pueden interferir con las cachés durante el tiempo de ejecución . Mida muchas veces y promedie para obtener mejores resultados (o use el tiempo más rápido o fíltrelo). Para una mejor precisión, use DOS y ASM, por ejemplo

    rep + movsb,movsw,movsd rep + stosb,stosw,stosd

    ¡así que mides la transferencia de memoria y no otra cosa como en tu código!

  2. medir los tiempos de transferencia sin procesar y trazar un gráfico

    • x eje x es el tamaño del bloque de transferencia
    • y eje es la velocidad de transferencia

    las zonas con la misma velocidad de transferencia son consistentes con la capa CACHE apropiada

[Edit1] no pudo encontrar mi antiguo código fuente para esto, así que busqué algo ahora mismo en C ++ para Windows :

Medida de tiempo:

//--------------------------------------------------------------------------- double performance_Tms=-1.0, // perioda citaca [ms] performance_tms= 0.0; // zmerany cas [ms] //--------------------------------------------------------------------------- void tbeg() { LARGE_INTEGER i; if (performance_Tms<=0.0) { QueryPerformanceFrequency(&i); performance_Tms=1000.0/double(i.QuadPart); } QueryPerformanceCounter(&i); performance_tms=double(i.QuadPart); } //--------------------------------------------------------------------------- double tend() { LARGE_INTEGER i; QueryPerformanceCounter(&i); performance_tms=double(i.QuadPart)-performance_tms; performance_tms*=performance_Tms; return performance_tms; } //---------------------------------------------------------------------------

Benchmark (aplicación de 32 bits):

//--------------------------------------------------------------------------- DWORD sizes[]= // used transfer block sizes { 1<<10, 2<<10, 3<<10, 4<<10, 5<<10, 6<<10, 7<<10, 8<<10, 9<<10, 10<<10, 11<<10, 12<<10, 13<<10, 14<<10, 15<<10, 16<<10, 17<<10, 18<<10, 19<<10, 20<<10, 21<<10, 22<<10, 23<<10, 24<<10, 25<<10, 26<<10, 27<<10, 28<<10, 29<<10, 30<<10, 31<<10, 32<<10, 48<<10, 64<<10, 80<<10, 96<<10, 112<<10,128<<10,192<<10,256<<10,320<<10,384<<10,448<<10,512<<10, 1<<20, 2<<20, 3<<20, 4<<20, 5<<20, 6<<20, 7<<20, 8<<20, 9<<20, 10<<20, 11<<20, 12<<20, 13<<20, 14<<20, 15<<20, 16<<20, 17<<20, 18<<20, 19<<20, 20<<20, 21<<20, 22<<20, 23<<20, 24<<20, 25<<20, 26<<20, 27<<20, 28<<20, 29<<20, 30<<20, 31<<20, 32<<20, }; const int N=sizeof(sizes)>>2; // number of used sizes double pmovsd[N]; // measured transfer rate rep MOVSD [MB/sec] double pstosd[N]; // measured transfer rate rep STOSD [MB/sec] //--------------------------------------------------------------------------- void measure() { int i; BYTE *dat; // pointer to used memory DWORD adr,siz,num; // local variables for asm double t,t0; HANDLE hnd; // process handle // enable priority change (huge difference) #define measure_priority // enable critical sections (no difference) // #define measure_lock for (i=0;i<N;i++) pmovsd[i]=0.0; for (i=0;i<N;i++) pstosd[i]=0.0; dat=new BYTE[sizes[N-1]+4]; // last DWORD +4 Bytes (should be 3 but i like 4 more) if (dat==NULL) return; #ifdef measure_priority hnd=GetCurrentProcess(); if (hnd!=NULL) { SetPriorityClass(hnd,REALTIME_PRIORITY_CLASS); CloseHandle(hnd); } Sleep(200); // wait to change take effect #endif #ifdef measure_lock CRITICAL_SECTION lock; // lock handle InitializeCriticalSectionAndSpinCount(&lock,0x00000400); EnterCriticalSection(&lock); #endif adr=(DWORD)(dat); for (i=0;i<N;i++) { siz=sizes[i]; // siz = actual block size num=(8<<20)/siz; // compute n (times to repeat the measurement) if (num<4) num=4; siz>>=2; // size / 4 because of 32bit transfer // measure overhead tbeg(); // start time meassurement asm { push esi push edi push ecx push ebx push eax mov ebx,num mov al,0 loop0: mov esi,adr mov edi,adr mov ecx,siz // rep movsd // es,ds already set by C++ // rep stosd // es already set by C++ dec ebx jnz loop0 pop eax pop ebx pop ecx pop edi pop esi } t0=tend(); // stop time meassurement // measurement 1 tbeg(); // start time meassurement asm { push esi push edi push ecx push ebx push eax mov ebx,num mov al,0 loop1: mov esi,adr mov edi,adr mov ecx,siz rep movsd // es,ds already set by C++ // rep stosd // es already set by C++ dec ebx jnz loop1 pop eax pop ebx pop ecx pop edi pop esi } t=tend(); // stop time meassurement t-=t0; if (t<1e-6) t=1e-6; // remove overhead and avoid division by zero t=double(siz<<2)*double(num)/t; // Byte/ms pmovsd[i]=t/(1.024*1024.0); // MByte/s // measurement 2 tbeg(); // start time meassurement asm { push esi push edi push ecx push ebx push eax mov ebx,num mov al,0 loop2: mov esi,adr mov edi,adr mov ecx,siz // rep movsd // es,ds already set by C++ rep stosd // es already set by C++ dec ebx jnz loop2 pop eax pop ebx pop ecx pop edi pop esi } t=tend(); // stop time meassurement t-=t0; if (t<1e-6) t=1e-6; // remove overhead and avoid division by zero t=double(siz<<2)*double(num)/t; // Byte/ms pstosd[i]=t/(1.024*1024.0); // MByte/s } #ifdef measure_lock LeaveCriticalSection(&lock); DeleteCriticalSection(&lock); #endif #ifdef measure_priority hnd=GetCurrentProcess(); if (hnd!=NULL) { SetPriorityClass(hnd,NORMAL_PRIORITY_CLASS); CloseHandle(hnd); } #endif delete dat; } //---------------------------------------------------------------------------

Donde las matrices pmovsd[] y pstosd[] contienen las tasas de transferencia de 32bit medidas [MByte/sec] . Puede configurar el código con use / rem two define al inicio de la función de medida.

Salida gráfica:

Para maximizar la precisión, puede cambiar la clase de prioridad del proceso al máximo. Así que cree thread de medida con máxima prioridad (lo intento pero en realidad hace un lío) y le agrego una sección crítica para que el sistema operativo no interrumpa la prueba con la frecuencia (no hay diferencia visible con y sin subprocesos). Si desea utilizar transferencias de Byte , tenga en cuenta que solo utiliza registros de 16bit por lo que debe agregar repeticiones de bucles y direcciones.

PD.

Si prueba esto en el portátil, debe sobrecalentar la CPU para asegurarse de medir en la CPU / velocidad máxima. Entonces no hay Sleep s. Algunos bucles estúpidos antes de la medición lo harán, pero deberían ejecutarse durante al menos unos pocos segundos. También puede sincronizar esto mediante la medición de frecuencia de la CPU y el bucle mientras aumenta. Parar después de que se satura ...

La instrucción asm RDTSC es la mejor para esto (pero ten en cuenta que su significado ha cambiado ligeramente con las nuevas arquitecturas).

Si no está en Windows , cambie las funciones tbeg,tend con sus equivalentes OS

[edit2] más mejoras de precisión

Después de resolver finalmente el problema con VCL que afecta la precisión de la medición que descubrí gracias a esta pregunta y más al respecto aquí , para mejorar la precisión, puede hacer esto antes del benchmark:

  1. establecer clase de prioridad de proceso en realtime

  2. establecer la afinidad del proceso a una sola CPU

    así que mides solo una CPU en multi-core

  3. enjuague DATOS e Instrucción CACHEs

Por ejemplo:

// before mem benchmark DWORD process_affinity_mask=0; DWORD system_affinity_mask =0; HANDLE hnd=GetCurrentProcess(); if (hnd!=NULL) { // priority SetPriorityClass(hnd,REALTIME_PRIORITY_CLASS); // affinity GetProcessAffinityMask(hnd,&process_affinity_mask,&system_affinity_mask); process_affinity_mask=1; SetProcessAffinityMask(hnd,process_affinity_mask); GetProcessAffinityMask(hnd,&process_affinity_mask,&system_affinity_mask); } // flush CACHEs for (DWORD i=0;i<sizes[N-1];i+=7) { dat[i]+=i; dat[i]*=i; dat[i]&=i; } // after mem benchmark if (hnd!=NULL) { SetPriorityClass(hnd,NORMAL_PRIORITY_CLASS); SetProcessAffinityMask(hnd,system_affinity_mask); }

Entonces la medida más precisa se ve así:

Obtuve este programa desde este enlace ( https://gist.github.com/jiewmeng/3787223). He estado buscando en la web con la idea de obtener una mejor comprensión de los cachés de procesador (L1 y L2). Quiero ser capaz de escribir un programa que me permita adivinar el tamaño de la caché L1 y L2 en mi nueva computadora portátil. (Solo para fines de aprendizaje. Sé que podría verificar las especificaciones).

#include <stdio.h> #include <stdlib.h> #include <time.h> #define KB 1024 #define MB 1024 * 1024 int main() { unsigned int steps = 256 * 1024 * 1024; static int arr[4 * 1024 * 1024]; int lengthMod; unsigned int i; double timeTaken; clock_t start; int sizes[] = { 1 * KB, 4 * KB, 8 * KB, 16 * KB, 32 * KB, 64 * KB, 128 * KB, 256 * KB, 512 * KB, 1 * MB, 1.5 * MB, 2 * MB, 2.5 * MB, 3 * MB, 3.5 * MB, 4 * MB }; int results[sizeof(sizes)/sizeof(int)]; int s; /*for each size to test for ... */ for (s = 0; s < sizeof(sizes)/sizeof(int); s++) { lengthMod = sizes[s] - 1; start = clock(); for (i = 0; i < steps; i++) { arr[(i * 16) & lengthMod] *= 10; arr[(i * 16) & lengthMod] /= 10; } timeTaken = (double)(clock() - start)/CLOCKS_PER_SEC; printf("%d, %.8f /n", sizes[s] / 1024, timeTaken); } return 0; }

El resultado del programa en mi máquina es el siguiente. ¿Cómo interpreto los números? ¿Qué me dice este programa?

1, 1.07000000 4, 1.04000000 8, 1.06000000 16, 1.13000000 32, 1.14000000 64, 1.17000000 128, 1.20000000 256, 1.21000000 512, 1.19000000 1024, 1.23000000 1536, 1.23000000 2048, 1.46000000 2560, 1.21000000 3072, 1.45000000 3584, 1.47000000 4096, 1.94000000


Su variable lengthMod no hace lo que cree que hace. Desea que limite el tamaño de su conjunto de datos, pero tiene 2 problemas allí:

  • Hacer un Y a nivel de bit con una potencia de 2 enmascararía todos los bits, excepto el que está activado. Si, por ejemplo, lengthMod es 1k (0x400), entonces todos los índices inferiores a 0x400 (es decir, i = 1 a 63) simplemente se asignarían al índice 0, por lo que siempre se presionará la caché. Esa es probablemente la razón por la cual los resultados son tan rápidos. En su lugar, use lengthMod - 1 para crear una máscara correcta (0x400 -> 0x3ff, que enmascararía solo los bits superiores y dejaría intactos los inferiores).
  • Algunos de los valores de lengthMod no son una potencia de 2, por lo que hacer la lengthMod-1 no va a funcionar allí ya que algunos de los bits de la máscara aún serían ceros. Elimínelos de la lista o use una operación de módulo en lugar de lengthMod-1 conjunto. Ver también mi respuesta aquí para un caso similar.

Otro problema es que los saltos de 16B probablemente no sean suficientes para omitir una línea de caché, ya que la mayoría de las CPU comunes funcionan con caché de 64 bytes, por lo que solo se obtiene una falla por cada 4 repeticiones. Use (i*64) lugar.