Comment la prédiction de branche du CPU accélère le code et crée des vulnérabilités
Les CPU modernes prédisent le résultat des branches conditionnelles pour éviter les blocages du pipeline. Cette optimisation booste l'exécution du code de 30 à 50 % en moyenne, mais dans 5 % des cas, elle provoque des vidages de pipeline et des pertes de performance. Dans des exemples de tri de données, l'écart de vitesse peut atteindre un facteur six.
Fonctionnement du pipeline et nécessité de la prédiction
Les CPU traitent les instructions en étapes via un pipeline : récupération, décodage, exécution, accès mémoire et écriture. Les pipelines des CPU modernes comptent 15 à 20 étapes. Lors d'une branche conditionnelle comme une instruction if, le CPU doit choisir : attendre la résolution de la condition ou récupérer spéculativement la prochaine instruction. Attendre cause 15 à 20 cycles d'arrêt, d'où la prédiction du chemin de code qui sera pris.
Évolution des algorithmes de prédiction de branche
La précision de prédiction a évolué des méthodes basiques vers des systèmes avancés basés sur des réseaux de neurones.
- Prédiction statique — suppose toujours que la branche est prise ou non, avec environ 50 % de précision.
- Prédiction dynamique (prédicteur 1-bit) — suit l'historique des branches précédentes, atteignant 99,8 % de précision sur les boucles.
- Compteur 2 bits — ajoute de la robustesse au bruit, portant la précision à 85–90 %.
- Prédicteur à deux niveaux — analyse les motifs des branches récentes via un Registre d'Historique de Branche et une Table d'Historique de Motifs.
- TAGE (TAgged GEometric history length predictor) — la référence moderne, utilisant plusieurs tables de profondeur d'historique pour 95–98 % de précision sur du code réel.
Impact sur les performances en conditions réelles
Une mauvaise prédiction vide le pipeline, gaspillant 15–20 cycles. À 5 GHz, cela fait 4 nanosecondes par erreur, mais des millions d'erreurs par seconde peuvent réduire les performances de 30–50 %. Un exemple classique est le traitement de tableau avec condition :
for (int i = 0; i < 100000; i++) {
for (int j = 0; j < arraySize; j++) {
if (data[j] >= 128) {
sum += data[j];
}
}
}
Sur des données non triées, le prédicteur se trompe ~50 % du temps, prenant 11,2 secondes. Sur des données triées, les erreurs tombent à une seule au seuil, réduisant le temps à 1,9 seconde — un gain de vitesse par six.
Techniques d'optimisation pour les développeurs
Les ajustements manuels de prédiction de branche ne sont nécessaires que pour les points chauds. Stratégies clés :
- Code sans branche — remplacez les conditionnels par des opérations arithmétiques ou binaires.
- Analyse des métriques —
perf statsous Linux révèle les taux d'erreur viabranch-misses. - Tri des données — gain énorme pour les parcours répétés sur le même jeu de données.
- Profilage — indispensable pour repérer les goulots d'étranglement.
- Indications au compilateur — utilisez les macros
likely()/unlikely()ou les attributs C++20[[likely]]/[[unlikely]]pour guider la disposition du code.
Vulnérabilité Spectre et risques de sécurité
La vulnérabilité Spectre de 2018 exploite l'exécution spéculative. Les attaquants entraînent le prédicteur sur des données inoffensives, puis déclenchent un accès spéculatif à la mémoire protégée. Le résultat spéculatif est annulé, mais les traces en cache persistent, permettant la récupération de données via des attaques par timing. Les correctifs atténuent cela mais réduisent les performances de 2 à 30 %.
Points clés
- La prédiction de branche est un pilier de l'optimisation CPU, offrant des accélérations de 30–50 %.
- Les mauvaises prédictions touchent 5 % des cas, vidant les pipelines et coûtant 15–20 cycles chacune.
- Trier les données peut accélérer le code par six en améliorant la précision de prédiction.
- Le code sans branche élimine totalement le surcoût de prédiction.
- Spectre exploite la spéculation pour voler des données mémoire protégées.
— Editorial Team
Aucun commentaire pour le moment.