Sobre como os computadores lidam com seu código - Jornada pessoal de estudos - Semana 1
Published: 2023-09-14 16:16:34

### Estudo efetivamente programação à um ano, por conta disso, pretendo construir minha jornada de estudos em textos, para que possa servir de apoio para meu aprendizado pessoal. Não pretendo construir um guia, mas sim, resumir o foco dos meus estudos da semana e compartilhar para que possa ser útil para iniciantes e pessoas curiosas pelo aprendizado.
### Nessa semana estudei sobre arquitetura de computadores. Como estudante de desenvolvimento web, sinto que eu não compreendia muito bem como o meu computador está interagindo e executando os programas que faço. Essa curiosidade me bateu e eu decidi aprender um pouco mais sobre isso, então, esse resumo da semana é Sobre's de como o computador lida, interage e executa os programas. 
### Quando escrevemos um código, escrevemos *Linguagem de Alto Nível*, linguagem que os humanos conseguem entender, escrito com variáveis e textos que conseguimos interpretar. Entretanto, o computador não consegue interpretar essa linguagem. O tipo que ele compreende, são operações de soma, armazenamento e comparação, por exemplo. Uma sequência desse tipo de instruções citado anteriormente, é chamado de *Código de Máquina*. O computador utiliza o código binário para representar as instruções. Esse *código binário* proposto com 0 e 1, é formado por um conjunto de fios e circuitos que está passando eletricidade, com isso, se temos um fio que está passando eletricidade ele irá entender como 1, ao contrário então, se não estiver passando eletricidade ele irá entender como 0.
### No computador cada um do 0 ou 1 é chamado de **bit** (Binary Digit), sendo ela a menor unidade possível para armazenar informação. Como visto, bit é uma unidade pequena, por isso, costuma-se trabalhar com grupos de 8 bits, esse grupo é chamado de **byte**. Para a representação das unidades é utilizado o b minúsculo para bit e B maiúsculo para byte. 
### **1B = 8b**
## Sobre o processo de tradução
### Quando escrevemos um código como: 

</br>

```js
let a = b + c;
```

</br>

### O computador irá interpretar como uma sequência de caracteres, como as letras, espaços, caracteres especiais, e também caracteres especiais de quebra de linha. Depois de interpretar, irá identificar o conjunto de caracteres como o let, quais são seus significados e a relação entre eles. Identificando então, as letras de variáveis como a, b, c, a declaração como o let e os caracteres de atribuição como o +. 
### A partir disso, ele começa a produzir o código de máquina, fazendo então como a variável b que por exemplo tem um valor de 7, em seguida, existe também o c, com a atribuição de soma, tendo um valor de 2. Depois de somado esse valores na hora de executar (9), o computador vai guardar então na variável c, o resultado. E com isso, essas instruções serão traduzidas para a linguagem de máquina.
### A tradução do código de fonte inteiro pode ser um processo feito de duas formas. A primeira, é quando o tradutor lê o código fonte inteiro e traduz ele por completo, agrupando tudo isso em um outro arquivo, gerando então o código de máquina. Depois que o tradutor termina de realizar essa transformação, ele já não é mais necessário e posteriormente o código de máquina então irá fazer a execução no computador. Esse tradutor é conhecido como **compilador**.
### A segunda forma de fazer esse tipo de tradução, no 'qual, há uma tradução direta do código fonte para a execução no computador, sem a necessidade de um arquivo externo com o código binário. O tradutor traduz um trecho do código e manda o computador executar, sendo assim, o tradutor está continuamente traduzindo os trechos de código. Esse tradutor é conhecido como **interpretador**. 
## Sobre O compilador vs O Interpretador
### O programa compilado tem uma execução mais rápida que o interpretado, porque o compilador tem todo o contexto do programa, assim, consegue fazer otimizações mais complexas e gerar um código de máquina pronto para o computador executar. Além disso, ter o processo de compilação permite que se verifique erros sem ter que necessariamente executar, nos interpretados apenas verifica erros que estão executando. Em contrapartida, nos compiladores há um tempo a mais para executar, enquanto nos interpretadores começa-se a executar na hora. Outra diferença é que nos compiladores se executa em apenas uma máquina, enquanto nos interpretadores pode ser executado em diferentes máquinas. 
### Exemplos conhecidos de compiladores: C, Rust e Go
### Exemplos conhecidos de interpretadores: Javascript, Python, Php e Ruby
### Uma linguagem é um formalismo para escrever uma sequência de operações ou algoritmos, na verdade o interpretador ou o compilador é apenas o interpretador da **linguagem**. Portanto, *pode-se ter compiladores e interpretadores para todas as linguagens*. 
## Sobre JIT COMPILATION 
### Há também uma forma híbrida, juntando um pouco do compilador e do interpretador. uma delas é o **JIT** (Just In Time) **COMPILATION**. Ao invés de compilar o código inteiro, compila uma função específica no momento de execução e manda o código binário da função inteira para executar, com isso aumenta a performance do código, traz a velocidade dos códigos compilados e não é necessário passar pelo processo de compilação no meio, ou seja, modifica o código e consegue executar. 
## Sobre JAVA (curiosidades)
### O meio termo entre interpretação e compilação. 
### A implementação de Java, quer resolver o problema de poder compilar o seu código e rodar em diversas máquinas diferentes. Por conta disso, com o código fonte que quer executar, compila. Ao invés de gerar um código binário, irá gera um código intermediário chamado de Bytecode conhecido no Java como .class, esse bytecode vai ser um código específico do Java, *mas não é específico da máquina que está rodando*, por isso, para executar esse código, têm que passar depois do Bytecode para o interpretador conhecido aqui como **JVM** (Java Virtual Machine), é ele que irá  pegar o Bytecode, interpretar e executar.
## Sobre memória.
### Quando você tem um código fonte e um código de máquina, esses dois arquivos ficam arquivados em um sistema de pastas. Isso é uma função do sistema operacional que vai controlar onde cada byte será armazenado e como será interpretado cada arquivo da estrutura. Os arquivos ficam fisicamente guardados na **memória não-volátil**, que guarda a informação mesmo com ele desligado. Existem dois tipos de **memória não-volátil**. A primeira delas é o **HD** (hard disk), a vantagem desse armazenamento é a grande capacidade de armazenamento e é mais barato, entretanto, ele é mais lento e frágil, deixa a leitura e a escrita dos arquivos mais lenta.
#### O segundo meio de armazenamento é o **SSD** (Solid State Drive), ele é formado por chips eletrônicos, bem parecido com a tecnologia dos pen-drives. A grande vantagem dos SSD é que eles são menores fisicamente, são mais rápidos e mais resistentes. A desvantagem é ser um pouco mais caro e ter uma limitação de escrita. O jeito que os SSD são construídos, se escrevermos muito em uma parte específica, os bits (a capacidade de armazenamento ou de retenção) dele, acaba se desgastando. 
### Além disso, o **HD** e o **SSD** são chamados de **memória secundária**, isso acontece porque o computador não trabalha diretamente com esse tipo de armazenamento, se temos os programas que queremos executar, para trabalharmos com ele, temos que enviar para um segundo tipo de memória que é chamado de **memória de trabalho** do computador a memória **RAM**.

## Sobre Memória RAM
### É na memória RAM (Random Access Memory) que guardamos os dados que o computador está lendo e modificando constantemente no momento, por isso ela é chamada de memória de trabalho. Quanto maior a memória RAM, mais consegue-se executar vários programas ao mesmo tempo. Quanto maior a memória secundária, mais aplicativos, programas e etc. Se precisamos desligar o computador todos os dados que estão lá dentro são apagados, por conta disso, ela é chamada de memória volátil. Acessar qualquer posição da memória sempre terá o mesmo custo/tempo para pular de um endereço pro outro. 

## Sobre Memória ROM
### Quando o computador liga ele sabe quais dispositivos estão conectados à ele por conta de memória ROM (Read-Only Memory), ela é uma memória não-volátil com baixo armazenamento. Importante saber que não é esperado que o usuário escreva informações nessa memória. Além disso, as primeiras informações que o computador irá buscar estão na memória ROM, como o BIOS (Basic Input/Output System).
### Com todo esse passo a passo do ssd e hd para a memória RAM, o código então tem que ser mandado para o *processador*, certo?
## Sobre o CPU
### O **CPU** (Central Process Unit) é como um chip e conhecido também como processador, ele que vai receber informações da memoria RAM e irá executar elas. Essa CPU é dividida em algumas partes. A primeira parte é a **UC** (Unidade de Controle), ela que vai pegar, olhar a **instrução** e analisar bit a bit, tentando entender o que ela significa. Ela coordena todos os estados do processador para executar a intrução. A segunda parte **ULA** (Unidade Lógico Aritmética),  é a parte que vai executar e manipular a maioria desses dados, ela que realiza operações aritméticas como soma, multiplicação e etc, as operações de comparação e também as lógicas (verdadeiro ou falso). Ela vai receber da unidade de controle qual vai ser os estados que ### Há também os **registradores**, que é um conjunto de posições o qual podemos armazenar valores intermediários entre as operações. É desses registradores que recebemos os valores e onde guarda-se o resultado das operações, é a memória mais básica, armazena a instrução atual, posição da instrução atual, valores intermediários.
### Um processador é um componente repleto de circuitos e fios, com eletrecidade passando a todo momento, por conta disso, temos que ter certeza de que as coisas estão acontecendo de forma ordenada.
### Todas as intruções do computador são realizadas em sequências especificas e cíclicas que realizam as intruções. A primeira é buscar (fetch), é nessa fase que o computador vai para a memoria RAM buscar a instrução que ele tem que executar. Com isso, ele guarda essa instrução no registrador, que guarda a instrução atual. O computador para saber qual instrução ele deve buscar, ele utilizar o **Contador**, que é ele que armazena a posição na memória da instrução atual, fazendo isso de forma sequencial, se executarmos na posição 0, depois sera a 1, 2. A segunda fase é decodificar, essa instrução é mandada para unidade de controle e ela configura o processador como todo para podermos executar a instrução, com isso ela configura o ponteiro da memoria RAM para saber a posição da memória. A terceira fase é executar a instrução. O processador irá pegar a posição da memória e armazena em um outro registrador especial chamado de acumulador, no qual vamos acumulando valores intermediários entre as operações. E depois de terminar de executar, ele volta para a primeira que é buscar. 
## **BUSCA -> DECODIFICA -> EXECUTA**
## Sobre o CLOCK
### Todas essas instruções precisam ser sincronizadas no processador, isso é chamado de **Clock**, ele determina o tempo que cada fase ou operação vai acontecer. A velocidade no qual as fases do clock ocorre, determina a velocidade que uma intrução vai ser executada. Um processador tem uma médica de clock de 3 bilhões de *tics* por segundo, isso equivale a 3 GHz. 
## Sobre os processadores modernos 
### Como vimos anteriormente, a velocidade do processador era diretamente ligada a velocidade do Clock. Entretanto, isso foi ficando cada vez mais dificil de aumentar a velocidade, por conta de problemas como aquecer demais e consumir muita energia. Com isso, foi necessário criar outras alternativas para melhorar o desempenho. 
### Uma alternativa é conseguir executar a parte de buscar, decodificar e executar simultaneamente. Dessa forma,  ao inves de buscar apenas quando terminar de executar, quando tivermos decodificando a instrução lida, o processador já ir pegar a proxima instrução. Dessa forma as instruções vão ficar como uma linha de montagem, sempre esperando para serem executadas. Com isso, consegue-se executar 1 instrução a cada ciclo de clock ao invés de uma instrução a cada 3, triplicando a velocidade com que os processadores executam as operações, chamando de **PipeLine de Instruções**.
### Quando se esta buscando coisas na memória RAM, a ULA não esta fazendo nada, então, pode-se utilizar instrução de conta para a ULA ir executando, para assim, o resultado já estiver feito quando precisar. Com essa ideia, se duplicarmos alguns componentes do processador, **duplicaremos** todo o pipeline, fazendo assim duas intruções a cada ciclo de clock. 
### Um processador que consegue ter essa performance para executar as intruções, se duplicar esse conujunto dos circuitos, consegue-se duplicar o número de instruções que a gente executa. Se duplicarmos a caixa do processador que tem a ULA, UC e Registradores que são chamados de **core** (núcleo)  têm-se um processador **dual-core** ou **quad-core**(dois ou quatro). Com isso, eles conseguem executar em paralelo todas as intruções. 

## Sobre Drivers de Dispositivos
### Esses dispositivos é a ponte entre o usuário e o computador, é com eles que conseguimos interagir com os componentes internos. 
### Internamente, o computador tem os **Drivers de Dispositivo** que conseguem abstrair várias particularidades desses dispositivos, atraves desses driver ele sabe como imprimir, por isso que, mesmo que você troque de monitor, ainda será impresso os mesmos dados.
## Sobre como esses dispositivos funcionam
### Quando liga-se o monitor há uma gama de gráficos, cores e formas. Esses gráficos são um conjunto de quadradinhos de cores diferentes, cada quadradinho desse é chamado de *pixel*. É como se fosse uma fonte de luz que apenas muda a cor que ele vai ter. A cor de um pixel é dado por três componentes: **R(red)G(green)B(blue)**. Se por exemplo, essas cores RGB estiverem no máximo, vemos a cor branca. 
### Os pixels são atualizados na tela variando de monitor para monitor. Mas de um modo geral, eles são atualizados linha por linha da esquerda para a direita. 
### Existe diversos cálculos complexos no qual o computador faz para saber qual a cor e posição de um pixel. Quando pensamos em uma tela full HD por exemplo, ela tem 1920 pixels por 1080 pixels, isso dá aproximadamente uma tela com 2 milhões de pixels. Isso é um processamento muito grande e para solucionar esse problema, a maioria dos computadores utiliza componentes especificos para esse tipo de serviço como a  **GPU** (Graphics Processing Unit) **placa de vídeo**. A placa de vídeo foi feita para ser capaz de realizar esses calculos mais complexos de forma rápida. Hoje em dia, existem as **placas de video integradas**, que vem junto com o chip do próprio processador. 
### O teclado é como um conjunto de fios energizados que passam pelas linhas do computador e um conjunto de fios desenergizados que passam pelas colunas, cada tecla será um conjunto de linha e coluna. Uma tecla em específico pode ser comparada como um *interruptor*, que liga alguma linha energizada com alguma coluna desernegizada, emitindo o sinal que a tecla foi pressionada. O interessante a se pensar é que não necessariamente o teclado envia um sinal que a tecla A por exemplo foi pressionada, mas sim que o carácter na posição 11 foi pressionado. Ele apenas avisa qual foi a posição que o carácter ### Portanto, a unidade de processamento GPU é muito usada para outras aplicações fora dos gráficos. Ele é utilizado para algoritmos de machine learning e dados, pois, como foi dito anteriormente, a GPU tem uma alta capacidade de processamento numérico, por isso, são utilizadas para acelerar cálculos.
### Além da GPU, foi criada tambem a TPU (Tensor Processing Unit) para o ramo de machine learning, usada por grandes empresas de tecnologia, existe um [vídeo da google](https://www.youtube.com/watch?v=MXxN4fv01c8) que explica mais sobre a TPU.

## Sobre Performance da memória construidas por materiais distintos:
###  **DRAM**(Dynamic RAM), esse tipo de RAM é mais barata, assim conseguimos armazenar grandes quantiades de memória, entretanto, ela é mais lenta.
### **SRAM** (Static RAM) esse tipo de RAM é mais rápida e mais cara, ela armazena apenas algumas quantidades de dados, por conta do custo.
### É a SRAM que auxilia o processo de comunicação com a memória DRAM, essa ram estática é colocada no processador, porque é no processador que os dados estão sendo manipulados. Nessa memória mais rápida é onde guardamos os dados utilizados no momento. Esse tipo de memória no processador é chamado de memoria **CACHE**
## Sobre como a memória cache funciona
### Quando um processador quer pegar um dado na memória e executar ele no processador, ele captura um bloco todo e copia na memória cache. Assim, ele pega o valor da primeira instrução e executa. Ele captura o bloco inteiro porque provavelmente os dados que queremos usar estão muito próximos. Quando ele busca valores na memória RAM, procura primeiro se na memória cachê já não tem.
## Cache Friendly
### Algumas linguagens que oferecem um controle maior na alocação de memória, como **C** e **C++**, podem ter o tempo de execução de seus programas influenciados pela memória cache. Ou seja, só mudando a ordem de execução de alguns trechos de código, ele pode ficar bem rápido ou lento. Programas que conseguem otimizar ao máximo isso são chamados de cache-friendly ou bons para o cache.

## Sobre a hierarquia de memória
### Ela pode ser descrita da mais rapida para mais lenta, com os regristradores no topo, desce na hierarquia e tem os niveis de CACHE, descendo mais sequencialmente temos a RAM, SSD, HD, CLOUD. 
![imagem da hierarquia de memória](https://jkolb.com.br/wp-content/uploads/2016/06/hierarquia-de-mem%C3%B3ria.png)

## Sobre o aprendizado
### Foi bem interesse entender um pouco de como o computador funciona. Saber mais afundo sobre seu instrumento de trabalho e de estudos é importante para compreender melhor o seu código e o como tudo isso está organizado. As vezes nos deparamos com pequenos problemas de performance e não sabemos lidar com isso porque não apreendemos o básico sobre o instrumento. Não é apenas abrir o VSCODE e codar, é conseguir enxergar a estrutura que a linguagem utiliza no computador, entender sua execução e entender o impacto que esse conhecimento causa na forma como você enxerga o seu código e também, o como você lida com os erros.