Como fazer benchmark de c贸digo Rust com Iai


O que 茅 Benchmarking?

Benchmarking 茅 a pr谩tica de testar o desempenho do seu c贸digo para ver qu茫o r谩pido (lat锚ncia) ou quanto (throughput) trabalho ele pode executar. Este passo frequentemente negligenciado no desenvolvimento de software 茅 crucial para criar e manter um c贸digo r谩pido e perform谩tico. O benchmarking fornece as m茅tricas necess谩rias para que os desenvolvedores compreendam o desempenho do seu c贸digo sob v谩rias cargas de trabalho e condi莽玫es. Pelas mesmas raz玫es que voc锚 escreve testes unit谩rios e de integra莽茫o para evitar regress玫es de funcionalidades, voc锚 deve escrever benchmarks para evitar regress玫es de desempenho. Bugs de desempenho s茫o bugs!

Escreva FizzBuzz em Rust

Para escrevermos testes de desempenho, precisamos de algum c贸digo-fonte para testar. Para come莽ar, vamos escrever um programa muito simples, FizzBuzz.

As regras para o FizzBuzz s茫o as seguintes:

Escreva um programa que imprima os inteiros de 1 a 100 (inclusive):

  • Para m煤ltiplos de tr锚s, imprima Fizz
  • Para m煤ltiplos de cinco, imprima Buzz
  • Para m煤ltiplos de tr锚s e cinco, imprima FizzBuzz
  • Para todos os outros, imprima o n煤mero

Existem muitas maneiras de escrever o FizzBuzz. Ent茫o vamos seguir com o meu favorito:

fn main() {
for i in 1..=100 {
match (i % 3, i % 5) {
(0, 0) => println!("FizzBuzz"),
(0, _) => println!("Fizz"),
(_, 0) => println!("Buzz"),
(_, _) => println!("{i}"),
}
}
}
  • Crie uma fun莽茫o main
  • Itere de 1 a 100 inclusivamente.
  • Para cada n煤mero, calcule o m贸dulo (resto depois da divis茫o) para ambos 3 e 5.
  • Fa莽a correspond锚ncia de padr玫es nos dois restos. Se o resto 茅 0, ent茫o o n煤mero 茅 m煤ltiplo do fator dado.
  • Se o resto 茅 0 para ambos 3 e 5 ent茫o imprima FizzBuzz.
  • Se o resto 茅 0 apenas para 3 ent茫o imprima Fizz.
  • Se o resto 茅 0 apenas para 5 ent茫o imprima Buzz.
  • Caso contr谩rio, apenas imprima o n煤mero.

Siga Passo a Passo

Para acompanhar este tutorial passo a passo, voc锚 precisa instalar Rust.

馃惏 O c贸digo fonte para esta postagem est谩 dispon铆vel no GitHub

Com Rust instalado, voc锚 pode ent茫o abrir uma janela de terminal e digitar: cargo init game

Em seguida, navegue para o diret贸rio game rec茅m-criado.

game
鈹溾攢鈹 Cargo.toml
鈹斺攢鈹 src
鈹斺攢鈹 main.rs

Voc锚 ver谩 um diret贸rio chamado src com um arquivo chamado main.rs:

fn main() {
println!("Hello, world!");
}

Substitua o conte煤do dele pela implementa莽茫o FizzBuzz acima. Depois, execute cargo run. A sa铆da deve ser parecida com:

$ cargo run
Compiling playground v0.0.1 (/home/bencher)
Finished dev [unoptimized + debuginfo] target(s) in 0.44s
Running `target/debug/game`
1
2
Fizz
4
Buzz
Fizz
7
8
Fizz
Buzz
11
Fizz
13
14
FizzBuzz
...
97
98
Fizz
Buzz

馃惏 Boom! Voc锚 est谩 arrasando na entrevista de programa莽茫o!

Um novo arquivo Cargo.lock deve ter sido gerado:

game
鈹溾攢鈹 Cargo.lock
鈹溾攢鈹 Cargo.toml
鈹斺攢鈹 src
鈹斺攢鈹 main.rs

Antes de prosseguir, 茅 importante discutir as diferen莽as entre micro-benchmarking e macro-benchmarking.

Micro-Benchmarking vs Macro-Benchmarking

Existem duas categorias importantes de benchmarks de software: micro-benchmarks e macro-benchmarks. Os micro-benchmarks operam em um n铆vel semelhante aos testes unit谩rios. Por exemplo, um benchmark para uma fun莽茫o que determina Fizz, Buzz, ou FizzBuzz para um n煤mero individual seria um micro-benchmark. Os macro-benchmarks operam em um n铆vel semelhante aos testes de integra莽茫o. Por exemplo, um benchmark para uma fun莽茫o que executa o jogo inteiro de FizzBuzz, de 1 a 100, seria um macro-benchmark.

Em geral, 茅 melhor testar no menor n铆vel de abstra莽茫o poss铆vel. No caso dos benchmarks, isso os torna mais f谩ceis de manter, e ajuda a reduzir a quantidade de ru铆do nas medi莽玫es. No entanto, assim como ter alguns testes de ponta a ponta pode ser muito 煤til para verificar se todo o sistema se junta conforme esperado, ter macro-benchmarks pode ser muito 煤til para garantir que os caminhos cr铆ticos atrav茅s do seu software permane莽am com bom desempenho.

Benchmarking em Rust

As tr锚s op莽玫es populares para benchmarking em Rust s茫o: libtest bench, Criterion, e Iai.

libtest 茅 o framework embutido de testes unit谩rios e benchmarking do Rust. Embora fa莽a parte da biblioteca padr茫o do Rust, o libtest bench ainda 茅 considerado inst谩vel, portanto, est谩 dispon铆vel apenas em vers玫es do compilador nightly. Para funcionar no compilador Rust est谩vel, um harness de benchmarking separado precisa ser usado. Nenhum dos dois est谩 sendo desenvolvido ativamente, no entanto.

O harness de benchmarking mais popular dentro do ecossistema Rust 茅 o Criterion. Ele funciona tanto em vers玫es est谩veis quanto em vers玫es nightly do compilador Rust, e se tornou o padr茫o de facto dentro da comunidade Rust. O Criterion tamb茅m possui muito mais recursos em compara莽茫o com o libtest bench.

Uma alternativa experimental ao Criterion 茅 o Iai, do mesmo criador do Criterion. No entanto, ele usa contagem de instru莽玫es em vez de tempo real: instru莽玫es da CPU, acessos L1, acessos L2 e acessos 脿 RAM. Isso permite benchmarking de tiro 煤nico, uma vez que essas m茅tricas devem permanecer quase id锚nticas entre as execu莽玫es.

Todos tr锚s s茫o suportados pelo Bencher. Ent茫o por que escolher o Iai? O Iai usa contagens de instru莽茫o em vez do tempo real. Isso o torna ideal para benchmark cont铆nuo, ou seja, benchmarking em CI. Eu sugeriria usar o Iai para benchmark cont铆nuo, especialmente se voc锚 est谩 usando runners compartilhados. 脡 importante entender que o Iai s贸 mede uma aproxima莽茫o do que voc锚 realmente se importa. Ir de 1.000 instru莽玫es para 2.000 instru莽玫es dobra a lat锚ncia do seu aplicativo? Talvez sim, talvez n茫o. Por isso, pode ser 煤til tamb茅m executar benchmarks baseados no tempo real em paralelo com benchmarks baseados em contagens de instru莽茫o.

馃惏 O Iai n茫o tem atualiza莽茫o h谩 mais de 3 anos. Ent茫o voc锚 pode considerar usar o Iai-Callgrind em vez dele.

Instale o Valgrind

O Iai usa uma ferramenta chamada Valgrind para coletar contagens de instru莽茫o. O Valgrind d谩 suporte ao Linux, Solaris, FreeBSD e MacOS. No entanto, o suporte ao MacOS est谩 limitado aos processadores x86_64, j谩 que os processadores arm64 (M1, M2, etc) ainda n茫o s茫o suportados.

No Debian use: sudo apt-get install valgrind

No MacOS (x86_64/Intel chip only): brew install valgrind

Refatorando o FizzBuzz

Para testar nosso aplicativo FizzBuzz, precisamos desacoplar nossa l贸gica da fun莽茫o main do programa. Os bancos de teste n茫o conseguem benchmarkar a fun莽茫o main. Para fazer isso, precisamos fazer algumas altera莽玫es.

Em src, crie um novo arquivo chamado lib.rs:

game
鈹溾攢鈹 Cargo.lock
鈹溾攢鈹 Cargo.toml
鈹斺攢鈹 src
鈹斺攢鈹 lib.rs
鈹斺攢鈹 main.rs

Adicione o seguinte c贸digo em lib.rs:

pub fn play_game(n: u32, print: bool) {
let result = fizz_buzz(n);
if print {
println!("{result}");
}
}
pub fn fizz_buzz(n: u32) -> String {
match (n % 3, n % 5) {
(0, 0) => "FizzBuzz".to_string(),
(0, _) => "Fizz".to_string(),
(_, 0) => "Buzz".to_string(),
(_, _) => n.to_string(),
}
}
  • play_game: Recebe um n煤mero inteiro n茫o assinado n, chama fizz_buzz com aquele n煤mero, e se print for true imprime o resultado.
  • fizz_buzz: Recebe um n煤mero inteiro n茫o assinado n e executa a l贸gica real de Fizz, Buzz, FizzBuzz, ou n煤mero retornando o resultado como uma string.

Em seguida, atualize main.rs para ter esta apar锚ncia:

use game::play_game;
fn main() {
for i in 1..=100 {
play_game(i, true);
}
}
  • game::play_game: Importa play_game do pacote game que acabamos de criar com lib.rs.
  • main: O ponto de entrada principal em nosso programa que percorre os n煤meros de 1 a 100 inclusos e chama play_game para cada n煤mero, com print definido como true.

Fazendo benchmark do FizzBuzz

Para fazer o benchmark do nosso c贸digo, precisamos criar um diret贸rio benches e adicionar um arquivo para conter nossos benchmarks, play_game.rs:

game
鈹溾攢鈹 Cargo.lock
鈹溾攢鈹 Cargo.toml
鈹斺攢鈹 benches
鈹斺攢鈹 play_game.rs
鈹斺攢鈹 src
鈹斺攢鈹 lib.rs
鈹斺攢鈹 main.rs

Dentro de play_game.rs, adicione o seguinte c贸digo:

use game::play_game;
fn bench_play_game() {
iai::black_box(for i in 1..=100 {
play_game(i, false)
});
}
iai::main!(bench_play_game);
  • Importe a fun莽茫o play_game do nosso pacote game.
  • Crie uma fun莽茫o chamada bench_play_game.
  • Execute nosso macro-benchmark dentro de uma 鈥渃aixa preta鈥 para que o compilador n茫o otimize nosso c贸digo.
  • Itere de 1 a 100 inclusivamente.
  • Para cada n煤mero, chame play_game, com print definido como false.

Agora, precisamos configurar o pacote game para executar nossos benchmarks.

Adicione o seguinte na parte inferior do seu arquivo Cargo.toml:

[dev-dependencies]
iai = "0.1"
[[bench]]
name = "play_game"
harness = false
  • iai: Adicione iai como uma depend锚ncia de desenvolvimento, j谩 que estamos usando apenas para testes de desempenho.
  • bench: Registre play_game como um benchmark e defina harness como false, j谩 que estaremos usando o Iai como nossa estrutura de benchmark.

Agora estamos prontos para fazer o benchmark do nosso c贸digo, execute cargo bench:

$ cargo bench
Compiling iai v0.1.1
Compiling game v0.1.0 (/home/bencher)
Finished bench [optimized] target(s) in 2.55s
Running unittests src/lib.rs (target/release/deps/game-9b1b504669ca4b29)
running 0 tests
test result: ok. 0 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.00s
Running unittests src/main.rs (target/release/deps/game-8d61ca5a97299729)
running 0 tests
test result: ok. 0 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.00s
Running benches/play_game.rs (target/release/deps/play_game-6896309faf45cd96)
bench_play_game
Instructions: 34370
L1 Accesses: 50373
L2 Accesses: 9
RAM Accesses: 35
Estimated Cycles: 51643

馃惏 Vamos aumentar o ritmo! Temos nossas primeiras m茅tricas de benchmark!

Finalmente, podemos descansar nossas cabe莽as cansadas de desenvolvedores鈥 Brincadeira, nossos usu谩rios querem um novo recurso!

Escreva FizzBuzzFibonacci em Rust

Nossos Indicadores Chave de Desempenho (KPIs) est茫o em baixa, ent茫o nosso Gerente de Produto (PM) quer que adicionemos um novo recurso. Ap贸s muitas discuss玫es e v谩rias entrevistas com usu谩rios, decidiu-se que o bom e velho FizzBuzz n茫o 茅 suficiente. As crian莽as de hoje querem um novo jogo, FizzBuzzFibonacci.

As regras para FizzBuzzFibonacci s茫o as seguintes:

Escreva um programa que imprime os n煤meros inteiros de 1 a 100 (inclusive):

  • Para m煤ltiplos de tr锚s, imprima Fizz
  • Para m煤ltiplos de cinco, imprima Buzz
  • Para m煤ltiplos de ambos tr锚s e cinco, imprima FizzBuzz
  • Para n煤meros que fazem parte da sequ锚ncia de Fibonacci, apenas imprima Fibonacci
  • Para todos os outros, imprima o n煤mero

A Sequ锚ncia de Fibonacci 茅 uma s茅rie na qual cada n煤mero 茅 a soma dos dois n煤meros precedentes. Por exemplo, come莽ando com 0 e 1 o pr贸ximo n煤mero na sequ锚ncia de Fibonacci seria 1. Seguido por: 2, 3, 5, 8 e assim por diante. N煤meros que fazem parte da Sequ锚ncia de Fibonacci s茫o conhecidos como n煤meros de Fibonacci. Ent茫o, teremos que escrever uma fun莽茫o que detecte n煤meros de Fibonacci.

Existem muitas maneiras de escrever a sequ锚ncia de Fibonacci e, da mesma forma, muitas maneiras de detectar um n煤mero de Fibonacci. Ent茫o, vamos com a minha favorita:

fn is_fibonacci_number(n: u32) -> bool {
for i in 0..=n {
let (mut previous, mut current) = (0, 1);
while current < i {
let next = previous + current;
previous = current;
current = next;
}
if current == n {
return true;
}
}
false
}
  • Crie uma fun莽茫o chamada is_fibonacci_number que recebe um n煤mero inteiro sem sinal e retorna um booleano.
  • Itere para todos os n煤meros de 0 ao nosso n煤mero espec铆fico n inclusive.
  • Inicialize nossa sequ锚ncia Fibonacci come莽ando com 0 e 1 como os n煤meros anterior e atual respectivamente.
  • Itere enquanto o n煤mero atual for menor que a itera莽茫o atual i.
  • Adicione o n煤mero atual e anterior para obter o n煤mero pr贸ximo.
  • Atualize o n煤mero anterior para o n煤mero atual.
  • Atualize o n煤mero atual para o n煤mero pr贸ximo.
  • Uma vez que atual for maior ou igual ao n煤mero especifico n, n贸s sairemos do loop.
  • Verifique se o n煤mero atual 茅 igual ao n煤mero especificado n e, se for, retorne true.
  • Caso contr谩rio, retorne false.

Agora precisaremos atualizar nossa fun莽茫o fizz_buzz:

pub fn fizz_buzz_fibonacci(n: u32) -> String {
if is_fibonacci_number(n) {
"Fibonacci".to_string()
} else {
match (n % 3, n % 5) {
(0, 0) => "FizzBuzz".to_string(),
(0, _) => "Fizz".to_string(),
(_, 0) => "Buzz".to_string(),
(_, _) => n.to_string(),
}
}
}
  • Renomeie a fun莽茫o fizz_buzz para fizz_buzz_fibonacci para torn谩-la mais descritiva.
  • Chame nossa fun莽茫o auxiliar is_fibonacci_number.
  • Se o resultado de is_fibonacci_number for true retorne Fibonacci.
  • Se o resultado de is_fibonacci_number for false, execute a mesma l贸gica Fizz, Buzz, FizzBuzz, ou n煤mero retornando o resultado.

Como renomeamos fizz_buzz para fizz_buzz_fibonacci, tamb茅m precisamos atualizar nossa fun莽茫o play_game:

pub fn play_game(n: u32, print: bool) {
let result = fizz_buzz_fibonacci(n);
if print {
println!("{result}");
}
}

Ambas as fun莽玫es main e bench_play_game podem permanecer exatamente as mesmas.

Fazendo benchmark do FizzBuzzFibonacci

Agora, podemos executar novamente nosso benchmark:

$ cargo bench
Compiling game v0.1.0 (/home/bencher)
Finished bench [optimized] target(s) in 2.20s
Running unittests src/lib.rs (target/release/deps/game-9b1b504669ca4b29)
running 0 tests
test result: ok. 0 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.00s
Running unittests src/main.rs (target/release/deps/game-8d61ca5a97299729)
running 0 tests
test result: ok. 0 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.00s
Running benches/play_game.rs (target/release/deps/play_game-6896309faf45cd96)
bench_play_game
Instructions: 304598 (+786.2322%)
L1 Accesses: 320024 (+535.3086%)
L2 Accesses: 8 (-11.11111%)
RAM Accesses: 42 (+20.00000%)
Estimated Cycles: 321534 (+522.6091%)

Ah, legal! O Iai nos diz que a diferen莽a entre os ciclos estimados dos nossos jogos FizzBuzz e FizzBuzzFibonacci 茅 de +522.6091%. Seus n煤meros ser茫o um pouco diferentes dos meus. No entanto, a diferen莽a entre os dois jogos provavelmente est谩 na faixa de 5x. Isso me parece bom! Especialmente para adicionar um recurso t茫o sofisticado quanto Fibonacci ao nosso jogo. A garotada vai adorar!

Expandindo FizzBuzzFibonacci em Rust

Nosso jogo 茅 um sucesso! As crian莽as realmente adoram jogar FizzBuzzFibonacci. Tanto que a dire莽茫o quer uma sequ锚ncia. Mas vivemos em um mundo moderno, precisamos de Receita Anual Recorrente (ARR) e n茫o de compras 煤nicas! A nova vis茫o para o nosso jogo 茅 que ele seja aberto, sem mais viver entre os limites de 1 e 100 (mesmo que inclusivo). N茫o, estamos partindo para novas fronteiras!

As regras para o Open World FizzBuzzFibonacci s茫o as seguintes:

Escreva um programa que aceite qualquer n煤mero inteiro positivo e imprima:

  • Para m煤ltiplos de tr锚s, imprima Fizz
  • Para m煤ltiplos de cinco, imprima Buzz
  • Para m煤ltiplos de ambos tr锚s e cinco, imprima FizzBuzz
  • Para n煤meros que fazem parte da sequ锚ncia de Fibonacci, apenas imprima Fibonacci
  • Para todos os outros, imprima o n煤mero

Para que nosso jogo funcione para qualquer n煤mero, precisaremos aceitar um argumento de linha de comando. Atualize a fun莽茫o main para ficar assim:

fn main() {
let args: Vec<String> = std::env::args().collect();
let i = args
.get(1)
.map(|s| s.parse::<u32>())
.unwrap_or(Ok(15))
.unwrap_or(15);
play_game(i, true);
}
  • Colete todos os argumentos (args) passados para o nosso jogo a partir da linha de comando.
  • Pegue o primeiro argumento passado para o nosso jogo e analise-o como um inteiro n茫o assinado i.
  • Se a an谩lise falhar ou nenhum argumento for passado, use por padr茫o o nosso jogo com 15 como entrada.
  • Finalmente, jogue nosso jogo com o novo inteiro n茫o assinado i analisado.

Agora podemos jogar nosso jogo com qualquer n煤mero! Use cargo run seguido de -- para passar argumentos para o nosso jogo:

$ cargo run -- 9
Compiling playground v0.0.1 (/home/bencher)
Finished dev [unoptimized + debuginfo] target(s) in 0.44s
Running `target/debug/game 9`
Fizz
$ cargo run -- 10
Finished dev [unoptimized + debuginfo] target(s) in 0.03s
Running `target/debug/game 10`
Buzz
$ cargo run -- 13
Finished dev [unoptimized + debuginfo] target(s) in 0.04s
Running `target/debug/game 13`
Fibonacci

E se omitirmos ou fornecermos um n煤mero inv谩lido:

$ cargo run
Finished dev [unoptimized + debuginfo] target(s) in 0.03s
Running `target/debug/game`
FizzBuzz
$ cargo run -- bad
Finished dev [unoptimized + debuginfo] target(s) in 0.05s
Running `target/debug/game bad`
FizzBuzz

Nossa, que teste completo! O CI passou. Nossos chefes est茫o entusiasmados. Vamos lan莽谩-lo! 馃殌

O Fim


SpongeBob SquarePants Tr锚s Semanas Depois
Meme Est谩 Tudo Bem

馃惏 鈥 o fim da sua carreira talvez?


Brincadeira! Tudo est谩 pegando fogo! 馃敟

Bem, a princ铆pio, tudo parecia estar indo bem. Ent茫o, 脿s 02:07 da madrugada de s谩bado, meu pager disparou:

馃摕 Seu jogo est谩 pegando fogo! 馃敟

Ap贸s sair da cama 脿s pressas, tentei descobrir o que estava acontecendo. Eu tentei pesquisar nos logs, mas era dif铆cil porque tudo continuava travando. Finalmente, encontrei o problema. As crian莽as! Elas adoravam tanto nosso jogo que jogavam at茅 chegar a um milh茫o! Num lampejo de brilhantismo, adicionei dois novos benchmarks:

fn bench_play_game_100() {
iai::black_box(play_game(100, false));
}
fn bench_play_game_1_000_000() {
iai::black_box(play_game(1_000_000, false));
}
  • Um micro-benchmark bench_play_game_100 para jogar o jogo com o n煤mero cem (100)
  • Um micro-benchmark bench_play_game_1_000_000 para jogar o jogo com o n煤mero um milh茫o (1_000_000)

Quando eu executei, eu obtive isso:

$ cargo bench
Compiling game v0.1.0 (/home/bencher)
Finished bench [optimized] target(s) in 1.92s
Running unittests src/lib.rs (target/release/deps/game-9b1b504669ca4b29)
running 0 tests
test result: ok. 0 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.00s
Running unittests src/main.rs (target/release/deps/game-8d61ca5a97299729)
running 0 tests
test result: ok. 0 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.00s
Running benches/play_game.rs (target/release/deps/play_game-6896309faf45cd96)
bench_play_game
Instructions: 304598 (No change)
L1 Accesses: 320025 (+0.000312%)
L2 Accesses: 7 (-12.50000%)
RAM Accesses: 42 (No change)
Estimated Cycles: 321530 (-0.001244%)
bench_play_game_100
Instructions: 6194
L1 Accesses: 6290
L2 Accesses: 2
RAM Accesses: 11
Estimated Cycles: 6685

Espere por isso鈥 espere por isso鈥

bench_play_game_1_000_000
Instructions: 155108715
L1 Accesses: 155108811
L2 Accesses: 2
RAM Accesses: 11
Estimated Cycles: 155109206

O qu锚! 6,685 ciclos estimados x 1,000 deveria ser 6,685,000 ciclos estimados e n茫o 155,109,206 ciclos estimados 馃く Apesar de ter acertado o c贸digo da minha sequ锚ncia de Fibonacci funcionalmente, devo ter algum bug de desempenho em algum lugar.

Corrigindo FizzBuzzFibonacci em Rust

Vamos dar outra olhada naquela fun莽茫o is_fibonacci_number:

fn is_fibonacci_number(n: u32) -> bool {
for i in 0..=n {
let (mut previous, mut current) = (0, 1);
while current < i {
let next = previous + current;
previous = current;
current = next;
}
if current == n {
return true;
}
}
false
}

Agora que estou pensando em desempenho, percebo que tenho um loop extra desnecess谩rio. Podemos nos livrar completamente do loop for i in 0..=n {} e apenas comparar o valor atual com o n煤mero dado (n) 馃う

fn is_fibonacci_number(n: u32) -> bool {
let (mut previous, mut current) = (0, 1);
while current < n {
let next = previous + current;
previous = current;
current = next;
}
current == n
}
  • Atualize sua fun莽茫o is_fibonacci_number.
  • Inicialize nossa sequ锚ncia de Fibonacci come莽ando com 0 e 1 como os n煤meros anterior e atual, respectivamente.
  • Itere enquanto o n煤mero atual for menor que o n煤mero dado n.
  • Adicione o n煤mero anterior e atual para obter o n煤mero pr贸ximo.
  • Atualize o n煤mero anterior para o n煤mero atual.
  • Atualize o n煤mero atual para o n煤mero pr贸ximo.
  • Uma vez que atual seja maior ou igual ao n煤mero dado n, sairemos do loop.
  • Verifique se o n煤mero atual 茅 igual ao n煤mero dado n e retorne esse resultado.

Agora vamos reexecutar esses benchmarks e ver como nos sa铆mos:

$ cargo bench
Compiling game v0.1.0 (/home/bencher)
Finished bench [optimized] target(s) in 4.22s
Running unittests src/lib.rs (target/release/deps/game-9b1b504669ca4b29)
running 0 tests
test result: ok. 0 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.00s
Running unittests src/main.rs (target/release/deps/game-8d61ca5a97299729)
running 0 tests
test result: ok. 0 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.00s
Running benches/play_game.rs (target/release/deps/play_game-6896309faf45cd96)
bench_play_game
Instructions: 38313 (-87.42178%)
L1 Accesses: 53739 (-83.20787%)
L2 Accesses: 7 (No change)
RAM Accesses: 43 (+2.380952%)
Estimated Cycles: 55279 (-82.80751%)
bench_play_game_100
Instructions: 295 (-95.23733%)
L1 Accesses: 389 (-93.81558%)
L2 Accesses: 2 (No change)
RAM Accesses: 13 (+18.18182%)
Estimated Cycles: 854 (-87.22513%)
bench_play_game_1_000_000
Instructions: 391 (-99.99975%)
L1 Accesses: 485 (-99.99969%)
L2 Accesses: 2 (No change)
RAM Accesses: 13 (+18.18182%)
Estimated Cycles: 950 (-99.99939%)

Uau! Nosso benchmark bench_play_game voltou ao patamar que estava para o FizzBuzz original. Eu gostaria de poder lembrar exatamente qual era essa pontua莽茫o. J谩 se passaram tr锚s semanas. Meu hist贸rico do terminal n茫o vai t茫o longe. E o Iai s贸 compara com o resultado mais recente. Mas eu acho que est谩 perto!

O benchmark bench_play_game_100 est谩 quase 10 vezes menor, -87.22513%. E o benchmark bench_play_game_1_000_000 est谩 mais de 10.000 vezes mais baixo! 155,109,206 ciclos estimados para 950 ciclos estimados! Isso 茅 -99.99939%!

馃惏 Ei, pelo menos pegamos esse bug de desempenho antes que ele chegasse 脿 produ莽茫o鈥 ah, certo. Nem me lembrei鈥

Detecte Regress玫es de Desempenho em CI

Os executivos n茫o ficaram felizes com a enxurrada de cr铆ticas negativas que nosso jogo recebeu devido ao meu pequeno bug de desempenho. Eles me disseram para n茫o deixar isso acontecer de novo, e quando perguntei como, eles simplesmente me disseram para n茫o faz锚-lo novamente. Como eu deveria gerenciar isso鈥

Felizmente, encontrei esta incr铆vel ferramenta open source chamada Bencher. Existe um n铆vel gratuito super generoso, ent茫o posso apenas usar Bencher Cloud para meus projetos pessoais. E no trabalho, onde tudo precisa estar em nossa nuvem privada, comecei a usar Bencher Auto-Hospedado.

Bencher tem adaptadores integrados, por isso 茅 f谩cil de integrar ao CI. Ap贸s seguir o guia R谩pido In铆cio, consegui executar meus benchmarks e rastre谩-los com o Bencher.

$ bencher run --project game "cargo bench"
Finished bench [optimized] target(s) in 0.18s
Running unittests src/lib.rs (target/release/deps/game-9b1b504669ca4b29)
running 0 tests
test result: ok. 0 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.00s
Running unittests src/main.rs (target/release/deps/game-8d61ca5a97299729)
running 0 tests
test result: ok. 0 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.00s
Running benches/play_game.rs (target/release/deps/play_game-6896309faf45cd96)
bench_play_game
Instructions: 38331 (+0.046981%)
L1 Accesses: 53765 (+0.048382%)
L2 Accesses: 6 (-14.28571%)
RAM Accesses: 45 (+4.651163%)
Estimated Cycles: 55370 (+0.164619%)
bench_play_game_100
Instructions: 313 (+6.101695%)
L1 Accesses: 416 (+6.940874%)
L2 Accesses: 2 (No change)
RAM Accesses: 13 (No change)
Estimated Cycles: 881 (+3.161593%)
bench_play_game_1_000_000
Instructions: 409 (+4.603581%)
L1 Accesses: 512 (+5.567010%)
L2 Accesses: 2 (No change)
RAM Accesses: 13 (No change)
Estimated Cycles: 977 (+2.842105%)
Finished bench [optimized] target(s) in 0.07s
Running unittests src/lib.rs (target/release/deps/game-13f4bad779fbfde4)
running 0 tests
test result: ok. 0 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.00s
Bencher New Report:
...
View results:
- bench_play_game (Latency): https://bencher.dev/console/projects/game/perf?measures=52507e04-ffd9-4021-b141-7d4b9f1e9194&branches=3a27b3ce-225c-4076-af7c-75adbc34ef9a&testbeds=bc05ed88-74c1-430d-b96a-5394fdd18bb0&benchmarks=077449e5-5b45-4c00-bdfb-3a277413180d&start_time=1697224006000&end_time=1699816009000&upper_boundary=true
- bench_play_game_100 (Latency): https://bencher.dev/console/projects/game/perf?measures=52507e04-ffd9-4021-b141-7d4b9f1e9194&branches=3a27b3ce-225c-4076-af7c-75adbc34ef9a&testbeds=bc05ed88-74c1-430d-b96a-5394fdd18bb0&benchmarks=96508869-4fa2-44ac-8e60-b635b83a17b7&start_time=1697224006000&end_time=1699816009000&upper_boundary=true
- bench_play_game_1_000_000 (Latency): https://bencher.dev/console/projects/game/perf?measures=52507e04-ffd9-4021-b141-7d4b9f1e9194&branches=3a27b3ce-225c-4076-af7c-75adbc34ef9a&testbeds=bc05ed88-74c1-430d-b96a-5394fdd18bb0&benchmarks=ff014217-4570-42ea-8813-6ed0284500a4&start_time=1697224006000&end_time=1699816009000&upper_boundary=true

Usando este incr铆vel dispositivo de viagem no tempo que um simp谩tico coelho me deu, consegui voltar ao passado e reviver o que teria acontecido se estiv茅ssemos usando o Bencher desde o in铆cio. Voc锚 pode ver onde fizemos pela primeira vez o push da implementa莽茫o bugada de FizzBuzzFibonacci. Imediatamente recebi falhas no CI como um coment谩rio na minha solicita莽茫o de pull. No mesmo dia, corrigi o bug de desempenho, eliminando aquele loop extra e desnecess谩rio. Sem inc锚ndios. Apenas usu谩rios felizes.

Bencher: Benchmarking Cont铆nuo

馃惏 Bencher

Bencher 茅 um conjunto de ferramentas de benchmarking cont铆nuas. J谩 teve algum impacto de regress茫o de desempenho nos seus usu谩rios? Bencher poderia ter prevenido isso. Bencher permite que voc锚 detecte e previna regress玫es de desempenho antes que cheguem 脿 produ莽茫o.

  • Execute: Execute seus benchmarks localmente ou no CI usando suas ferramentas de benchmarking favoritas. O CLI bencher simplesmente envolve seu harness de benchmark existente e armazena seus resultados.
  • Rastreie: Acompanhe os resultados de seus benchmarks ao longo do tempo. Monitore, consulte e fa莽a gr谩ficos dos resultados usando o console web do Bencher baseado na branch de origem, testbed e medida.
  • Capture: Capture regress玫es de desempenho no CI. Bencher usa an谩lises personaliz谩veis e de 煤ltima gera莽茫o para detectar regress玫es de desempenho antes que elas cheguem 脿 produ莽茫o.

Pelos mesmos motivos que os testes de unidade s茫o executados no CI para prevenir regress玫es de funcionalidades, benchmarks deveriam ser executados no CI com o Bencher para prevenir regress玫es de desempenho. Bugs de desempenho s茫o bugs!

Comece a capturar regress玫es de desempenho no CI 鈥 experimente o Bencher Cloud gratuitamente.

馃 Este documento foi gerado automaticamente pelo OpenAI GPT-4. Pode n茫o ser preciso e pode conter erros. Se voc锚 encontrar algum erro, abra um problema no GitHub.