From f09d908910ea62efb5c376556c7d08f9e4e8a0cf Mon Sep 17 00:00:00 2001 From: Diogo Correia Date: Sun, 12 Nov 2023 11:37:19 +0100 Subject: [PATCH] refactor: fix typos and clarify stuff --- content/oc/0006-pipelines.md | 56 ++++++++++++++++++++---------------- 1 file changed, 32 insertions(+), 24 deletions(-) diff --git a/content/oc/0006-pipelines.md b/content/oc/0006-pipelines.md index 6dade11c..fb637f16 100644 --- a/content/oc/0006-pipelines.md +++ b/content/oc/0006-pipelines.md @@ -25,8 +25,8 @@ tempo para executar uma instrução não é reduzido. A Pipeline do MIPS tem [5 etapas](color:pink) (stages): - **[IF](color:green): Instruction Fetch** - a instrução é lida -- **[ID](color:green): Instruction Decode** - leitura de registos involvidos -- **[EX](color:green): Execute** - a operação aritemética é executada ou o endereço é calculado +- **[ID](color:green): Instruction Decode** - leitura de registos envolvidos +- **[EX](color:green): Execute** - a operação aritmética é executada ou o endereço é calculado - **[MEM](color:green): Memory** - acesso à memória - **[WB](color:green): Write-Back** - escrever o resultado num registo @@ -43,15 +43,15 @@ várias instruções (com diferentes fases) simultaneamente. Assim que a pipelin é completada a cada ciclo, o que nos dá um CPI de 1. Se todas as etapas tiverem balanceadas, i.e. demorarem todas o mesmo tempo: $$ -TimeInstructions_{pipelined} = \frac{TimeInstructions_{non pipelined}}{Number of Stages} +\text{TimeInstructions}_{\text{pipelined}} = \frac{\text{TimeInstructions}_{\text{non pipelined}}}{\text{Number of Stages}} $$ ## ISA do MIPS & Pipeline O Instruction-Set Architecture (**ISA**) do MIPS foi desenhado para pipelining. Logo: -1. Todas as instruções são de 32-bits - mais fácil fazer fetch e decode num ciclo. -2. Existem poucos formatos de instrução - mais facil fazer decode e ler registos numa etapa. +1. Todas as instruções são do mesmo tamanho (32-bits) - mais fácil fazer fetch e decode num ciclo. +2. Existem [poucos formatos de instrução](/oc/linguagem-computador/#categorias-de-instruções) - mais fácil fazer decode e ler registos numa etapa. 3. Apenas ocorrem operações de memória em _Loads_ e _Stores_ - podemos usar o passo de execução para calcular endereços de memória. 4. Cada instrução escreve no máximo 1 resultado - nos últimos andares da pipeline (MEM ou WB) 5. Operandos têm que estar alinhados em memória - uma transferência de dados leva apenas a um acesso à memória de dados @@ -66,20 +66,23 @@ chamamos **[Pipeline Hazards](color:yellow)** e existem 3 tipos: - **[Data Hazards](color:green)**: a instrução seguinte depende do resultado da instrução anterior. -- **[Control Hazards](color:green)**: uma decisão pode ser tomada antes da condição ter sido avaliada por uma instrução anterior (como acontece nos _Branches_). +- **[Control Hazards](color:green)**: uma decisão pode ser tomada antes da condição ter sido + avaliada por uma instrução anterior (como acontece nos _branches_). -Normalmente, estes problemas conseguem ser resolvidos através de _stalls_. A unidade de controlo +Normalmente, estes problemas conseguem ser resolvidos através de _stalls_, isto é, a introdução de um atraso +entre a execução de duas instruções, de forma a evitar estes _hazards_. No entanto, _stalls_ são indesejados +dado que aumentam o tempo de execução e diminuem a eficiência do processador. A unidade de controlo da pipeline é responsável por detetar estes problemas e resolvê-los. ## Structural Hazards Quando há conflito no uso de um recurso. Por exemplo no caso da pipeline do MIPS com uma única -memória: instruções _Load_/_Store_ acedem a dados, pelo que o fetch da instrução deveria -ter que usar um "stall" para esse ciclo. +memória: instruções _Load/Store_ acedem a dados, pelo que o fetch da instrução deveria +ter que usar um _stall_ para esse ciclo. ## Data Hazards -Uma instrução depende do acesso a dados realizado por uma instrucão anterior. Existem vários tipos +Uma instrução depende do acesso a dados realizado por uma instrução anterior. Existem vários tipos de data hazards mas o mais abordado na cadeira é o _RAW_ (Read After Write), onde uma instrução tenta ler um registo que ainda não foi escrito por outra anterior. @@ -97,11 +100,11 @@ Olhemos para o seuinte exemplo: ![Data Hazard](./assets/0006-dataforwarding.png#dark=1) -Na primeira instrução, o valor do registo \$s0 irá ser determinado no andar EX, como em -qualquer outra instrução aritmética. Se forwarding não fosse usado teriamos de esperar -até que este valor fosse escrito no registo \$s0, no andar WB, para que a segunda instrução -podesse usar esse valor, o que nos obrigaria a usar _stalls_ que [degradariam a performance](color:pink) -do CPU. Em vez disso podemos simplesmente propagar o valor (fazer forwarding) logo após este +Na primeira instrução, o valor do registo `$s0` irá ser determinado no andar EX, como em +qualquer outra instrução aritmética. Se forwarding não fosse usado, teríamos de esperar +até que este valor fosse escrito no registo `$s0`, no andar WB, para que a segunda instrução +pudesse usar esse valor, o que nos obrigaria a usar _stalls_ que [degradariam a performance](color:pink) +do CPU. Em vez disso podemos simplesmente propagar o valor (fazer _forwarding_) logo após este ser calculado, partilhando-o entre etapas. É importante ter em conta que apesar de bastante útil, esta sofisticação pode não ser @@ -110,9 +113,11 @@ anteriormente ao ciclo em que o valor foi calculado. ![Exemplo de Data Forwarding](./assets/0006-dataforwardingeg.png#dark=1) -:::tip[Exemplos] +:::info[Exemplos] -É recomendado a resolução da ficha prática de Pipelining para melhor compreensão do conceito de data forwading. +É recomendado a resolução da ficha prática de _pipelining_ para melhor compreensão do conceito de _data forwading_. + + ::: @@ -121,8 +126,8 @@ anteriormente ao ciclo em que o valor foi calculado. Um branch determina o fluxo de controlo, sendo necessário esperar pelo seu resultado. Nem sempre o pipeline consegue fazer fetch da instrução correta. Existem varias soluções: -- **Stalling**: lento e forte impacto negativo no CPI -- Fazer a decisão o mais cedo possível no pipeline (reduzindo ciclos afetados por stall) +- _**Stalling**_: lento e forte impacto negativo no CPI +- Fazer a decisão o mais cedo possível no pipeline (reduzindo ciclos afetados por _stall_) - Adiar a decisão (necessário apoio do compilador) - **Prever o resultado** (e esperar que corra bem) @@ -133,7 +138,8 @@ todos os branch stalls com **[delayed branches](color:pink)**. Com esta técnica executamos sempre a próxima instrução sequencial depois da instrução branch, sendo que o branch só é efetuado após essa instrução. -Com pipelines maiores, o branch delay começou a precisar de mais que um slot. +Em processadores mais sofisticados, com pipelines maiores, o branch delay começou +a precisar de mais que um slot. ### Branch Prediction @@ -143,16 +149,18 @@ o que permite ir buscar a instrução a seguir ao branch sem delay. Caso, afinal seja efetuado, é necessário fazer **[flush](color:yellow)** das instruções que entretanto tinham sido entretanto feitas, i.e. substituí-las por um _nop_. -### Static Branch Prediction +Existem dois tipos de _branch prediction_. + +#### Static Branch Prediction -Os control hazards são rresolvidos assumindo sempre um dado resultado e procedendo sem +Os control hazards são resolvidos assumindo sempre um dado resultado e procedendo sem esperar para ver o resultado final. Os dois tipos de Static Prediction mais comuns são: **Predict Branch Not Taken** - assume-se sempre que [o branch não é tomado](color:pink) (é feito flush caso seja tomado) **Predict Branch Taken** - assume-se sempre que [o branch é tomado](color:pink) (é feito flush se não for tomado) -### Dynamic Branch Prediction +#### Dynamic Branch Prediction O hardware mede o comportamento do branch, tendo em conta o seu historial das últimas decisões. Assume que no futuro, o comportamento se vai manter, atualizando o historial quando estiver errado @@ -165,7 +173,7 @@ ocorram duas escolhas erradas sucessivas. ## Exceções e Interrupções -Até agora vimos a utilidade do _Pipelining_ e as diversas formas que temos de +Até agora vimos a utilidade do _pipelining_ e as diversas formas que temos de [resolver dependências entre instruções](color:yellow). Mas é imperativo que quando surjam eventos inesperados na execução, estes sejam tratados pelo CPU. Estes eventos podem ser geralmente categorizados em duas categorias: