Bom dia a todos,
Os computadores atuais são incrivelmente rápidos, têm dezenas de milhares de milhões de transistors, são super escalares (tendo várias unidades de execução dentro de cada core e funcionando todas em pipeline e em paralelo, com múltiplas instruções em transito em simultâneo. Nestes pipelines pretendemos o mínimo de bolhas, pois as bolhas são oportunidades perdidas de aproveitar ao máximo a performance que têm disponível no vosso CPU), têm múltiplos cores, 8 ou 16 já são comuns, mas já existem CPU’s de 64 cores e de 128 cores dentro de um único chip. Cada core é hyper threaded, para poder aproveitar melhor as suas unidades de execução e para esconder subtilmente os tempos de acesso à memoria, tem instruções SIMD (Single Instruction Multiple Data) como as AVX512 que fazem 16 instruções FMA (Fused Muiltiply and Accumulate) f32 por ciclo de clock, tem pelo menos 3 níveis de cache, L1 (instructions, data), L2 e L3, permitem fazer prefetch de linhas de cache e assim ter os dados prontos na L2 ou L1 quando chegar a vez de os usar no código, têm um grande número de registos, com register renaming, permitem usar TLB’s de tradução de endereços de memoria virtual com dimensões muito grandes, quando o kernel do sistema operativo assim está configurado (Linux vem por omissão).
Todo este manancial significa que mesmo no CPU, sem programar para o GPU de uma placa gráfica (onde podem ter até 40 tera flops de fp32 ), um programador dito “normal”, consegue obter ganhos de performance muito grandes se estudar um pouco e se ganhar alguma experiência a fazer estas optimizações.
Todos os programadores sejam eles de Javascript, sejam eles de Python ou de Ruby, ou de outra linguagem qualquer high level, deveriam pensar bem neste slide.
Não podemos dar-nos ao luxo de estarmos a desperdiçar o poder de cálculo que já conseguimos ter nos nosso CPU’s actuais, pela utilização de linguagens menos eficientes para resolver problemas que exijam alta performance, ou que possam ser feitos com pouco esforço com mecanismos de alta performance.
Vejam o seguinte slide do Professor Patterson e pensem bem nele:
Ver a imagem em anexo:
Patterson_slide.png
Aqui está a ser dito que o mesmo algoritmo de multiplicação de matrizes feito em Python vs feito em C otimizado é mais lento em Python 62.806 x vezes!!!
O que é que isso equivale?
Equivale a que se as linguagens fossem à mesma velocidade se estivesse a usar um core a 5 GHz (5_000_000 KHz) de um computador actual em C Vs é como se em Python se estivesse a usar um core a 80 Kilo Hertz, ou seja era o equivalente a um chip de 2021 vs um CPU da década de 1970 ou 1960 de há 50 anos atrás!
E isto é na mesma máquina, só escolhendo uma linguagem e programação diferente e fazendo umas optimizações!
Por isso está ao alcance de qualquer programador!
Por isso é que é tão importante a todos os programadores conhecerem linguagens como o C, ter umas luzes do C++ e quem sabe aprender Rust, que é uma forma boa de entrar nas linguagens de alta performance de forma suave e progressiva, mas sem ter de descer o nível de abstração.
O Python, que é uma linguagem que eu gosto particularmente, só consegue ser utilizado por muita gente, pois tem o trabalho de muitos programadores dedicados e fazerem libs e módulos de alta performance em C, C++ e Rust e que depois são usados em Python. Mas como é óbvio isso cria o problema das duas linguagens de programação e da dificuldade de debug em duas linguagens em simultâneo.
A imagem do slide anterior foi extraída de uma palestra dada na ACM.
John Hennessy and David Patterson 2017 ACM A.M. Turing Award LecturePara perceberem a fundo como funciona um computador atual (CPU e não só) vejam os 3 livros dos professores John L. Hennessy e David A. Patterson, eles são as principais referências sobre o tema. Comecem por ver em RISC-V pelo facto da ISA (Instruction Set Architecture) ser mais simples, com menos instruções do que a x86_64 e mais fácil de perceber.
Computer Organization and Design RISC-V Edition: The Hardware Software Interface 2nd Ed.by David Patterson, John L. Hennessy
Computer Architecture: A Quantitative Approach 6th Ed.by John L. Hennessy, David A. Patterson
The RISC-V Reader: An Open Architecture Atlas 1st Edby David Patterson, Andrew Waterman
Depois existe um outro livro também muito importante, é
uma visão mais focada no programador:
Computer Systems: A Programmer's Perspective, 3 Edby David R. O'Hallaron, Randal E. Bryant
As optimizações iniciais devem ser sempre feitas usando os melhores algoritmos possíveis com código dito normal, sem preocupações de optimizar para um CPU moderno, quer seja em C, C++ ou Rust ou para este efeito em qualquer outra linguagem, isto antes de se passar para as optimizações para aproveitar ao máximo um determinado CPU genérico actual, CPU específico ou GPU.
Podem encontrar boa informação sobre algoritmos no link seguinte:
How to become dangerous in Algorithmshttps://github.com/joaocarvalhoopen/How_to_become_dangerous_in_algorithmsRecentemente encontrei uma referencia fantástica com as aulas de Programação Paralela tanto a nível single thread, ensinar a aproveitar ao máximo cada um dos cores super escalares, como a nível multi-thread, e depois no final com programação de GPU’s. Isto para C, C++ e Rust.
Programming Parallel Computers - In depth lectures noteshttps://ppc.cs.aalto.fi/Comparing Parallel Rust and C++https://parallel-rust-cpp.github.io/Estas optimizações nunca são feitas em todo o programa, normalmente existe um chamado Hot Spot onde o programa passa a maior parte do seu tempo e é só essa parte que nos devemos focar. É essa parte que devemos atacar com todos os nosso esforços. Em Linux para saberem onde estão os vossos Hot Spots podemos usar o Perf.
Compilem o vosso programa em C, C++ ou Rust na versão optimizada seja -O3 em GCC ou G++ ou seja em Rust na versão de release (opção --release no cargo). Podem ainda escolher a opção para gerar código para o vosso processador nativo. No entanto não se esqueçam que têm de gerar o executável com a tabela de simbolos de debug, de modo a que depois vejam o assembly anotado com as instruções do vosso código C, C++ ou Rust, caso contrário é muito fácil de se perderem no assembly e terão de adicionar ao vosso código labels dummy de Assembly para se situarem. Mas se adicionares a geração da tabela de símbolos de Debug tudo é mais simples. Se não me engano em GCC é a opção -g e em Rust tem de acrescentar no ficheiro .toml as duas seguintes linhas e funciona quando compilarem a versão optimizada --release.
[profile.release]
debug = true Para usarem o Perf tem de o instalar, por exemplo em Linux da Debian ou derivados (como o Ubuntu) com um simples apt-get e depois tem de ver a vossa versão do kernel e tem de instalar com o apt-get o package para a vossa versão especifica de kernel. Quando o Linux fizer um update à vossa versão de kernel, vão ter de instalar de novo o package para a nova versão de kernel, mas isso é simples e instantâneo.
Para correrem temporariamente o perf vão ter que alterar a flag perf_event_paranoid para dar permissão de acesso do perf aos eventos do Kernel:
>
echo -1 | sudo tee /proc/sys/kernel/perf_event_paranoidPara fazerem o profilling do vosso executável no perf basta fazerem o seguinte, que ele gera um ficheiro .data com os dados do profilling em binário:
>
perf record -F99 --call-graph dwarf <path para o executável>Depois para verem o relatório iterativo dos dados gerados basta fazerem:
>
perf reportNeste podem usar as teclas up/down das setas, para escolher a vossa função que está ordenada de-crescentemente da mais intensiva em CPU para a menos intensiva. Podem usar a tecla “+” para expandir os nós e depois podem usar a tecla “A” para ver as anotações para uma determinada função onde vai aparecer o assembly dessa função anotado com a percentagem do tempo gasta em cada bloco de instruções assembly e em que vão ver a que linhas do vosso código corresponde cada bloco de instruções assembly.
O Perf tem muitos comandos e por exemplo o “
stat” dá coisas como o IPC – Instruction Per Clock Cycle, que é a média.
Podem depois também instalar coisas como o
flamegraph que usa o Perf e o DTrace para terem uma representação mais visual em formato SVG dos hot spots do vosso código.
Para saberem mais sobre Perf vejam:
perf ExamplesSee also the lecture.
https://www.brendangregg.com/perf.htmlperf: Linux profiling with performance countershttps://perf.wiki.kernel.org/index.php/Main_PageSystems Performance Enterprise and the Cloud 2nd Edby Brendan Gregg
The Rust Performance Bookhttps://nnethercote.github.io/perf-book/title-page.htmlPara saberem a memória máxima ocupada por um processo podem usar o:
>
/usr/bin/time -v <path para o executável>Ter em atenção que este time é diferente do time da bash pelo que tem de colocar o full path para /usr/bin .
Para saberem mais sobre Rust ou Linux com muitas tips vejam:
How to Learn Modern Rust. https://github.com/joaocarvalhoopen/How_to_learn_modern_RustOu
How to learn modern Linuxhttps://github.com/joaocarvalhoopen/How_to_learn_modern_LinuxObrigado,
Cumprimentos e votos de um bom fim-de-semana a todos,
João