chip - cpu cache levels
Estimación del tamaño de la caché en su sistema (2)
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!
medir los tiempos de transferencia sin procesar y trazar un gráfico
-
x
ejex
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:
establecer clase de prioridad de proceso en
realtime
establecer la afinidad del proceso a una sola CPU
así que mides solo una CPU en multi-core
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, uselengthMod - 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 lalengthMod-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 delengthMod-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.