La influencia y el lento análisis del lenguaje C mediante el índice de matriz y el índice de puntero en la optimización del compilador en el ciclo

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:
Arr
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:
ptr
del mismo modo, a medida que aumenta el nivel de optimización, el tiempo empleado disminuye a su vez. Pero hemos notado que -O1el 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
    arro1
  • índice de puntero
    ptro1

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
    arro2
  • í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
    arro2n
  • í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.

Supongo que te gusta

Origin blog.csdn.net/u011570312/article/details/121265729
Recomendado
Clasificación