# 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

Taille chunk Précision Index Vitesse recherche Contexte
10 lignes 🔴 Énorme 🔴 Lente ⚠️ Faible
20 lignes 🟡 Moyen 🟢 Rapide 🟢 Bon
50 lignes 🟢 Petit 🟢🟢 Très rapide 🟢 Bon
100 lignes 🟢🟢 Très petit 🟢🟢🟢 Ultra rapide ⚠️ Trop

# 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

Overlap Précision Index Redondance
0% (actuel) 🟢 Normal Aucune
25% 🟡 +25% ⚠️ Faible
50% 🔴 +100% 🔴 Moyenne
75% 🔴🔴 +300% 🔴🔴 Forte

# 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

Modèle Dims Vitesse indexation Taille par chunk Qualité RAM
MiniLM 384 Rapide 1.5 KB 500 MB
MPNet 768 Moyen 3 KB 800 MB
CodeBERT 768 Moyen 3 KB 800 MB
Jina Code 768 Lent 3 KB 1 GB

# 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

k Vitesse Précision Utilité
k=1 Vous connaissez exactement ce que vous cherchez
k=5 Recommandé : exploration
k=10 Recherche large
k=50 🐌 Trop de bruit

# 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 ?

Mode Usage Exemple
Hybride Vous connaissez le nom exact/partiel "jwt_required", "UserService"
Sémantique Recherche par concept/description "validate email format", "send notification"

# 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

Si vous voulez... Alors...
Meilleure précision Jina Code + 15 lignes + overlap 50%
Meilleur compromis MPNet + 20 lignes + overlap 25%
Plus rapide MiniLM + 30 lignes + pas d'overlap
Petit index MiniLM + 50 lignes + pas d'overlap

# Prochaines étapes

  1. Comprendre les trade-offs (ce document)
  2. → Décider de votre priorité (précision ou vitesse ?)
  3. → Tester différentes configurations
  4. → Mesurer l'impact sur votre codebase