Optimização do Desempenho:Técnicas Dependentes da Máquina
Arquitectura de ComputadoresLic. em Engenharia Informática
Luís Paulo Santos
Optimização: Dependência do Processador
2
Conteúdos
10 – Optimização do Desempenho
10.3 – Super-escalaridade e Execução fora-de-ordem
C2
10.4 – Loop Unrolling/Splitting C2
Resultados de Aprendizagem
R10.1 – Descrever, aplicar e avaliar técnicas de optimização de desempenho
C1
R10.2 – Analisar e justificar o impacto de múltiplas unidades funcionais no desempenho da máquina
C2
Optimização de combine()
3
• Função– Calcula a soma de todos os elementos de um vector
– Alcançou um CPE de 2.00• Ciclos por elemento
void combine4(vec_ptr v, int *dest){ int i; int length = vec_length(v); int *data = get_vec_start(v); int sum = 0; for (i = 0; i < length; i++) sum += data[i]; *dest = sum;}
Forma Geral de combine()
4
•Tipos de Dados– Usar diferentes declarações
para data_t– int– float
void abstract_combine4(vec_ptr v, data_t *dest){ int i; int length = vec_length(v); data_t *data = get_vec_start(v); data_t t = IDENT; for (i = 0; i < length; i++) t = t OP data[i]; *dest = t;}
•Operações– Diferentes definições paraOP e IDENT
– + / 0– * / 1
Optimizações Independentes da Máquina
5
Método Integer Floating Point + * + *
-g 42.06 41.86 41.44 160.00 -O2 31.25 33.25 31.25 143.00 Mover vec_length 20.66 21.25 21.15 135.00 acesso dados 6.00 9.00 8.00 117.00 Acum. em temp 2.00 4.00 3.00 5.00
• Anomalia Desempenho– Grande optimização quando o produto FP é acumulado em
temp:• A Memória usa formato de 64-bits, os registos usam 80 bits• A operação causou overflow com 64 bits, mas não com 80
Processadores Actuais
6ExecutionExecution
FunctionalUnits
Instruction ControlInstruction Control
Integer/Branch
FPAdd
FPMult/Div
Load Store
InstructionCache
DataCache
FetchControl
InstructionDecode
Address
Instrs.
Operations
PredictionOK?
DataData
Addr. Addr.
GeneralInteger
Operation Results
RetirementUnit
RegisterFile
RegisterUpdates
Unidades Funcionais: Desempenho (Pentium III)
7
• Múltiplas Instruções podem ser executadas em Paralelo– 1 load (leitura da memória)– 1 store (escrita na memória)– 2 operações inteiras (apenas uma pode ser um salto)– 1 Adição FP– 1 Multiplicação ou Divisão FP
• Algumas Instruções necessitam de > 1 Ciclo, mas podem ser em encadeadas (pipeline)– Instrução Latência Ciclos entre Operações– Load / Store 3 1– Multiplicação Inteira 4 1– Divisão Inteira 36 36– Multiplicação FP 5 2– Adição FP 3 1– Divisão FP 38 38
Controlo das Instruções
8
Instruction ControlInstruction Control
InstructionCache
FetchControl
InstructionDecode
Address
Instrs.
Operations
RetirementUnit
RegisterFile
• Lê Instruções da Memória– Baseada no PC + alvos previstos para os saltos– Hardware prevê dinamicamentes se os saltos são tomados ou não
• Traduz Instruções em Micro-Operações– Micro-Operação: passos elementares para a execução de uma instrução– Instrução típica requer 1–3 micro-operações
• Referências aos registos são substituídas por etiquetas– Etiqueta: Identificador abstracto liga o destino de uma operação aos operandos das
próximas
Tradução em micro-operações: Exemplo
9
• Combine4(): dados inteiros, multiplicação.L24: # Loop:imull (%eax,%edx,4),%ecx # t *= data[i]incl %edx # i++cmpl %esi,%edx # i:lengthjl .L24 # if < goto Loop
.L24:
imull (%eax,%edx,4),%ecx
incl %edxcmpl %esi,%edxjl .L24
load (%eax,%edx.0,4) t.1imull t.1, %ecx.0 %ecx.1incl %edx.0 %edx.1cmpl %esi, %edx.1 cc.1jl-taken cc.1
• Tradução de uma iteração:
Tradução em micro-operações: imull
10
– Dividir em 2 operações• load lê da memória e produz o valor t.1• imull opera apenas sobre registos
– Operandos• %eax não muda no ciclo. Lido do banco de registos durante o
decode• %ecx muda em cada iteração. Identificar diferentes versões com %ecx.0, %ecx.1, %ecx.2, …
– Register renaming– Valores passam directamente do produtor aos consumidores
imull (%eax,%edx,4),%ecx load (%eax,%edx.0,4) t.1imull t.1, %ecx.0 %ecx.1
Tradução em micro-operações: incl, cmpl
11
– %edx muda em cada iteração. Renomear %edx.0, %edx.1, %edx.2, …
incl %edx incl %edx.0 %edx.1
– Códigos de Condição são tratados como registos
cmpl %esi,%edx cmpl %esi, %edx.1 cc.1
Tradução em micro-operações: jl
12
– Instruction control determina destino do salto
– Prevê se é tomado ou não
– Instruções são lidas do destino previsto
– Execution unit verifica se previsão foi correcta– Se não, sinaliza instruction control
• Instruction control invalida operações e valores gerados por instruções lidas inadvertidamente
• Começa então a ler instruções a partir do endereço correcto
jl .L24 jl-taken cc.1
Visualização da execução das μoperações -imull
13
• Operações– Eixo Vertical determina tempo
• Uma operação não pode começar antes de os operandos estarem disponíveis
– Altura representa latência• Operandos
– Representados por arcos
cc.1
t.1
load
%ecx.1
incl
cmpl
jl
%edx.0
%edx.1
%ecx.0
imull
load (%eax,%edx,4) t.1imull t.1, %ecx.0 %ecx.1incl %edx.0 %edx.1cmpl %esi, %edx.1 cc.1jl-taken cc.1
Time
Visualização da execução das μoperações -addl
14
• Operações– Idêntico ao anterior, mas
adição tem latência=1
Time
cc.1
t.1
%ecx.i +1
incl
cmpl
jl
load
%edx.0
%edx.1
%ecx.0
addl%ecx.1
load
load (%eax,%edx,4) t.1iaddl t.1, %ecx.0 %ecx.1incl %edx.0 %edx.1cmpl %esi, %edx.1 cc.1jl-taken cc.1
Execução imull: Recursos Ilimitados
15
cc.1
cc.2%ecx.0
%edx.3t.1
imull
%ecx.1
incl
cmpl
jl
%edx.0
i=0
load
t.2
imull
%ecx.2
incl
cmpl
jl
%edx.1
i=1
load
cc.3
t.3
imull
%ecx.3
incl
cmpl
jl
%edx.2
i=2
load
Cycle
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
cc.1
cc.2
Iteration 3
Iteration 2
Iteration 1
cc.1
cc.2%ecx.0
%edx.3t.1
imull
%ecx.1
incl
cmpl
jl
%edx.0
i=0
load
t.1
imull
%ecx.1
incl
cmpl
jl
%edx.0
i=0
load
t.2
imull
%ecx.2
incl
cmpl
jl
%edx.1
i=1
load
t.2
imull
%ecx.2
incl
cmpl
jl
%edx.1
i=1
load
cc.3
t.3
imull
%ecx.3
incl
cmpl
jl
%edx.2
i=2
load
Cycle
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
Cycle
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
cc.1
cc.2
Iteration 3
Iteration 2
Iteration 1
• 3 iterações– Assume que as
operações podem começar logo que os operandos estão disponíveis
– Operações de diferentes iterações sobrepõem-se no tempo
• Desempenho– Limitado pela latência
da unidade de multiplicação
– CPE = 4.0
Execução addl: Recursos Ilimitados
16
• Desempenho– Começa uma nova iteração a cada ciclo– CPE = 1.0– Requer a execução de 4 operações inteiras em paralelo
%edx.0
t.1
%ecx.i +1
incl
cmpl
jl
addl%ecx.1
i=0
loadcc.1
%edx.0
t.1
%ecx.i +1
incl
cmpl
jl
addl%ecx.1
i=0
loadcc.1
%edx.1
t.2
%ecx.i +1
incl
cmpl
jl
addl%ecx.2
i=1
loadcc.2
%edx.1
t.2
%ecx.i +1
incl
cmpl
jl
addl%ecx.2
i=1
loadcc.2
%edx.2
t.3
%ecx.i +1
incl
cmpl
jl
addl%ecx.3
i=2
loadcc.3
%edx.2
t.3
%ecx.i +1
incl
cmpl
jl
addl%ecx.3
i=2
loadcc.3
%edx.3
t.4
%ecx.i +1
incl
cmpl
jl
addl%ecx.4
i=3
loadcc.4
%edx.3
t.4
%ecx.i +1
incl
cmpl
jl
addl%ecx.4
i=3
loadcc.4
%ecx.0
%edx.4
Cycle
1
2
3
4
5
6
7
Cycle
1
2
3
4
5
6
7
Iteration 1
Iteration 2
Iteration 3
Iteration 4
4 ops inteiras
Execução addl: Recursos Limitados
17
Iteration 4
Iteration 5
Iteration 6
Iteration 7
Iteration 8
%ecx.3
%edx.8
%edx.3
t.4%ecx.i +1
incl
cmpl
jladdl
%ecx.4
i=3
load
cc.4
%edx.3
t.4%ecx.i +1
incl
cmpl
jladdl
%ecx.4
i=3
load
cc.4
%edx.4
t.5%ecx.i +1
incl
cmpl
jladdl%ecx.5
i=4
load
cc.5
%edx.4
t.5%ecx.i +1
incl
cmpl
jladdl%ecx.5
i=4
load
cc.5
cc.6
%edx.7
t.8%ecx.i +1
incl
cmpl
jladdl
%ecx.8
i=7
load
cc.8
%edx.7
t.8%ecx.i +1
incl
cmpl
jladdl
%ecx.8
i=7
load
cc.8
%edx.5
t.6
incl
cmpl
jl
addl
%ecx.6
load
i=5
%edx.5
t.6
incl
cmpl
jl
addl
%ecx.6
load
i=5
6
7
8
9
10
11
12
Cycle
13
14
15
16
17
6
7
8
9
10
11
12
Cycle
13
14
15
16
17
18
cc.6
%edx.6
t.7
cmpl
jl
addl
%ecx.7
load
cc.7
i=6
incl
%edx.6
t.7
cmpl
jl
addl
%ecx.7
load
cc.7
i=6
incl
– Apenas 2 unidades funcionais para operações inteiras– Algumas operações esperam por recursos apesar de os
operandos estarem disponíveis– Prioridade das ops baseada na order do programa
• Desempenho– CPE = 2.0
Escalonamento de μoperações
• 1 μoperação pode ser escalonada para execução quando:
– Os seus operandos estão disponíveis
– Existem recursos disponíveis, isto é, uma unidade funcional livre que a possa executar
18
Optimização: Loop unrolling
19
void combine5(vec_ptr v, int *dest){ int length = vec_length(v); int limit = length-2; int *data = get_vec_start(v); int sum = 0; int i; /* Combine 3 elements at a time */ for (i = 0; i < limit; i+=3) { sum += data[i] + data[i+2] + data[i+1]; } /* Finish any remaining elements */ for (; i < length; i++) { sum += data[i]; } *dest = sum;}
As operações de controlo do ciclo representam um custo: • actualização da variável de controlo• teste da condição• salto
Desenrolar do ciclo:• combinar múltiplas operações numa única iteração• reduz custo do controlo do ciclo
CPE = 1.33
Esta optimização pode ser feita autonomamente pelo compilador
Visualização: Loop unrolling
20
– Loads encadeados (não têm dependências)
– Apenas 1 conjunto de operações de controlo
load (%eax,%edx.0,4) t.1aiaddl t.1a, %ecx.0c %ecx.1aload 4(%eax,%edx.0,4) t.1biaddl t.1b, %ecx.1a %ecx.1bload 8(%eax,%edx.0,4) t.1ciaddl t.1c, %ecx.1b %ecx.1ciaddl $3,%edx.0 %edx.1cmpl %esi, %edx.1 cc.1jl-taken cc.1
%edx.0
%edx.1
%ecx.0c
cc.1
t.1a
%ecx.i +1
addl
cmpl
jl
addl
%ecx.1c
addl
addl
t.1b
t.1c
%ecx.1a
%ecx.1b
load
load
load
Visualização: Loop unrolling
21
i=6
cc.3
t.3a
%ecx.i +1
addl
cmpl
jl
addl
%ecx.3c
addl
addl
t.3b
t.3c
%ecx.3a
%ecx.3b
load
load
load
%ecx.2c
i=9
cc.4
t.4a
%ecx.i +1
addl
cmpl
jl
addl
%ecx.4c
addl
addl
t.4b
t.4c
%ecx.4a
%ecx.4b
load
load
load
cc.4
t.4a
%ecx.i +1
addl
cmpl
jl
addl
%ecx.4c
addl
addl
t.4b
t.4c
%ecx.4a
%ecx.4b
load
load
load
%edx.3
%edx.2
%edx.4
5
6
7
8
9
10
11
Cycle
12
13
14
15
5
6
7
8
9
10
11
Cycle
12
13
14
15
Iteration 3
Iteration 4
Desempenho Previsto• Pode terminar uma iteração em 3 ciclos• CPE deveria ser 1.0
Desempenho Medido• CPE = 1.33• Uma iteração cada 4 ciclos
combine5(): resultado de desenrolar o ciclo
22
– Neste exemplo só benefecia a soma de inteiros• Outros casos limitados pela latência das unidades funcionais
– Optimização é não linear com o grau de unrolling• Existem muitos efeitos subtis que condicionam o escalonamento
das operações
Grau de Grau de unrollingunrolling 11 22 33 44 88 1616
IntegerInteger SumSum 2.002.00 1.501.50 1.331.33 1.501.50 1.251.25 1.061.06
IntegerInteger ProductProduct 4.004.00
FPFP SumSum 3.003.00
FPFP ProductProduct 5.005.00
Multiplicação: unrolling
23
load
load
load
imull
imull
imull
addl
cmpl
jlNão há vantagem com o desenrolar do ciclo:• não tiramos partido do encadeamento da unidade de multiplicação devido às dependências de operandos
Computação em Série
24
• Cálculo ((((((((((((1 * x0) * x1) * x2) * x3)
* x4) * x5) * x6) * x7) * x8) * x9) * x10) * x11)
• Desempenho– N elementos, D ciclos/operação
– Total = N*D ciclos
*
*
11xx00
xx11
*
xx22
*
xx33
*
xx44
*
xx55
*
xx66
*
xx77
*
xx88
*
xx99
*
xx1010
*
xx1111
Desenrolar do ciclo paralelo: Loop Spliting
25
– Produto de Inteiros• Optimização
– Acumular em 2 produtos diferentes • Realizados
simultaneamente– Combinar no fim
• Desempenho– CPE = 2.0 (era 4.0)
void combine6(vec_ptr v, int *dest){ int length = vec_length(v); int limit = length-1; int *data = get_vec_start(v); int x0 = 1; int x1 = 1; int i; /* Combine 2 elements at a time */ for (i = 0; i < limit; i+=2) { x0 *= data[i]; x1 *= data[i+1]; } /* Finish any remaining elements */ for (; i < length; i++) { x0 *= data[i]; } *dest = x0 * x1;}
Loop Spliting grau 2
26
• Computação ((((((1 * x0) * x2) * x4) * x6) * x8) * x10) *
((((((1 * x1) * x3) * x5) * x7) * x9) * x11)
• Desempenho– N elementos, D ciclos/operação– (N/2+1)*D ciclos – ~2X melhoria no desempenho
*
*
11xx11
xx33
*
xx55
*
xx77
*
xx99
*
xx1111
*
*
11xx00
xx22
*
xx44
*
xx66
*
xx88
*
xx1010
*
Requisitos para Loop Spliting
27
• Matemáticos– A operação tem que ser associativa e comutativa
• Multiplicação de inteiros: OK• Não é necessariamente verdade para operações em vírgula
flutuante
• Hardware– Múltiplas unidades funcionais ou unidades funcionais
encadeadas
Loop Spliting: Visualização
28
– As multiplicações não dependem uma da outra
– Podem ser executadas em paralelo
load (%eax,%edx.0,4) t.1aimull t.1a, %ecx.0 %ecx.1load 4(%eax,%edx.0,4) t.1bimull t.1b, %ebx.0 %ebx.1iaddl $2,%edx.0 %edx.1cmpl %esi, %edx.1 cc.1jl-taken cc.1
%edx.1
%ecx.0
%ebx.0
cc.1
t.1a
imull
%ecx.1
addl
cmpl
jl
%edx.0
imull
%ebx.1
t.1b
load
load
Loop Spliting: Visualização
29
%edx.3%ecx.0
%ebx.0
i=0
i=2
cc.1
t.1a
imull
%ecx.1
addl
cmpl
jl
%edx.0
imull
%ebx.1
t.1b
load
loadcc.1
t.1a
imull
%ecx.1
addl
cmpl
jl
%edx.0
imull
%ebx.1
t.1b
load
loadcc.2
t.2a
imull
%ecx.2
addl
cmpl
jl
%edx.1
imull
%ebx.2
t.2b
load
loadcc.2
t.2a
imull
%ecx.2
addl
cmpl
jl
%edx.1
imull
%ebx.2
t.2b
load
load
i=4
cc.3
t.3a
imull
%ecx.3
addl
cmpl
jl
%edx.2
imull
%ebx.3
t.3b
load
load
14
Cycle
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
Cycle
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
Iteration 1
Iteration 2
Iteration 3
– Desempenho Previsto• Multiplicador 4 ciclos
ocupado com 2 operações simultâneas
• CPE = 2.0
Resultados para combine()
30
Método Integer Floating Point + * + *
Abstract -g 42.06 41.86 41.44 160.00 Abstract -O2 31.25 33.25 31.25 143.00 Mover vec_length 20.66 21.25 21.15 135.00 acesso dados 6.00 9.00 8.00 117.00 Acum. em temp 2.00 4.00 3.00 5.00 Unroll x4 1.50 4.00 3.00 5.00 Unroll x16 1.06 4.00 3.00 5.00 Split 2 X 2 1.50 2.00 2.00 2.50 Split 4 X 4 1.50 2.00 1.50 2.50 Split 8 X 4 1.25 1.25 1.50 2.00 Ponto Óptimo Teór. 1.00 1.00 1.00 2.00 Pior/Melhor 39.7 33.5 27.6 80.0
Limitações de Spliting
31
• Necessita de muitos registos• Para armazenar somas/produtos temporários– Apenas 6 registos inteiros (na arquitectura IA32)
• Usados também para apontadores e variáveis auxiliares (ex. Ciclo)– 8 registos FP (na arquitectura IA32)
– Se os registos não são suficientes: register spilling na stack• Elimina quais quer ganhos de desempenho
Saltos Condicionais
• Previsão de saltos condicionais para manter o pipeline ocupado
• Nos processadores modernos uma previsão errada implica custos elevados– Pentium III: ≈ 14 ciclos do relógio
• Os saltos condicionais podem, em algumas situações, ser evitados pelo compilador com ajuda do programador
32
Saltos Condicionais
Exemplo: Calcular o máximo de 2 valores
33
int max(int x, int y){ if (x < y) return y; else return x;}
movl 12(%ebp),%edx # Get ymovl 8(%ebp),%eax # ret val=xcmpl %edx,%eax # rval-yjge L11 # skip when >=movl %edx,%eax # ret val=y
L11:
Pentium III:• 14 ciclos se previsão correcta• 29 ciclos se previsão errada
mov condicional
34
– Adicionadas à microarquitectura P6 (PentiumPro)– cmovXXl %edx, %eax
• Se condição XX verdadeira, copia %edx para %eax• Não há saltos condicionais• Corresponde a uma única µoperação
– Sem as opções correctas o compilador pode não usar esta optimização:
• Compila para o 386– Desempenho
• 14 ciclos
movl 12(%ebp),%edx # Get ymovl 8(%ebp),%eax # ret val=xcmpl %edx, %eax # ret val-y
cmovll %edx,%eax # If <, ret val=y
int max(int x, int y){ return(x < y) ? y:x;}
Top Related