Onde o processador armazena seus dados?

TL; DR

O que de fato são Registradores?

São posições de memória dentro do processador com nomes específicos, é como se fossem variáveis.

Como é o funcionamento básico de um Registrador?

São endereços que armazenam dados por um curto período (poderia ser longo, só não faz sentido) para que o processador possa manipular esse dado ou usá-lo para manipular algum outro. Inclusive alguns servem para controle fundamental do funcionamento do processador ou da execução do seu código, em geral coisas que você nem sabe se existe.

Qual é a importância que os Registradores tem em relação aos programas que eu desenvolvo?

Nada no sentido abstrato que você lida. Tudo concretamente. É só neles que há real execução e eles são muito mais rápidos que a memória onde você acha que seus dados estão na execução.

Há alguma relação entre memória RAM e Registradores?

Eles são um tipo de memória de curto prazo. A única relação com a RAM é que eles se conversam o tempo todo. Em relação aos registradores dados vem e vão de e para a RAM.

Detalhando

Basicamente é isso que está na definição :P

Memória você sabe? E variável?

Memória

Memória é composta por vários slots de dados e podemos dizer que sempre um slot tem 1 byte de tamanho. O acesso a cada slot é feito por um número, até porque tem uma quantidade grande deles. Pensa na memória como um enorme array de bytes.

Alguns desses slots podem ser acessados juntos e é possível dar um nome para acessar alguns deles em específico durante a criação do código, mas de fato o acesso é feito pelo número, mesmo que você não veja isto nele.

Registradores

Os registradores não deixam de ser uma memória, mas com características especiais e em baixíssimo número, até porque a distância que o sinal elétrico precisa percorrer precisa ser bem pequeno para acontecer muito rápido. Se tivessem muitos registradores boa parte deles ficariam longe e o tempo de acesso seria maior.

Ao contrário da memória normal, cada slot nessa memória dentro do núcleo do processador tem um tamanho um pouco maior, geralmente chamamos isso de palavra. Então em processadores 32 bits esse tamanho é 4 bytes e um de 64 bits o tamanho é 8 bytes. Mas existem registrados especiais com tamanhos diferentes, alguns são de 1 bit porque não precisa mais que aquilo, e outros pode ter vários bytes para processar ações especiais em vetores, criptografia, etc.

Não deixa de ser um local onde bits ficam armazenados por um tempo, em geral bem pouco tempo, em até 1 ou poucos ciclos. Como eles são poucos podem ter um nome. Mas como tudo em Assembly não deram nomes tão fáceis. E como não é uma tarefa específica como o ocorre em um código normal de uma aplicação os nomes são bem genéricos. Mas podemos dizer que eles são as variáveis de baixo nível de qualquer código.

Mantendo a analogia que fiz com a memória entenda eles como um grande objeto com vários membros nomeados, seria definido como uma classe ou uma estrutura.

Operações

Todas operações que o processador consegue realizar é em cima dos registradores. Não é possível manipular a memória RAM diretamente, você tem que mover a informação para o registrador manipular e depois pode mover o resultado para a RAM novamente, se for o que deseja.

As portas lógicas que executam algo pegando os bits presentes em um ou dois registradores (tem instruções especiais que podem pegar mais dados, são chamadas de SIMD) e transformando em outro(s) bit(s) que devem entrar em algum(ns) registrador(es).

Performance

O acesso a um registrador em um processador x86 tem custo na casa do picossegundos. É possível fazer 3 ou 4 bilhões de acessos por segundo. Um ARM não fica muito atrás disso. O acesso à RAM custa quase 100 nanossegundos (tem baixado um pouco), portanto uns 10 milhões de acessos por segundo. É uma diferença brutal.

Por isso é importante manter os dados no registrador. E por isso no passado escrever Assembly ajudava muito. Hoje os compiladores tendem a fazer escolhas melhores que humanos em muitos casos e coloca o que é mais importante no registrador.

Note que o tempo de acesso não é o mesmo de uma operação de manipulação. Uma divisão por exemplo pode custar vários nanossegundos até mesmo acessando apenas o registrador.

Abstração

Tudo o que você escreve em linguagem de alto nível que toca em um dado passará por um registrador.

Esse código Assembly é um pouco alto nível porque as variáveis X e Y não existem no contexto do Assembly, aí iria endereços puros de memória (no caso da stack).

Limitação

Deve estar imaginando que por serem poucos registradores (16 principais nos casos mais comuns) o que fazer quando você está trabalhando com muita variáveis (mesmo que conceitualmente falando). Você vai mandando para a memória o que não cabe no processador naquele momento. Na prática isso ocorre naturalmente porque você põe alguns dados nos principais registradores, executa algo e pega o resultado mandando para a memória.

Cache

O processador tem uma abstração legal que ele pode manter certos dados muito acessados em cache, os famosos L1, L2, L3 e finado L4 que por serem pequenos ficam mais perto do processador e tem tempos de acesso bem melhores que a RAM. E a distância é o motivo de ter vários níveis.

Em certo ponto de vista o registrador é uma espécie de cache também, onde a memória seria como o arquivo de swap dos sistema operacional, está lá para garantir que tudo funciona com grandes volumes, mas é melhor evitar o seu uso.

Eu poderia até falar nas novas memórias não voláteis que farão a RAM persistirem dados, ou poderia falar do cache line onde os dados são sempre transferidos em bloco, por isso acessar 1 byte ou 64 (tipicamente) tem custo quase igual, mas isso foge um pouco do foco.

Registradores existentes

Existem 4 registradores principais em um processador Intel x86 que são chamados EAX, EBX, ECX, EDX. Em 64 bits os nomes são RAX, RBX, RCX, RCX e obviamente os tamanhos são maiores. Como curiosidade em 16 bits eles se chamam AX, BX, CX, DX, e eles podem ser acessados em cada byte individual na sua parte baixa ou alta, então tem o AL e AH, BL e BH, e assim por diante.

Lembre-se esses são apenas nomes como se fossem variáveis, não tem muito segredo. E podemos dizer que eles tem um tipo só, que é a palavra. Quase tudo é feito nesses registradores. O mais comum, mas só por convenção é que sejam:

  • EAX usado como um acumulador (recebe resultados de operações)
  • EBX seria usado como dado base para operações
  • ECX é um contador (vai incrementando algo)
  • EDX age como dado geral a ser usado na operação.

Outros registradores bem importantes usados o tempo todo em toda aplicação que são considerados de uso geral mas que quase sempre são usados para algo bem específico são:

  • ESP (Stack Pointer - indicador de onde está o final da pilha na memória)
  • EBP (Base Pointer - indicador de onde está o escopo agora, os acessos ao dado na pilha são sempre relativos a esse endereço, em geral ele indica o começo dos dados da função em execução, por isso há uma aritmética em cada acesso a um dado)
  • ESI (Source, alguma vezes chamado de index)
  • EDI (Destination, esses últimos são usados por instruções otimizadas de acesso a dados múltiplos como arrays, incluindo strings)

Lembrando que em 64 bits eles começam com R.

Depois temos registradores especiais de segmento que não há uso prático hoje em dia com o advento da memória virtual.

Um dos mais importantes registradores é o EIP ou Instruction Pointer. É ele que sabe onde do código está executando. Cada instrução que termina sua execução incrementa para o próximo endereço de execução que o código deve realizar, que é variável nos processadores Intel-like, mas tem tamanho fixo em processadores RISC como é o ARM. Um goto (jmp) entre outras instruções manipulam esse endereço desviando para um endereço específico totalmente fora da sequência.

Em 64 bits temos os R8 ao R15 que são registradores complementares e funcionam como os primeiros, mas sem nada mais convencional para uso e são usados como otimizações, em operações simples costumam ficar vazios (conceitualmente já que sempre terá dado que estava lá).

Não falei de registradores especiais usados por instruções MMX, SSEx, etc. porque não os entendo bem e acho que não é caso da maioria dos usos.

Finalmente chegamos nos registradores de bit (flags) que recebem certos resultados de controle e são consultados em certas instruções para decidir o que fazer. Já deve imaginar que role em muitas instruções de comparação, mas não só, até em aritmética pode rolar bastante. Esses registradores são atualizados em boa parte das operações, portanto você só tem o último estado, se precisa dessa informação para alguma operação posterior (geralmente não precisa) então deve armazenar em algum lugar, seja um registrador geral ou na memória. Não vou listar todos, mas os principais são (endereços dos bits):

  • 00 CF - Carry Flag - é o famoso "vai um" (sim, o computador precisa fazer conta do jeito que você sempre fez desde criança)
  • 02 PF - Parity Flag - indica se o resultado é par ou ímpar, o que permite algumas otimizações
  • 04 AF - Adjust Flag - usado para cálculo BCD, pouco importante hoje em dia
  • 06 ZF - Zero Flag - controle se a operação resultou em 0 (falso)
  • 07 SF - Sign Flag - indica se a operação tem um sinal negativo ou não
  • 08 TF - Trap Flag - controle de passo do debugger
  • 09 IF - Interruption Flag - indica se interrupções são permitidas (responder eventos)
  • 10 DF - Direction Flag - controle de direção de strings
  • 11 OF - Overflow Flag - indica que houve estouro em operação (o resultado não cabe no espaço reservado)
  • 12-13 IOPL - I/O Privilege Level field - indica o nível de privilégio que a operação pode realizar, algumas só o kernel do SO pode realizar
  • 14 - NT - Nested Task flag - controle de encadeamento de interrupções
  • 16 - RF - Resume Flag - controle do debugger
  • 17 - VM - Virtual-8086 Mode - estabelece modo de compatibilidade
  • 18-31 - indicadores modernos de virtualização e identificação
  • 1, 3, 5, 32-63 - reservados

Seu código:

  • mov eax,Y - Está movendo para o registrador chamado EAX o valor que está na memória em certo endereço que é conceitualmente indicado por Y
  • add eax,4 - Está fazendo uma operação de adição entre o valor que está em EAX e a constante 4, obviamente que o resultado alterará alguns registradores de bits e manterá o resultado em EAX mesmo, portanto é uma operação com efeito colateral ao que está sendo usado no cálculo
  • mov ebx,3 - Está armazenando o número 3 no registrador EBX, é como se fizesse um ebx = 3;
  • imul ebx - Está realizando uma multiplicação de inteiros, que é mais simples que um de ponto flutuante usando o EBX com multiplicador, o multiplicando é implicitamente o EAX, e o resultado será armazenado em EAX, como de costume
  • mov X,eax - Está movendo para o que se chama conceitualmente X (na prática é um endereço de memória) o que está armazenado em EAX nesse momento.

Tipicamente isso pode demorar uns cento e tantos nanossegundos para executar. E apenas pouco mais de 2 ou 3 nanossegundo é o cálculo em si. Por isso que eu falo que otimizar é não acessar memória, não é economizar processamento em si. Fazer tudo o que precisa estar no processador e operar nele e conseguir evitar que registradores sejam usados de forma sobreposta em uma linha de processamento fazem absurdamente mais pela performance que economizar instruções de processamento ou usar instruções com menos ciclos de custo.

Porque toda vez que precisa usar o EAX por exemplo, e ele está ocupado, você tem que jogar para a memória o que tem ali (empilhar) para poder usá-lo sem problemas e depois que terminar essa nova operação tem que voltar o valor salvo antes na pilha para o registrador para ele continuar o que estava fazendo. Mesmo que o empilhamento ocorra no cache L1 custa bem mais caro não só porque é uma operação extra só de controle, mas também porque ela custa caro.

Um dos motivos que tenta-se fazer inline de funçãoé porque há muita cópia de dados do registrador para memória e vice-versa em chamadas de função.

Ao contrário do que as pessoas pensam, Assembly não é tão difícil assim, é chato, é esquisito, tem que ter muito cuidado, mas tem baixa complexidade e pouca abstração que dificulta o entendimento. Na verdade se adotassem uma sintaxe um pouco melhor assustaria menos. Mas claro, assim como em C o acesso ao heap ou uso de ponteiros já assusta porque é fácil cometer erros e melar a memória, em Assembly qualquer acesso assusta geral.

Pode interessar:

  • Como um computador entende o código binário?
  • Todo processador utiliza o mesmo conjunto de instruções?
  • Como um programa é carregado na memória e depois executado?
  • Execução de Instruções
  • Onde ficam armazenadas as instruções de um processador?

Coloquei no GitHub para referência futura.

Onde ficam armazenadas as informações que o processador usa?

A memória RAM é onde são armazenados dados temporários para que o processador possa acessar informações importantes de maneira mais rápida.

Onde se armazena dados?

Barato e criado de forma simples; os dados são armazenados em arquivos e pastas. É normalmente encontrado em unidades de disco rígido e significa que os arquivos são exatamente iguais tanto para o disco rígido como para o usuário.

O que fica armazenado no processador?

Ela tem o objetivo de guardar dados, informações e processos temporários acessados com frequência e assim agilizar o processo de uso no momento em que são requisitados pelo usuário. Desse modo, a memória cache pode ser considerada um pequeno componente que consta dentro do processador.

Onde os dados são processados dentro de uma CPU?

Componentes da CPU A UC também é responsável pelo controle de entrada e saída de dados da CPU. Unidade lógica e aritmética (ULA): é a parte da CPU responsável pelos cálculos (operações lógicas e aritméticas). É na ULA que os dados são processados no interior da CPU.