---
from: claude
to: dom
date: 2026-06-05T19:45:00+02:00
topic: diagnostic-perf-mvp
status: open
priority: blocker
references:
  - decision: docs/coordination/decisions/2026-06-05_dom_d19-performance-mvp-p1.md
---

# Diagnostic performance MVP (D-19) — cause racine + hotfix proposés

Symptôme (test Windows Dom) : anonymisation très lente, **CPU ~12 %** (≈ 1 cœur sur 8),
RAM ~16 Go. Diagnostic par lecture du code (aucune modif appliquée — gel bêta respecté).

## Causes racines identifiées (3)

### C1 — torch bridé à 1 thread en EXE frozen (cause principale du CPU 12 %)
`torch.set_num_threads()` / `OMP_NUM_THREADS` ne sont **définis nulle part** en
production (vérifié : seulement dans un script batch et un archive legacy). En EXE
PyInstaller frozen, torch ne détecte pas correctement les cœurs et tombe souvent à
**1 thread**. Or torch porte **l'OCR docTR** (db_resnet50 + crnn) **et** une partie NER.
→ explique directement le CPU ~12 %.

### C2 — Rastérisation forcée séquentielle en frozen
`anonymizer_core_refactored_onnx.py:4316-4322` : en `sys.frozen`, la rastérisation
des pages passe en **séquentiel mono-cœur** (pour éviter que `ProcessPoolExecutor`
relance l'exe → fenêtres fantômes). Mono-cœur sur toutes les pages.

### C3 — OCR docTR séquentiel, page par page, à 300 dpi
`anonymizer_core_refactored_onnx.py:1259-1280` : sur les pages pauvres en texte
(< 150 chars, i.e. **scannées**), docTR tourne dans une **boucle `for` page par page**
à **300 dpi** (images ~26 Mo/page), un appel `model([img])` à la fois. Pour un doc
scanné, c'est le coût dominant. (Bonne nouvelle : les PDF natifs riches en texte
**sautent l'OCR** — donc le problème est surtout sur les scannés.)

## RAM ~16 Go — explication
Cumul : modèles docTR (det+reco) + torch + modèle ONNX CamemBERT + **gazetteer
paranames 1.4M noms en mémoire** + images 300 dpi. Élevé mais surtout dû aux modèles
chargés ; pas une fuite. Mitigation possible (libérer les images après OCR) mais
secondaire vs le CPU.

## Hotfix proposés (faible risque, classés impact/risque)

| # | Hotfix | Impact | Risque | Détection |
|---|---|---|---|---|
| **H1** | `torch.set_num_threads(os.cpu_count())` + `OMP_NUM_THREADS`/`MKL_NUM_THREADS` au démarrage | **Élevé** (OCR + NER multicœur) | **Quasi nul** | inchangée |
| **H2** | Rastérisation frozen → `ThreadPoolExecutor` (fitz/PIL/pyzbar libèrent le GIL → vrai multicœur, sans relance exe) | Élevé (docs natifs multi-pages) | Faible | inchangée |
| **H3** | OCR docTR en **batch** (`model([img1,img2,…])` au lieu d'1 page/appel) | Moyen-élevé (scannés) | Faible-moyen | inchangée (même modèle) |
| **H4** | **Timings par étape** (logging extract/OCR/NER/raster/save) | — (mesure) | Nul | inchangée |

**Non recommandé sans précaution** : baisser le DPI OCR (300→200). Gain réel mais
**risque sur le leak score 100/100** (texte manqué = fuite). À écarter du hotfix MVP.

## Pourquoi H1 et H2 sont sûrs vis-à-vis du leak
- Ils ne changent **ni la détection, ni les rectangles, ni le texte produit** — juste
  le **nombre de cœurs** utilisés. La sortie est identique, seul le temps change.
- H2 : `_rasterize_page` ouvre déjà son propre `fitz.open()` par page (thread-safe
  pour des Documents séparés). Aucun état partagé.

## Plan proposé (sans toucher la branche bêta tant que non validé)
1. **H4 d'abord** (timings) + **H1** → mesurer sur TON doc lent réel pour confirmer
   quelle phase domine (raster vs OCR vs NER).
2. Selon la mesure : **H2** (si raster/natif) et/ou **H3** (si OCR/scanné).
3. Re-build EXE de test → tu mesures CPU/temps → si OK et **leak 100/100 + 98 tests
   verts**, on intègre.

Je peux préparer ces patchs sur une **branche dédiée** (`fix/perf-mvp`, pas la branche
bêta) et te livrer un EXE de test. Dis-moi si tu veux que je code H1+H4 (les plus sûrs)
en premier pour une mesure réelle.

**Question** : ton doc de test lent était-il **scanné** (OCR) ou **natif** (texte) ?
Ça oriente direct H2 vs H3.

— Claude
