a[i]
El lenguaje C puede usar tanto el método de subíndice como*(a+i)
el método de puntero al acceder a la matriz , que es completamente equivalente en teoría. Sin embargo, cuando el compilador optimiza el bucle, es posible que el índice del método del puntero no se analice a fondo, por lo que lleva más tiempo que el índice de la matriz.
La indexación de matrices lleva tiempo
#include <stdio.h>
#include <stdlib.h>
#include <time.h>
unsigned long get_start_ms() {
struct timespec ts;
clock_gettime(CLOCK_MONOTONIC, &ts);
return (ts.tv_sec * 1000 + ts.tv_nsec / 1000000);
}
int main() {
unsigned long t = get_start_ms();
uint64_t* mem = malloc(1024*1024*128*sizeof(uint64_t));
register uint64_t sum = 0;
for(int i = 0; i < 1024*1024*128; i++) sum += mem[i];
printf("[%lums]0x%016llx\n", get_start_ms()-t, sum);
}
Los resultados después de compilar y ejecutar por separado son los siguientes:
se puede observar que a medida que aumenta el nivel de optimización, el tiempo empleado a su vez disminuye.
Indexación de punteros que requiere mucho tiempo
#include <stdio.h>
#include <stdlib.h>
#include <time.h>
unsigned long get_start_ms() {
struct timespec ts;
clock_gettime(CLOCK_MONOTONIC, &ts);
return (ts.tv_sec * 1000 + ts.tv_nsec / 1000000);
}
int main() {
unsigned long t = get_start_ms();
uint64_t* mem = malloc(1024*1024*128*sizeof(uint64_t));
uint64_t* end = mem + 1024*1024*128;
register uint64_t sum = 0;
while(mem<end) sum += *mem++;
printf("[%lums]0x%016llx\n", get_start_ms()-t, sum);
}
Los resultados después de compilar y ejecutar por separado son los siguientes:
del mismo modo, a medida que aumenta el nivel de optimización, el tiempo empleado disminuye a su vez. Pero hemos notado que -O1
el puntero y la matriz tardan aproximadamente el mismo tiempo, pero la matriz es más rápida que el puntero en las siguientes dos etapas 10ms
, que no es un número pequeño.
Análisis de optimización del compilador
Por las razones de este fenómeno, necesitamos analizar desde el código ensamblador.
1. Nivel de optimización-O1
En este nivel, la diferencia entre los dos no es obvia.
- índice de matriz
- índice de puntero
2. Nivel de optimización - O2
En este nivel, se introduce la vectorización, pero la indexación de matrices está más vectorizada y tiene menos instrucciones.
- índice de matriz
- índice de puntero
cmpq $4, %r9
jae LBB1_2
## %bb.1:
## implicit-def: $rbx
jmp LBB1_11
LBB1_2:
movq %r9, %r8
andq $-4, %r8
leaq -4(%r8), %rdi
movq %rdi, %rsi
shrq $2, %rsi
incq %rsi
movl %esi, %ebx
andl $3, %ebx
cmpq $12, %rdi
jae LBB1_4
## %bb.3:
pxor %xmm0, %xmm0
xorl %edi, %edi
pxor %xmm1, %xmm1
testq %rbx, %rbx
jne LBB1_7
jmp LBB1_9
LBB1_4:
movl $1, %edi
subq %rsi, %rdi
leaq -1(%rbx,%rdi), %rsi
pxor %xmm0, %xmm0
xorl %edi, %edi
pxor %xmm1, %xmm1
.p2align 4, 0x90
LBB1_5: ## =>This Inner Loop Header: Depth=1
movdqu 8(%rax,%rdi,8), %xmm2
paddq %xmm0, %xmm2
movdqu 24(%rax,%rdi,8), %xmm0
paddq %xmm1, %xmm0
movdqu 40(%rax,%rdi,8), %xmm1
movdqu 56(%rax,%rdi,8), %xmm3
movdqu 72(%rax,%rdi,8), %xmm4
paddq %xmm1, %xmm4
paddq %xmm2, %xmm4
movdqu 88(%rax,%rdi,8), %xmm2
paddq %xmm3, %xmm2
paddq %xmm0, %xmm2
movdqu 104(%rax,%rdi,8), %xmm0
paddq %xmm4, %xmm0
movdqu 120(%rax,%rdi,8), %xmm1
paddq %xmm2, %xmm1
addq $16, %rdi
addq $4, %rsi
jne LBB1_5
## %bb.6:
testq %rbx, %rbx
je LBB1_9
LBB1_7:
leaq 24(%rax,%rdi,8), %rax
negq %rbx
.p2align 4, 0x90
LBB1_8: ## =>This Inner Loop Header: Depth=1
movdqu -16(%rax), %xmm2
paddq %xmm2, %xmm0
movdqu (%rax), %xmm2
paddq %xmm2, %xmm1
addq $32, %rax
incq %rbx
jne LBB1_8
LBB1_9:
paddq %xmm1, %xmm0
pshufd $78, %xmm0, %xmm1 ## xmm1 = xmm0[2,3,0,1]
paddq %xmm0, %xmm1
movq %xmm1, %rbx
cmpq %r8, %r9
je LBB1_12
## %bb.10:
leaq (%rdx,%r8,8), %rdx
.p2align 4, 0x90
LBB1_11: ## =>This Inner Loop Header: Depth=1
addq (%rdx), %rbx
addq $8, %rdx
cmpq %rcx, %rdx
jb LBB1_11
LBB1_12:
3. Nivel de optimización -O2 -march=native
Introducido en este nivel avx512
, el grado de vectorización de la indexación de matrices es aún mayor y el número de instrucciones es menor.
- índice de matriz
- índice de puntero
cmpq $16, %r9
jae LBB1_2
## %bb.1:
## implicit-def: $rbx
jmp LBB1_11
LBB1_2:
movq %r9, %r8
andq $-16, %r8
leaq -16(%r8), %rdi
movq %rdi, %rsi
shrq $4, %rsi
addq $1, %rsi
movl %esi, %ebx
andl $3, %ebx
cmpq $48, %rdi
jae LBB1_4
## %bb.3:
vpxor %xmm0, %xmm0, %xmm0
xorl %edi, %edi
vpxor %xmm1, %xmm1, %xmm1
vpxor %xmm2, %xmm2, %xmm2
vpxor %xmm3, %xmm3, %xmm3
testq %rbx, %rbx
jne LBB1_7
jmp LBB1_9
LBB1_4:
movl $1, %edi
subq %rsi, %rdi
leaq (%rbx,%rdi), %rsi
addq $-1, %rsi
vpxor %xmm0, %xmm0, %xmm0
xorl %edi, %edi
vpxor %xmm1, %xmm1, %xmm1
vpxor %xmm2, %xmm2, %xmm2
vpxor %xmm3, %xmm3, %xmm3
.p2align 4, 0x90
LBB1_5: ## =>This Inner Loop Header: Depth=1
vpaddq 8(%rax,%rdi,8), %ymm0, %ymm0
vpaddq 40(%rax,%rdi,8), %ymm1, %ymm1
vpaddq 72(%rax,%rdi,8), %ymm2, %ymm2
vpaddq 104(%rax,%rdi,8), %ymm3, %ymm3
vpaddq 136(%rax,%rdi,8), %ymm0, %ymm0
vpaddq 168(%rax,%rdi,8), %ymm1, %ymm1
vpaddq 200(%rax,%rdi,8), %ymm2, %ymm2
vpaddq 232(%rax,%rdi,8), %ymm3, %ymm3
vpaddq 264(%rax,%rdi,8), %ymm0, %ymm0
vpaddq 296(%rax,%rdi,8), %ymm1, %ymm1
vpaddq 328(%rax,%rdi,8), %ymm2, %ymm2
vpaddq 360(%rax,%rdi,8), %ymm3, %ymm3
vpaddq 392(%rax,%rdi,8), %ymm0, %ymm0
vpaddq 424(%rax,%rdi,8), %ymm1, %ymm1
vpaddq 456(%rax,%rdi,8), %ymm2, %ymm2
vpaddq 488(%rax,%rdi,8), %ymm3, %ymm3
addq $64, %rdi
addq $4, %rsi
jne LBB1_5
## %bb.6:
testq %rbx, %rbx
je LBB1_9
LBB1_7:
leaq (%rax,%rdi,8), %rax
addq $104, %rax
negq %rbx
.p2align 4, 0x90
LBB1_8: ## =>This Inner Loop Header: Depth=1
vpaddq -96(%rax), %ymm0, %ymm0
vpaddq -64(%rax), %ymm1, %ymm1
vpaddq -32(%rax), %ymm2, %ymm2
vpaddq (%rax), %ymm3, %ymm3
subq $-128, %rax
incq %rbx
jne LBB1_8
LBB1_9:
vpaddq %ymm3, %ymm1, %ymm1
vpaddq %ymm2, %ymm0, %ymm0
vpaddq %ymm1, %ymm0, %ymm0
vextracti128 $1, %ymm0, %xmm1
vpaddq %ymm1, %ymm0, %ymm0
vpshufd $78, %xmm0, %xmm1 ## xmm1 = xmm0[2,3,0,1]
vpaddq %xmm1, %xmm0, %xmm0
vmovq %xmm0, %rbx
cmpq %r8, %r9
je LBB1_12
## %bb.10:
leaq (%rdx,%r8,8), %rdx
.p2align 4, 0x90
LBB1_11: ## =>This Inner Loop Header: Depth=1
addq (%rdx), %rbx
addq $8, %rdx
cmpq %rcx, %rdx
jb LBB1_11
LBB1_12:
Se puede ver que el compilador tiene un conjunto de métodos de optimización maduros para la vectorización del índice de la matriz en el bucle for Si cambia al índice del puntero precipitadamente, disminuirá la velocidad, por lo que debe tener cuidado.