#
Compromis : Précision vs Performance
#
Introduction
Chaque choix d'architecture dans mgrep implique un trade-off entre :
- ⚡ Performance : vitesse, taille de l'index, consommation mémoire
- 🎯 Précision : qualité des résultats, pertinence sémantique
Ce document explore tous les compromis et aide à faire les bons choix.
#
1. Taille des chunks
#
Setup actuel : MAX_CHUNK_LINES = 20
Fonction de 60 lignes → divisée en 3 chunks :
- Part 1 : lignes 1-20
- Part 2 : lignes 21-40
- Part 3 : lignes 41-60
#
Compromis
#
Impact concret
#
Avec 10 lignes (très précis)
Avantages :
def authenticate_user(username, password):
# Chunk 1 (lignes 1-10) : validation input
if not username or not password:
return None
# Chunk 2 (lignes 11-20) : database query
user = db.query(User).filter_by(username=username).first()
# Chunk 3 (lignes 21-30) : password check
if check_password(user.password, password):
return user
Recherche "validate user input" → trouve exactement le chunk 1 ✅
Inconvénients :
- 3x plus de chunks → 3x plus de stockage
- 3x plus d'embeddings à calculer → 3x plus lent à indexer
- Index Elasticsearch beaucoup plus gros
#
Avec 50 lignes (moins précis)
Avantages :
- 1 chunk pour toute la fonction
- Index compact
- Recherche rapide
Inconvénients :
Recherche "validate user input"
→ Trouve tout le chunk (50 lignes)
→ Dilution : validation + query + password check mélangés
→ Score moins bon (embedding moyenné sur 50 lignes)
#
Recommandation actuelle
20 lignes = sweet spot
Pourquoi :
- ✅ Assez précis (1 concept par chunk généralement)
- ✅ Pas trop de chunks (index raisonnable)
- ✅ Vitesse correcte
#
2. Overlap entre chunks (sliding window)
#
Sans overlap (actuel)
Fonction de 60 lignes :
Chunk 1 : lignes 1-20
Chunk 2 : lignes 21-40 ← Pas de zone commune
Chunk 3 : lignes 41-60
Problème :
# Ligne 18-22 du code
if user is None: # ligne 18
return None # ligne 19
# --- COUPURE ---
result = process(user) # ligne 21
return result # ligne 22
Si vous cherchez "process user", le chunk 2 a process(user) mais pas le contexte du if user is None.
#
Avec overlap 50% (proposé)
Fonction de 60 lignes :
Chunk 1 : lignes 1-20
Chunk 2 : lignes 11-30 ← Overlap 10 lignes avec chunk 1
Chunk 3 : lignes 21-40 ← Overlap 10 lignes avec chunk 2
Chunk 4 : lignes 31-50
Chunk 5 : lignes 41-60
Avantage :
# Chunk 1 (lignes 1-20)
if user is None: # ligne 18
return None # ligne 19
# Chunk 2 (lignes 11-30) - OVERLAP
if user is None: # ligne 18 (répété)
return None # ligne 19 (répété)
result = process(user) # ligne 21
return result # ligne 22
Maintenant "process user" trouve le chunk 2 avec contexte complet ✅
#
Compromis
#
Impact sur l'index
Codebase de 10,000 lignes Python :
Sans overlap :
- 500 chunks de 20 lignes
- Index : ~50 MB
- Temps indexation : 2 min
Overlap 50% :
- 1000 chunks (2x plus)
- Index : ~100 MB (2x plus gros)
- Temps indexation : 4 min (2x plus long)
#
Recommandation
Overlap 50% recommandé SI :
- Vous avez assez d'espace disque (2x l'index)
- Vous cherchez la meilleure précision possible
- Le temps d'indexation n'est pas critique
Pas d'overlap acceptable SI :
- Codebase massive (>100k lignes)
- Contraintes de stockage
- Temps d'indexation important
#
3. Choix du modèle d'embedding
#
Comparaison complète
#
Impact concret sur un projet réel
Codebase : 50,000 lignes Python (2,500 chunks)
#
Avec MiniLM (384 dims)
Index : 2,500 chunks × 1.5 KB = 3.75 MB
Temps indexation : ~1 minute
RAM nécessaire : 500 MB
Qualité :
- "jwt middleware" → 60% des résultats pertinents
- "form submission" → 70% des résultats pertinents
#
Avec Jina Code (768 dims)
Index : 2,500 chunks × 3 KB = 7.5 MB
Temps indexation : ~3 minutes
RAM nécessaire : 1 GB
Qualité :
- "jwt middleware" → 95% des résultats pertinents
- "form submission" → 90% des résultats pertinents
#
Trade-off : Qualité vs Vitesse
Qualité
↑
95% │ ● Jina Code (lent)
│
80% │ ● MPNet
│
70% │ ● MiniLM (rapide)
│
60% │ ● CodeBERT (mean pooling)
│
└────────────────→ Vitesse
Lent Moyen Rapide
#
Recommandation
Pour développement interactif :
- Jina Code ou MPNet (qualité > vitesse)
- Vous indexez 1 fois, vous cherchez 100 fois
Pour CI/CD ou indexation fréquente :
- MPNet (bon compromis)
- MiniLM si vraiment limité en ressources
#
4. Nombre de résultats retournés (-k)
#
Setup actuel : k=5 (top 5 résultats)
python sgrep.py "jwt middleware" -k 5
# Retourne 5 meilleurs résultats
#
Compromis
#
Impact sur Elasticsearch
k=1 : Elasticsearch parcourt ~100 chunks → 10ms
k=5 : Elasticsearch parcourt ~500 chunks → 30ms
k=10 : Elasticsearch parcourt ~1000 chunks → 60ms
k=50 : Elasticsearch parcourt ~5000 chunks → 200ms
Plus k est grand, plus Elasticsearch doit comparer de vecteurs.
#
Recommandation
k=5 optimal pour l'usage quotidien :
- Assez pour explorer différentes approches
- Pas trop pour être submergé
- Vitesse acceptable
#
5. Recherche hybride vs purement sémantique
#
Hybride (défaut actuel)
Score final = Score sémantique (kNN) × 1.5 + Score lexical (text match)
Exemple :
Recherche : "jwt_required"
Résultat 1 : def jwt_required():
- Score sémantique : 0.8
- Score lexical : 1.0 (match exact du nom)
→ Score total : 0.8×1.5 + 1.0 = 2.2
Résultat 2 : def authenticate_with_token():
- Score sémantique : 0.9
- Score lexical : 0.1 (pas de match lexical)
→ Score total : 0.9×1.5 + 0.1 = 1.45
Le résultat 1 gagne grâce au match exact du nom !
#
Purement sémantique (--semantic-only)
Score final = Score sémantique (kNN) uniquement
Même exemple :
Résultat 1 : def jwt_required():
→ Score : 0.8
Résultat 2 : def authenticate_with_token():
→ Score : 0.9 ← Gagne maintenant !
#
Quand utiliser quoi ?
#
Compromis performance
Hybride :
- kNN search : 30ms
- Text match : 5ms
- Merge results : 2ms
→ Total : 37ms
Sémantique uniquement :
- kNN search : 30ms
→ Total : 30ms
Différence négligeable !
Recommandation : Garder hybride par défaut (meilleur des deux mondes)
#
6. Taille de la codebase vs stratégies
#
Petit projet (<10k lignes)
Stratégie optimale :
- MAX_CHUNK_LINES = 15 (très précis)
- Overlap = 50%
- Modèle = Jina Code (meilleur)
- k = 10 (explorer largement)
Résultat :
- Index : ~20 MB
- Recherche : instantanée (<50ms)
- Précision : excellente
#
Projet moyen (10k-100k lignes)
Stratégie équilibrée (actuelle) :
- MAX_CHUNK_LINES = 20
- Overlap = 0% ou 25%
- Modèle = Jina Code ou MPNet
- k = 5
Résultat :
- Index : 50-500 MB
- Recherche : rapide (<100ms)
- Précision : très bonne
#
Gros projet (>100k lignes)
Stratégie performance :
- MAX_CHUNK_LINES = 30 (moins de chunks)
- Overlap = 0%
- Modèle = MPNet (rapide)
- k = 5
Résultat :
- Index : 500-2000 MB
- Recherche : acceptable (<200ms)
- Précision : bonne
#
Monorepo massif (>500k lignes)
Stratégie spéciale :
- Indexer par sous-projet (diviser l'index)
- MAX_CHUNK_LINES = 50
- Overlap = 0%
- Modèle = MiniLM (le plus rapide)
- k = 3
Considérer :
- Filtrer par langage
- Exclure certains dossiers (vendor, generated)
- Indexer en background
#
Résumé des recommandations
#
Configuration optimale pour mgrep (projet moyen)
# .env
EMBEDDING_MODEL=jinaai/jina-embeddings-v2-base-code
# watcher.py
MAX_CHUNK_LINES = 20
OVERLAP = 0.5 # 50% (à implémenter)
# Utilisation
python sgrep.py "query" -k 5 # Hybride par défaut
#
Tableau décisionnel
#
Prochaines étapes
- ✅ Comprendre les trade-offs (ce document)
- → Décider de votre priorité (précision ou vitesse ?)
- → Tester différentes configurations
- → Mesurer l'impact sur votre codebase