Compétence Optimisation des Performances
Analyse et optimisation systématiques des performances pour l'outil RTK CLI, en se concentrant sur le temps de démarrage (<10ms), l'utilisation mémoire (<5MB) et les économies de tokens (60-90%).
Quand l'utiliser
- Déclenchement automatique : Après des changements de filtres, des modifications regex ou des ajouts de dépendances
- Invocation manuelle : En cas de dégradation soupçonnée des performances ou avant une release
- Proactif : Après tout changement de code susceptible d'impacter le temps de démarrage ou la mémoire
Objectifs de Performance RTK
| Métrique | Objectif | Méthode de Vérification | Seuil d'Échec |
|---|---|---|---|
| Temps de démarrage | <10ms | hyperfine 'rtk <cmd>' |
>15ms = bloquant |
| Utilisation mémoire | <5MB resident | /usr/bin/time -l rtk <cmd> (macOS) |
>7MB = bloquant |
| Économies de tokens | 60-90% | Tests avec count_tokens() |
<60% = bloquant |
| Taille binaire | <5MB strippé | ls -lh target/release/rtk |
>8MB = à investiguer |
Flux de Travail d'Analyse des Performances
1. Établir une Référence
Avant toute modification, capturez les performances actuelles :
# Référence temps de démarrage
hyperfine 'rtk git status' --warmup 3 --export-json /tmp/baseline_startup.json
# Référence utilisation mémoire (macOS)
/usr/bin/time -l rtk git status 2>&1 | grep "maximum resident set size" > /tmp/baseline_memory.txt
# Référence utilisation mémoire (Linux)
/usr/bin/time -v rtk git status 2>&1 | grep "Maximum resident set size" > /tmp/baseline_memory.txt
# Référence taille binaire
ls -lh target/release/rtk | tee /tmp/baseline_binary_size.txt
2. Apporter les Changements
Implémentez l'optimisation ou les changements de fonctionnalité.
3. Reconstruire et Mesurer
# Reconstruire avec optimisations
cargo build --release
# Mesurer le temps de démarrage
hyperfine 'target/release/rtk git status' --warmup 3 --export-json /tmp/after_startup.json
# Mesurer l'utilisation mémoire
/usr/bin/time -l target/release/rtk git status 2>&1 | grep "maximum resident set size" > /tmp/after_memory.txt
# Vérifier la taille binaire
ls -lh target/release/rtk | tee /tmp/after_binary_size.txt
4. Comparer les Résultats
# Comparaison temps de démarrage
hyperfine 'rtk git status' 'target/release/rtk git status' --warmup 3
# Exemple de sortie :
# Benchmark 1: rtk git status
# Time (mean ± σ): 6.2 ms ± 0.3 ms [User: 4.1 ms, System: 1.8 ms]
# Benchmark 2: target/release/rtk git status
# Time (mean ± σ): 7.8 ms ± 0.4 ms [User: 5.2 ms, System: 2.1 ms]
#
# Summary
# 'rtk git status' ran 1.26 times faster than 'target/release/rtk git status'
# Comparaison mémoire
diff /tmp/baseline_memory.txt /tmp/after_memory.txt
# Comparaison taille binaire
diff /tmp/baseline_binary_size.txt /tmp/after_binary_size.txt
5. Identifier les Régressions
Régression temps de démarrage (>15% d'augmentation ou >2ms absolu) :
# Profiler avec flamegraph
cargo install flamegraph
cargo flamegraph -- target/release/rtk git status
# Ouvrir flamegraph.svg
open flamegraph.svg
# Chercher :
# - Compilation de regex (doit être dans l'init lazy_static)
# - Allocations excessives
# - I/O fichier au démarrage (doit être zéro)
Régression mémoire (>20% d'augmentation ou >1MB absolu) :
# Profiler les allocations (nécessite nightly)
cargo +nightly build --release -Z build-std
RUSTFLAGS="-C link-arg=-fuse-ld=lld" cargo +nightly build --release
# Utiliser DHAT pour le profiling heap
cargo install dhat
# Ajouter à main.rs :
# #[global_allocator]
# static ALLOC: dhat::Alloc = dhat::Alloc;
Régression économies de tokens (<60% d'économies) :
# Exécuter les tests de précision des tokens
cargo test test_token_savings
# Exemple de sortie d'échec :
# Git log filter: expected ≥60% savings, got 52.3%
# Correction : Améliorer la logique de condensation des filtres
Problèmes de Performance Courants
Problème 1 : Recompilation de Regex
Symptôme : Temps de démarrage >20ms, flamegraph montre une compilation regex dans le chemin critique
Détection :
# Flamegraph montre les appels Regex::new() lors de l'exécution
cargo flamegraph -- target/release/rtk git log -10
# Chercher "regex::Regex::new" en dehors des sections lazy_static
Correction :
// ❌ FAUX : Recompilé à chaque appel
fn filter_line(line: &str) -> Option<&str> {
let re = Regex::new(r"pattern").unwrap(); // RECOMPILÉ !
re.find(line).map(|m| m.as_str())
}
// ✅ CORRECT : Compilé une fois avec lazy_static
use lazy_static::lazy_static;
lazy_static! {
static ref LINE_PATTERN: Regex = Regex::new(r"pattern").unwrap();
}
fn filter_line(line: &str) -> Option<&str> {
LINE_PATTERN.find(line).map(|m| m.as_str())
}
Problème 2 : Allocations Excessives
Symptôme : Utilisation mémoire >5MB, nombreuses petites allocations dans flamegraph
Détection :
# Profiling heap DHAT
cargo +nightly build --release
valgrind --tool=dhat target/release/rtk git status
Correction :
// ❌ FAUX : Alloue Vec pour chaque ligne
fn filter_lines(input: &str) -> String {
input.lines()
.map(|line| line.to_string()) // Alloue String
.collect::<Vec<_>>()
.join("\n")
}
// ✅ CORRECT : Emprunte des slices, allocation unique
fn filter_lines(input: &str) -> String {
input.lines()
.collect::<Vec<_>>() // Vec de &str (pas d'allocation String)
.join("\n")
}
Problème 3 : I/O au Démarrage
Symptôme : Temps de démarrage très variable (5ms à 50ms), flamegraph montre des lectures de fichier
Détection :
# strace sur Linux
strace -c target/release/rtk git status 2>&1 | grep -E "open|read"
# dtrace sur macOS (nécessite SIP désactivé)
sudo dtrace -n 'syscall::open*:entry { @[execname] = count(); }' &
target/release/rtk git status
sudo pkill dtrace
Correction :
// ❌ FAUX : I/O fichier au démarrage
fn main() {
let config = load_config().unwrap(); // Lit ~/.config/rtk/config.toml
// ...
}
// ✅ CORRECT : Chargement lazy de la configuration (seulement si nécessaire)
fn main() {
// Pas d'I/O au démarrage
// Configuration chargée à la demande lors du premier accès
}
Problème 4 : Surcharge de Dépendances
Symptôme : Taille binaire >5MB, nombreuses dépendances inutilisées dans Cargo.toml
Détection :
# Analyser l'arbre de dépendances
cargo tree
# Trouver les dépendances lourdes
cargo install cargo-bloat
cargo bloat --release --crates
# Exemple de sortie :
# File .text Size Crate
# 0.5% 2.1% 42.3KB regex
# 0.4% 1.8% 36.1KB clap
# ...
Correction :
# ❌ FAUX : Ensemble complet de features (surcharge)
[dependencies]
clap = { version = "4", features = ["derive", "color", "suggestions"] }
# ✅ CORRECT : Features minimales
[dependencies]
clap = { version = "4", features = ["derive"], default-features = false }
Techniques d'Optimisation
Technique 1 : Initialisation Lazy Static
Cas d'usage : Patterns regex, configuration statique, allocations uniques
Implémentation :
use lazy_static::lazy_static;
use regex::Regex;
lazy_static! {
static ref COMMIT_HASH: Regex = Regex::new(r"[0-9a-f]{7,40}").unwrap();
static ref AUTHOR_LINE: Regex = Regex::new(r"^Author: (.+)$").unwrap();
static ref DATE_LINE: Regex = Regex::new(r"^Date: (.+)$").unwrap();
}
// Tous les regex compilés une fois au démarrage, réutilisés indéfiniment
Impact : ~5-10ms économisés par pattern regex (s'il était compilé à la runtime)
Technique 2 : Traitement de Strings Sans Copies
Cas d'usage : Filtrer la sortie sans allouer d'intermédiaires Strings
Implémentation :
// ❌ FAUX : Alloue String pour chaque ligne
fn filter(input: &str) -> String {
input.lines()
.filter(|line| !line.is_empty())
.map(|line| line.to_string()) // Alloue !
.collect::<Vec<_>>()
.join("\n")
}
// ✅ CORRECT : Emprunte des slices, allocation finale unique
fn filter(input: &str) -> String {
input.lines()
.filter(|line| !line.is_empty())
.collect::<Vec<_>>() // Vec<&str> (pas d'alloc String)
.join("\n") // Allocation unique pour le résultat joint
}
Impact : ~1-2MB mémoire économisée, ~1-2ms démarrage économisé
Technique 3 : Dépendances Minimales
Cas d'usage : Réduire la taille binaire et le temps de compilation
Implémentation :
# Inclure uniquement les features réellement utilisées
[dependencies]
clap = { version = "4", features = ["derive"], default-features = false }
serde = { version = "1", features = ["derive"], default-features = false }
# Éviter les dépendances lourdes
# ❌ À éviter : tokio (ajoute 5-10ms overhead de démarrage)
# ❌ À éviter : regex complet (utiliser regex-lite si possible)
# ✅ Utiliser : anyhow (gestion d'erreurs légère)
# ✅ Utiliser : lazy_static (zéro overhead runtime)
Impact : ~1-2MB réduction taille binaire, ~2-5ms démarrage économisé
Checklist de Test de Performance
Avant de valider les changements de filtres :
Temps de Démarrage
- [ ] Benchmark avec
hyperfine 'rtk <cmd>' --warmup 3 - [ ] Vérifier <10ms temps moyen
- [ ] Vérifier variance (σ) petite (<1ms)
- [ ] Comparer à la référence (régression <2ms)
Utilisation Mémoire
- [ ] Profiler avec
/usr/bin/time -l rtk <cmd> - [ ] Vérifier <5MB resident set size
- [ ] Comparer à la référence (régression <1MB)
Économies de Tokens
- [ ] Exécuter
cargo test test_token_savings - [ ] Vérifier tous les filtres atteignent ≥60% d'économies
- [ ] Vérifier fixtures réelles utilisées (non synthétiques)
Taille Binaire
- [ ] Vérifier
ls -lh target/release/rtk - [ ] Vérifier <5MB binaire strippé
- [ ] Exécuter
cargo bloat --release --cratessi >5MB
Suivi Continu des Performances
Hook Pre-Commit
Ajouter à .claude/hooks/bash/pre-commit-performance.sh :
#!/bin/bash
# Vérification régression performance avant commit
echo "🚀 Exécution des vérifications de performance..."
# Benchmark temps de démarrage
CURRENT_TIME=$(hyperfine 'rtk git status' --warmup 3 --export-json /tmp/perf.json 2>&1 | grep "Time (mean" | awk '{print $4}')
# Extraire valeur numérique (retirer "ms")
CURRENT_MS=$(echo $CURRENT_TIME | sed 's/ms//')
# Vérifier si > 10ms
if (( $(echo "$CURRENT_MS > 10" | bc -l) )); then
echo "❌ Régression temps de démarrage : ${CURRENT_MS}ms (objectif : <10ms)"
exit 1
fi
# Vérifier taille binaire
BINARY_SIZE=$(ls -l target/release/rtk | awk '{print $5}')
MAX_SIZE=$((5 * 1024 * 1024)) # 5MB
if [ $BINARY_SIZE -gt $MAX_SIZE ]; then
echo "❌ Régression taille binaire : $(($BINARY_SIZE / 1024 / 1024))MB (objectif : <5MB)"
exit 1
fi
echo "✅ Vérifications de performance réussies"
Intégration CI/CD
Ajouter à .github/workflows/ci.yml :
- name: Performance Regression Check
run: |
cargo build --release
cargo install hyperfine
# Benchmark temps de démarrage
hyperfine 'target/release/rtk git status' --warmup 3 --max-runs 10
# Vérifier taille binaire
BINARY_SIZE=$(ls -l target/release/rtk | awk '{print $5}')
MAX_SIZE=$((5 * 1024 * 1024))
if [ $BINARY_SIZE -gt $MAX_SIZE ]; then
echo "Binary too large: $(($BINARY_SIZE / 1024 / 1024))MB"
exit 1
fi
Priorités d'Optimisation des Performances
Ordre de priorité (impact décroissant) :
- 🔴 Lazy static regex (5-10ms par pattern s'il était compilé à la runtime)
- 🔴 Supprimer I/O démarrage (10-50ms pour lectures fichiers config)
- 🟡 Traitement sans copies (1-2MB mémoire, 1-2ms démarrage)
- 🟡 Dépendances minimales (1-2MB binaire, 2-5ms démarrage)
- 🟢 Optimisation algorithme (varie, profiler d'abord)
En cas de doute : Profiler d'abord avec flamegraph, puis optimiser le chemin le plus chaud.
Référence des Outils
| Outil | Objectif | Commande |
|---|---|---|
| hyperfine | Benchmark temps de démarrage | hyperfine 'rtk <cmd>' --warmup 3 |
| time | Utilisation mémoire (macOS) | /usr/bin/time -l rtk <cmd> |
| time | Utilisation mémoire (Linux) | /usr/bin/time -v rtk <cmd> |
| flamegraph | Profiling CPU | cargo flamegraph -- rtk <cmd> |
| cargo bloat | Analyse taille binaire | cargo bloat --release --crates |
| cargo tree | Arbre de dépendances | cargo tree |
| DHAT | Profiling heap | cargo +nightly build && valgrind --tool=dhat |
| strace | Traçage appels système (Linux) | strace -c target/release/rtk <cmd> |
| dtrace | Traçage appels système (macOS) | sudo dtrace -n 'syscall::open*:entry' |
Installer les outils :
# macOS
brew install hyperfine
# Linux / cross-platform via cargo
cargo install hyperfine
cargo install flamegraph
cargo install cargo-bloat