Como as linhas de código são executadas pela CPU?

11

Estou tentando realmente entender como exatamente uma linguagem de alto nível é convertida em código de máquina e depois executada pela CPU.

Entendo que o código é compilado no código da máquina, que é o código de baixo nível que uma CPU pode usar. Se eu tiver uma declaração de atribuição, diga:

x = x + 5;
y = x - 3;

A CPU executa cada linha uma de cada vez? Portanto, ele primeiro executará x = x + 5; instrução e, em seguida, a próxima instrução que a CPU executará é y = x- 3; Estou realmente tentando entender o processo de execução e como o código que escrevo é realmente executado pela CPU.

Frankie
fonte
Você pode tentar entender o design de uma das CPUs de código aberto; existem algumas implementações baseadas em pilha realmente simples, como excamera.com/sphinx/fpga-j1.html - são muito mais simples que as arquiteturas de 3 endereços como no seu exemplo.
SK-logic
3
Quando entrei nesse negócio, isso teria respostas simples e bem definidas. Atualmente, as CPUs são extremamente complicadas e fazem todo o tipo de coisas para aumentar o poder de processamento.
precisa saber é o seguinte

Respostas:

12

As linhas de código não têm nada a ver com a maneira como a CPU a executa. Eu recomendo a leitura do assembler, porque isso ensinará muito sobre como o hardware realmente faz as coisas. Você também pode obter a saída do assembler de muitos compiladores.

Esse código pode ser compilado em algo como (em uma linguagem assembly criada):

load R1, [x] ; meaning load the data stored at memory location x into register 1
add R1, 5
store [x], R1 ; store the modified value into the memory location x
sub R1, 3
store R1, [y]

No entanto, se o compilador souber que uma variável não é usada novamente, a operação de armazenamento pode não ser emitida.

Agora, para o depurador saber qual código de máquina corresponde a uma linha de origem do programa, as anotações são adicionadas pelo compilador para mostrar qual linha corresponde a onde no código de máquina.

maxpolun
fonte
Por que não? Uma arquitetura de três endereços terá instruções como ADD Rx, Rx, $5e SUB Ry, Rx, $3(assumindo que as variáveis ​​xey foram mapeadas nos registradores). Você está descrevendo uma abordagem RISC de carregamento / armazenamento.
SK-logic
1
@ SK-logic: Embora isso possa acontecer para linhas de código muito simples em linguagens de programação muito simples com tipos de dados e operações que a CPU suporta com bastante facilidade, não é o caso geral. É conveniente para especialistas, mas primeiro é importante perceber que as instruções do código da máquina geralmente apresentam pouca resemelhança às linhas de código em uma linguagem de alto nível.
@ SK-Logic: isso só funciona para este exemplo em particular. Em geral, no entanto, maxpolun está certo. As instruções de idioma de alto nível devem ser traduzidas para um idioma de nível inferior, com mais "burocracia" necessária para fazer coisas conceitualmente simples. Acho que o OP estava pedindo um exemplo dessa transformação.
Andrés F.
1
@ SK-Logic: o OP começou sua pergunta com "Estou realmente tentando entender exatamente como uma linguagem de alto nível [...]"
Andres F. -
1
@ SK-logic O contexto é "Se eu tiver uma declaração de atribuição, diga: [snippet de código] A CPU executa cada linha uma de cada vez?" - parece-me que ele pretende ser código-fonte em uma linguagem não montadora. De maneira mais geral, não vejo nenhum indicador de como é o código da máquina de baixo nível e algumas frases (como falar de linhas) indicam alguns conceitos errôneos. Isso não é tão impossível quanto você sugere, nem todo mundo teve o prazer de ser jogado de cabeça em alguns microcontroladores simples (como eu e aparentemente outros). Talvez Frankie deva esclarecer.
2

Depende.

Nos primeiros dias de máquinas realmente simples, sim, o código executava uma linha de cada vez. À medida que as máquinas se tornaram maiores, mais rápidas e mais complexas, você começou a ver a capacidade de executar várias instruções simultaneamente e a leitura e gravação da memória, demorando muito mais tempo do que as operações nos registradores.

A otimização dos compiladores teve que levar isso em conta, e as linhas fornecidas podem ser executadas "mais ou menos" em paralelo, com uma parte do processador trabalhando no cálculo de y, enquanto outra parte estava armazenando o novo valor anteriormente calculado de x (e o cálculo de y estava usando esse novo valor do registrador).

O Control Data 6600 foi a primeira máquina que conheço que fez esse tipo de coisa. A adição de números inteiros levou 300 nsec, a referência de memória (leitura ou gravação) levou 1000 nsec, multiplica e divide leva muito mais tempo. Até dez instruções podem ser executadas em paralelo, dependendo das unidades funcionais necessárias. Os compiladores CDC 6600 FORTRAN foram MUITO bons em agendar tudo isso.

John R. Strohm
fonte
Nesse caso, a entrada da próxima instrução depende do primeiro resultado da instrução, portanto, ela deve ser executada sequencialmente.
SK-logic
@ SK-logic: Não é bem assim. A entrada da segunda linha depende do resultado do lado direito da primeira linha, mas, com base apenas no que podemos ver no código de exemplo original, NÃO pode depender do armazenamento na memória do resultado de a primeira linha. Se x tivesse sido declarado volátil (em C / C ++), o compilador seria obrigado a armazenar o resultado primeiro e, em seguida, recolocá-lo na memória, antes de começar a calcular o novo valor de y, pois o "volátil" significa que algo (um manipulador de interrupção, digamos) poderia entrar e zap x entre as duas linhas.
John R. Strohm
Eu assumi que xey são registradores (e o código está em uma linguagem de pseudo-montagem de três endereços em vez de algo como C). Nesse caso, ambas as instruções são inevitavelmente seqüenciais. Caso contrário, o OP teria que fazer duas ou mais perguntas diferentes em vez desta.
SK-logic
Gostaria de saber se os processadores tentariam "especular" qual é o valor x? Dessa forma, ele já executou o código e o armazenou em cache.
Kolob Canyon
Mesmo que sejam registradores, DEPENDENDO NA MÁQUINA, você não pode assumir que as instruções são executadas completamente sequencialmente. O 6600 tinha uma lógica de agendamento (o "placar") que forçaria a semântica seqüencial, com base no pressuposto de que o programador queria fazer o óbvio. Máquinas posteriores omitiram esse hardware, confiando nos compiladores para agendar as instruções cuidadosamente. Os programadores humanos que faziam a programação em linguagem assembly naquelas feras eram por conta própria.
John R. Strohm
1

Não, não há mapeamento individual entre linhas / instruções de código em idiomas de níveis superior e inferior. De fato, as duas linhas acima são traduzidas em várias instruções de código de máquina , como

  1. carregar um valor de um determinado endereço de memória em um registro
  2. modificar o valor
  3. escreva de volta à memória

Os detalhes reais dessas instruções variam entre as plataformas.

Esta é a visão básica das coisas. No entanto, para complicar ainda mais os problemas, as CPUs modernas aplicam técnicas como pipelines de execução , execução fora de ordem e múltiplos núcleos , entre outros. Isso resulta na CPU fazendo várias coisas ao mesmo tempo, por exemplo, pipelines processam diferentes fases das instruções subseqüentes em paralelo dentro da mesma unidade de processamento, enquanto vários núcleos podem processar instruções independentes em paralelo.

Péter Török
fonte
0

Você deve procurar grandes detalhes em um livro para encontrar mais detalhes sobre como ele funciona, possivelmente também uma classe de compilador.

Basicamente, sua pergunta está focada em dois aspectos diferentes.

1) Como o código é traduzido em código de máquina?

2) Quando / como o código é calculado usando paralelização?

A resposta para 1) depende do idioma que você usa (embora, por exemplo, seja trivial, a saída seria a mesma). A maneira como o compilador faz a tradução para o código de máquina é uma das forças do idioma. Além disso, existem várias preocupações que precisam ser levadas em consideração no seu exemplo: o código deve carregar os dados na memória, armazená-los etc.

Finalmente, a paralelização é um recurso que você pode forçar do ponto de vista da programação, mas, em poucas palavras, alguns processadores podem tentar pensar que uma parte do código pode ser executada ao mesmo tempo, porque são independentes. No seu caso, claramente, não é o caso, pois você precisa executar as instruções sequencialmente; portanto, não, ele não será executado ao mesmo tempo.

SRKX
fonte