Como fazer benchmark de c贸digo Rust com Criterion


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 os tr锚s s茫o suportados pelo Bencher. Ent茫o, por que escolher o Criterion? Criterion 茅 o padr茫o de facto para realiza莽茫o de benchmark na comunidade Rust. Eu sugeriria o uso do Criterion para fazer benchmark da lat锚ncia do seu c贸digo. Ou seja, o Criterion 茅 贸timo para medir o tempo de rel贸gio.

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.

Benchmarking do FizzBuzz

Para fazer 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 criterion::{criterion_group, criterion_main, Criterion};
use game::play_game;
fn bench_play_game(c: &mut Criterion) {
c.bench_function("bench_play_game", |b| {
b.iter(|| {
std::hint::black_box(for i in 1..=100 {
play_game(i, false)
});
});
});
}
criterion_group!(
benches,
bench_play_game,
);
criterion_main!(benches);
  • Importe o executor de benchmark Criterion.
  • Importe a fun莽茫o play_game da nossa crate game.
  • Crie uma fun莽茫o chamada bench_play_game que recebe uma refer锚ncia mut谩vel para Criterion.
  • Use a inst芒ncia Criterion (c) para criar um benchmark chamado bench_play_game.
  • Em seguida, use o executor de benchmark (b) para executar nosso macro-benchmark v谩rias vezes.
  • 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 a crate game para executar nossos benchmarks.

Adicione o seguinte ao final do seu arquivo Cargo.toml:

[dev-dependencies]
criterion = "0.5"
[[bench]]
name = "play_game"
harness = false
  • criterion: Adicione criterion como uma depend锚ncia de desenvolvimento, pois estamos usando apenas para testes de performance.
  • bench: Registre play_game como um benchmark e defina harness como false, pois usaremos o Criterion como nossa ferramenta de benchmarking.

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

$ cargo bench
Compiling playground v0.0.1 (/home/bencher)
Finished bench [optimized] target(s) in 4.79s
Running unittests src/main.rs (target/release/deps/game-68f58c96f4025bd4)
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-043972c4132076a9)
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-e0857103eb02eb56)
bench_play_game time: [3.0020 碌s 3.0781 碌s 3.1730 碌s]
Found 12 outliers among 100 measurements (12.00%)
2 (2.00%) high mild
10 (10.00%) high severe

馃惏 Vamos agitar a centr铆fuga! Conseguimos nossas primeiras m茅tricas de benchmark!

Finalmente, podemos descansar nossas cansadas cabe莽as 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.

Benchmarking do FizzBuzzFibonacci

Agora podemos reexecutar nosso benchmark:

$ cargo bench
Compiling playground v0.0.1 (/home/bencher)
Finished bench [optimized] target(s) in 4.79s
Running unittests src/main.rs (target/release/deps/game-68f58c96f4025bd4)
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-043972c4132076a9)
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-e0857103eb02eb56)
bench_play_game time: [20.067 碌s 20.107 碌s 20.149 碌s]
change: [+557.22% +568.69% +577.93%] (p = 0.00 < 0.05)
Performance has regressed.
Found 6 outliers among 100 measurements (6.00%)
4 (4.00%) high mild
2 (2.00%) high severe

Oh, interessante! O Criterion nos informa que a diferen莽a entre o desempenho dos nossos jogos FizzBuzz e FizzBuzzFibonacci 茅 +568.69%. 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 por adicionar um recurso t茫o sofisticado quanto Fibonacci ao nosso jogo. As crian莽as v茫o 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(c: &mut Criterion) {
c.bench_function("bench_play_game_100", |b| {
b.iter(|| std::hint::black_box(play_game(100, false)));
});
}
fn bench_play_game_1_000_000(c: &mut Criterion) {
c.bench_function("bench_play_game_1_000_000", |b| {
b.iter(|| std::hint::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 executei, obtive isto:

$ cargo bench
Compiling playground v0.0.1 (/home/bencher)
Finished bench [optimized] target(s) in 4.79s
Running unittests src/main.rs (target/release/deps/game-68f58c96f4025bd4)
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-043972c4132076a9)
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-e0857103eb02eb56)
bench_play_game time: [20.024 碌s 20.058 碌s 20.096 碌s]
change: [-0.0801% +0.1431% +0.3734%] (p = 0.21 > 0.05)
No change in performance detected.
Found 17 outliers among 100 measurements (17.00%)
9 (9.00%) high mild
8 (8.00%) high severe
bench_play_game_100 time: [403.00 ns 403.57 ns 404.27 ns]
Found 13 outliers among 100 measurements (13.00%)
6 (6.00%) high mild
7 (7.00%) high severe

Aguarde鈥 aguarde鈥

bench_play_game_1_000_000
time: [9.5865 ms 9.5968 ms 9.6087 ms]
Found 16 outliers among 100 measurements (16.00%)
8 (8.00%) high mild
8 (8.00%) high severe

O qu锚! 403,57 ns x 1,000 deveria ser 403.570 ns e n茫o 9,596,800 ns (9.5968 ms x 1_000_000 ns/1 ms) 馃く Mesmo que eu tenha meu c贸digo da sequ锚ncia de Fibonacci funcionando corretamente, devo ter algum bug de desempenho nele.

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 benchmark e ver como nos sa铆mos:

$ cargo bench
Compiling playground v0.0.1 (/home/bencher)
Finished bench [optimized] target(s) in 4.79s
Running unittests src/main.rs (target/release/deps/game-68f58c96f4025bd4)
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-043972c4132076a9)
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-e0857103eb02eb56)
bench_play_game time: [3.1201 碌s 3.1772 碌s 3.2536 碌s]
change: [-84.469% -84.286% -84.016%] (p = 0.00 < 0.05)
Performance has improved.
Found 5 outliers among 100 measurements (5.00%)
1 (1.00%) high mild
4 (4.00%) high severe
bench_play_game_100 time: [24.460 ns 24.555 ns 24.650 ns]
change: [-93.976% -93.950% -93.927%] (p = 0.00 < 0.05)
Performance has improved.
bench_play_game_1_000_000
time: [30.260 ns 30.403 ns 30.564 ns]
change: [-100.000% -100.000% -100.000%] (p = 0.00 < 0.05)
Performance has improved.
Found 4 outliers among 100 measurements (4.00%)
1 (1.00%) high mild
3 (3.00%) high severe

Oh, uau! Nosso benchmark bench_play_game voltou para algo pr贸ximo de onde estava para o FizzBuzz original. Eu queria lembrar exatamente qual era esse score. Mas j谩 se passaram tr锚s semanas. Meu hist贸rico de terminal n茫o vai t茫o longe. E o Criterion s贸 compara com o resultado mais recente. Mas acho que est谩 perto!

O benchmark bench_play_game_100 est谩 quase 10x para baixo, -93.950%. E o benchmark bench_play_game_1_000_000 est谩 mais de 10,000x para baixo! 9,596,800 ns para 30.403 ns! N贸s at茅 maximizamos o medidor de mudan莽a do Criterion, que s贸 vai at茅 -100.000%!

馃惏 Ei, pelo menos pegamos este bug de desempenho antes de ir para a produ莽茫o鈥 ah, certo. Esque莽a鈥

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.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
Running unittests src/main.rs (target/release/deps/game-043972c4132076a9)
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-e0857103eb02eb56)
Gnuplot not found, using plotters backend
bench_play_game time: [3.0713 碌s 3.0902 碌s 3.1132 碌s]
Found 16 outliers among 100 measurements (16.00%)
3 (3.00%) high mild
13 (13.00%) high severe
bench_play_game_100 time: [23.938 ns 23.970 ns 24.009 ns]
Found 15 outliers among 100 measurements (15.00%)
5 (5.00%) high mild
10 (10.00%) high severe
bench_play_game_1_000_000
time: [30.004 ns 30.127 ns 30.279 ns]
Found 5 outliers among 100 measurements (5.00%)
1 (1.00%) high mild
4 (4.00%) high severe
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.