performance

Par rtk-ai · rtk

Optimisation des performances CLI – temps de démarrage, utilisation mémoire, benchmarking des économies de tokens

npx skills add https://github.com/rtk-ai/rtk --skill performance

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 --crates si >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) :

  1. 🔴 Lazy static regex (5-10ms par pattern s'il était compilé à la runtime)
  2. 🔴 Supprimer I/O démarrage (10-50ms pour lectures fichiers config)
  3. 🟡 Traitement sans copies (1-2MB mémoire, 1-2ms démarrage)
  4. 🟡 Dépendances minimales (1-2MB binaire, 2-5ms démarrage)
  5. 🟢 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

Skills similaires