Qu'est-ce que le multicœur exactement ?
Lorsque les améliorations de performance des processeurs monocœur ont atteint des goulots d'étranglement (mur de puissance, mur de fréquence, mur de complexité du pipeline), les ingénieurs ont trouvé une solution « simple et rudimentaire » mais efficace : placer plusieurs cœurs de processeur sur une seule puce. Ce cours présentera les principes et les avantages du multicœur, ainsi que les limites de la loi d'Amdahl sur l'accélération parallèle.
Analogie du quotidien : une cuisine de fast-food
Imaginez un restaurant de fast-food pendant l'heure de pointe du déjeuner.
Mode monocœur: Un seul chef. Il essaie désespérément d'accélérer (augmenter la fréquence) et d'optimiser le processus de cuisson (pipeline), mais peu importe à quel point il optimise, un seul plat peut être cuisiné à la fois. Les clients font la queue et s'impatientent.
Mode multicœur: Le patron embauche quatre chefs, chacun avec son propre fourneau. Les quatre chefs peuvent cuisiner quatre plats différents en même temps. Il n'est pas nécessaire que chaque chef devienne plus rapide, mais la production globale augmente plusieurs fois.
C'est la philosophie de conception du multicœur :Quand il est difficile d'accélérer davantage un seul cœur, il vaut mieux ajouter plus de cœurs et les laisser travailler en parallèle.
Le multicœur ne signifie pas « faire 4 copies de tout ». Les 4 cœurs partagent généralement la même mémoire, la même interface disque et le même système d'exploitation ; ils ne se dupliquent qu'au niveau de l'« unité de calcul ». Comme 4 chefs partageant le même réfrigérateur et le même entrepôt d'ingrédients.
Monocœur vs multicœur : comparaison d'architecture
Le dilemme des CPU monocœur
Au début des années 2000, l'industrie des CPU améliorait les performances en augmentant constamment la fréquence — de 1 GHz à 2 GHz puis à 3 GHz. Mais cette voie a rapidement atteint ses limites.
Trois goulets d'étranglement majeurs de la fréquence monocœur :
- mur de puissance: Plus la fréquence est élevée, plus la consommation d'énergie croît de manière exponentielle, et la dissipation thermique devient une limite physique.
- mur de mémoire: Le CPU est trop rapide, la mémoire ne peut pas suivre, et la plupart du temps il attend passivement les données.
- Mur de l'ILP: Le parallélisme au niveau des instructions (ILP) a été exploité jusqu'à sa limite ; de moins en moins d'instructions dans un même flux d'instructions peuvent être parallélisées.
Vers 2005, Intel et AMD ont successivement abandonné la pure « course à la fréquence » et se sont tournés vers la voie du multicœur.
Structure d'un processeur multicœur
La structure interne typique d'un processeur multicœur est la suivante :
+-----------------------------------------------------------+ | CPU 芯片 | | +-------------+ +-------------+ +-------------+ +-----+ | | 核心 1 | | 核心 2 | | 核心 3 | |核 4 | | | L1 Cache | | L1 Cache | | L1 Cache | | ... | | | L2 Cache | | L2 Cache | | L2 Cache | | | | +------+------+ +------+------+ +------+------+ +--+--+ | | | | | | +----------------+----------------+--------------+ | | | +-----+------+ | | 共享 L3 Cache | | +-----+------+ | | | +-----+------+ | | 内存控制器 | ← 连接主内存 (RAM) | +------------+ +-----------------------------------------------------------+
Conception clé :
- Chaque cœur possède ses propres caches L1 et L2 (privés).
- Plusieurs cœurs partagent un cache L3
- Tous les cœurs accèdent à la même mémoire principale via le contrôleur de mémoire.
- Le système d'exploitation est chargé d'attribuer les tâches aux différents cœurs.
Loi d'Amdahl : le plafond de l'accélération multicœur.
L'intuition selon laquelle « plus de cœurs signifie plus rapide » n'est pas toujours vraie. En 1967, l'informaticien Gene Amdahl a proposé une loi célèbre.
Loi d'Amdahl : L'accélération globale d'une tâche est limitée par la portion qui doit être exécutée en série.
La formule est la suivante :
加速比 = 1 / (S + P/N) 其中: S = 任务中必须串行执行的比例(0~1) P = 任务中可以并行执行的比例(0~1),且 S + P = 1 N = 并行处理单元的数量(核心数)
Cette formule révèle un fait cruel :
Même avec un nombre infini de cœurs, si seulement 50% de la tâche peut être parallélisée, l’accélération peut être au maximum de 2x.
Dérivation : lorsque N tend vers l’infini, P/N tend vers 0, et l’accélération tend vers 1/S.
| Proportion parallélisable | 1 cœur | 2 cœurs | 4 cœurs | 8 cœurs | 16 cœurs | Cœurs infinis (la limite supérieure) |
|---|---|---|---|---|---|---|
| 50% | 1.00x | 1.33x | 1.60x | 1.78x | 1.88x | 2.00x |
| 75% | 1.00x | 1.60x | 2.29x | 2.91x | 3.37x | 4.00x |
| 90% | 1.00x | 1.82x | 3.08x | 4.71x | 6.40x | 10.00x |
| 95% | 1.00x | 1.90x | 3.48x | 5.93x | 9.14x | 20.00x |
| 99% | 1.00x | 1.98x | 3.88x | 7.48x | 13.91x | 100.00x |
Observez la tendance dans ce tableau :
- Plus le taux de parallélisme est élevé, plus l’accélération multicœur est importante.Une tâche qui est parallélisable à 99% peut être accélérée près de 14 fois sur 16 cœurs.
- Plus de cœurs, des rendements marginaux plus faibles.L’amélioration de l’accélération en passant de 2 cœurs à 4 cœurs est bien plus grande que celle en passant de 16 cœurs à 32 cœurs.
- La partie séquentielle est la limite absolueLorsque seulement 50% est parallélisable, même avec dix mille cœurs, on ne peut être au maximum que 2 fois plus rapide.
La loi d'Amdahl explique pourquoi « plus de cœurs ne signifie pas nécessairement plus rapide ». Si votre programme est principalement composé de logique séquentielle — par exemple, une étape de calcul doit attendre le résultat de l'étape précédente — alors quel que soit le nombre de cœurs, ils ne peuvent qu'observer et ne peuvent pas aider.
Démo interactive : calculateur de la loi d'Amdahl
Le code Python ci-dessous calcule et imprime un tableau comparatif des accélérations selon la loi d'Amdahl, et trace également comment l'accélération évolue avec le nombre de cœurs, afin que vous puissiez ressentir intuitivement l'impact du « goulot d'étranglement séquentiel ».
Exemple
Calculateur de la loi d'Amdahl (exemple de démonstration)
Calculer l'accélération pour différents taux de parallélisme et nombres de cœurs
Afficher le tableau comparatif et l'analyse de tendance
"""
def amdahl_speedup(parallel_fraction, num_cores):
"""
Formule fondamentale de la loi d'Amdahl
parallel_fraction : fraction pouvant être exécutée en parallèle (0.0 ~ 1.0)
num_cores : nombre de cœurs
Retourner l'accélération
"""
if num_cores == 1:
return 1.0
serial_fraction = 1.0 - parallel_fraction
speedup = 1.0 / (serial_fraction + parallel_fraction / num_cores)
return speedup
def amdahl_limit(parallel_fraction):
"""
Calculez la limite supérieure théorique d'accélération de la loi d'Amdahl (lorsque le nombre de cœurs tend vers l'infini)
Lorsque N→∞, P/N→0, accélération → 1/S
"""
serial_fraction = 1.0 - parallel_fraction
if serial_fraction == 0:
return float('inf') # Entièrement parallélisable, peut théoriquement être accéléré à l'infini
return 1.0 / serial_fraction
def print_comparison_table(parallel_fractions, core_counts):
"""Affiche le tableau complet de comparaison des accélérations."""
# En-tête de tableau
header = f"{'Cœurs':>6} |"
for pf in parallel_fractions:
header += f" {pf*100:>5.0f}% parallélisable |"
print("=" * (12 + 12 * len(parallel_fractions)))
print(Loi d'Amdahl - Tableau comparatif d'accélération multicœur)
print("=" * (12 + 12 * len(parallel_fractions)))
print(header)
print("-" * (12 + 12 * len(parallel_fractions)))
for cores in core_counts:
row = f" {cores:>5} |"
for pf in parallel_fractions:
speedup = amdahl_speedup(pf, cores)
row += f" {speedup:>6.2f}x |"
print(row)
# Limite supérieure théorique
print("-" * (12 + 12 * len(parallel_fractions)))
limit_row = f" {'Limite supérieure':>5} |"
for pf in parallel_fractions:
limit = amdahl_limit(pf)
if limit == float('inf'):
limit_row += f" aucune limite supérieure |"
else:
limit_row += f" {limit:>6.2f}x |"
print(limit_row)
print("=" * (12 + 12 * len(parallel_fractions)))
def analyze_diminishing_returns(parallel_fraction, max_cores=64):
"""Analyser les rendements marginaux décroissants de l'accélération multicœur"""
print(f"\nAnalyse du bénéfice marginal (proportion parallélisable {parallel_fraction*100:.0f}%) :)
print(f{'Cœurs':>6} | {'Accélération':>8} | {'Amélioration':>10} | {'Efficacité':>8})
print("-" * 42)
prev_speedup = 1.0
cores = 1
while cores <= max_cores:
speedup = amdahl_speedup(parallel_fraction, cores)
improvement = speedup - prev_speedup
# Efficacité = accélération réelle / nombre de cœurs (100% avec une accélération linéaire parfaite)
efficiency = (speedup / cores) * 100
print(f" {cores:>6} | {speedup:>8.2f}x | +{improvement:>8.3f}x | {efficiency:>7.1f}%")
prev_speedup = speedup
if cores == 1:
cores = 2
else:
cores *= 2
def compare_serial_vs_parallel(serial_work_ms, parallel_work_ms, num_cores):
"""
Utilisez le temps mis par une tâche spécifique pour démontrer la loi d'Amdahl
serial_work_ms: temps passé sur la partie séquentielle (millisecondes)
parallel_work_ms: temps de la partie parallélisable (mono-cœur, millisecondes)
num_cores: nombre de cœurs disponibles
"""
total_single_ms = serial_work_ms + parallel_work_ms
parallel_fraction = parallel_work_ms / total_single_ms
parallel_time_multi = parallel_work_ms / num_cores
total_multi_ms = serial_work_ms + parallel_time_multi
speedup = total_single_ms / total_multi_ms
print(f"\nSimulation de la durée d'une tâche spécifique :)
print(fTemps écoulé de la partie série : {serial_work_ms} ms (ne peut pas être accélérée))
print(fTemps passé dans la partie parallélisable : {parallel_work_ms} ms (mono-cœur))
print(fFraction parallèle : {parallel_fraction*100:.0f}%)
print(f"")
print(fTemps total mono-cœur : {total_single_ms} ms)
print(f" Temps total pour {num_cores} cœurs : {total_multi_ms:.1f} ms(partie série {serial_work_ms} + Parallélisme {parallel_work_ms}/{num_cores} = {parallel_time_multi:.1f})")
print(f" Accélération réelle : {speedup:.2f}x")
print(fLimite d'Amdahl : {amdahl_limit(parallel_fraction):.2f}x)
return speedup
# ===== Programme principal =====
print("=" * 60)
print(Calculateur de la loi d'Amdahl (exemple))
print("=" * 60)
# 1. Tableau de comparaison de base
parallel_levels = [0.50, 0.75, 0.90, 0.95, 0.99]
core_list = [1, 2, 4, 8, 16, 32, 64, 128, 256]
print_comparison_table(parallel_levels, core_list)
# 2. Analyse du bénéfice marginal (en utilisant 75% de parallélisabilité comme exemple)
analyze_diminishing_returns(0.75, max_cores=64)
# 3. Simulation de tâche spécifique
print("\n" + "=" * 60)
print(" Simulation de scénario réel")
print("=" * 60)
# Scénario 1 : Rendu vidéo (hautement parallèle, 95%)
print("\n[Scène 1] Tâche de rendu vidéo")
print(Partie série : Lire le fichier, initialiser l'encodeur = 10ms)
print(Partie parallèle : rendu par image = 200ms (mono-cœur))
compare_serial_vs_parallel(10, 200, 16)
# Scénario 2 : Transaction de base de données (modérément parallélisable, 50%)
print("\n[Scénario 2] Traitement des transactions de base de données")
print(" Partie série : écriture des logs, gestion des verrous = 50ms")
print(Partie parallèle : exécution de requête = 50ms (mono-cœur))
compare_serial_vs_parallel(50, 50, 16)
# Scénario 3 : Calcul pur (hautement parallèle, 99%)
print("\n[Scénario 3] Calcul scientifique / opérations matricielles)
print(Partie série : agrégation des résultats = 1ms)
print(Partie parallèle : multiplication matricielle = 1000ms (mono-cœur))
compare_serial_vs_parallel(1, 1000, 16)
# 4. Nombre de cœurs recommandé
print("\n" + "=" * 60)
print(Conseils pratiques : Comment déterminer si vous avez besoin de plus de cœurs ?)
print("=" * 60)
# Calculer le point d'inflexion 'coût-performance' à différents ratios de parallélisme
print(f" {'Ratio de parallélisme':>10} | {'2 cœurs mention gain':>10} | {'4 cœurs mention gain':>10} | {'8 cœurs mention gain':>10} | {'16 cœurs mention gain':>9} | {'Suggestions nombre de cœurs':>10}")
print("-" * 70)
for pf in [0.50, 0.60, 0.70, 0.75, 0.80, 0.85, 0.90, 0.95, 0.99]:
ratios = []
prev = 1.0
recommendations = []
for cores in [2, 4, 8, 16]:
sp = amdahl_speedup(pf, cores)
gain = (sp / prev - 1) * 100 # Pourcentage d'amélioration par rapport au niveau précédent
ratios.append(gain)
prev = sp
if gain < 10:
recommendations.append(cores)
# Suggestion : une amélioration inférieure à 10% ne justifie pas une mise à niveau
suggested = ">=16 cœurs" if not recommendations else f{recommendations[0]} cœurs suffisent
print(f" {pf*100:>9.0f}% | {ratios[0]:>9.1f}% | {ratios[1]:>9.1f}% | {ratios[2]:>9.1f}% | {ratios[3]:>8.1f}% | {suggested:>10}")
print()
print(" Règle empirique :")
print(- Travail de bureau quotidien, navigation web (faible taux de parallélisme) : 4-6 cœurs suffisent)
print(- Compilation de programmes, jeux légers : 6-8 cœurs)
print(- Rendu vidéo, modélisation 3D : 8-16 cœurs)
print(- Calcul scientifique, entraînement d'IA : plus il y a de cœurs, mieux c'est (mais tenez également compte de la VRAM et de la bande passante mémoire))
En exécutant le code ci-dessus, vous observerez plusieurs phénomènes clés :
- La proportion de parallélisme détermine tout: Une tâche parallélisable à 50%, même avec 256 cœurs, obtient une accélération inférieure à 2 fois. Mais une tâche parallélisable à 99% peut obtenir une accélération de près de 14 fois sur 16 cœurs.
- Rendements marginaux décroissants: La plus grande amélioration se produit en passant de 1 cœur à 2 cœurs ; ensuite, chaque fois que le nombre de cœurs double, le gain de vitesse supplémentaire devient de plus en plus faible.
- Baisse d'efficacité: Lorsque le nombre de cœurs double, l'efficacité (accélération réelle / nombre de cœurs) continue de baisser. C'est pourquoi empiler aveuglément des cœurs n'est pas rentable.
Démo interactive : animation de répartition des tâches multicœur
La démonstration suivante attribue 12 tâches de calcul à1 cœuret4 cœurspour être traitées. En mode 1 cœur, les tâches sont mises en file d'attente et exécutées en série ; en mode 4 cœurs, quatre tâches sont traitées en parallèle en même temps. Observez la différence de vitesse de remplissage de la barre de progression et de temps d'achèvement entre les deux modes.
Démo d'animation d'attribution de tâches multicœur (exemple)
File d'attente des tâches1 cœur (traitement en série)
4 cœurs (traitement parallèle)
Le multicœur dans les applications du monde réel
Comment le système d'exploitation planifie le multicœur
L'ordonnanceur du système d'exploitation est chargé d'attribuer différents processus et threads à différents cœurs.
Il prendra en compte :
- Équilibrage de charge: Essayez de garder chaque cœur occupé, en évitant « un cœur en difficulté, de nombreux cœurs qui regardent ».
- Affinité de cache: essayez de faire en sorte que le même thread s'exécute toujours sur le même cœur, en tirant parti du cache privé de ce cœur.
- Gestion de l'alimentation: Lorsque la charge est faible, désactivez certains cœurs pour économiser de l'énergie.
Multi-cœur vs Hyper-Threading
Outre le multi-cœur physique, il existe aussi une technologie appeléeHyper-Threading (SMT)。
L'Hyper-Threading fait qu'un cœur physique ressemble à deux « cœurs logiques ». Lorsqu'un thread attend des données (défaut de cache), le cœur peut basculer vers un autre thread pour continuer à travailler.
| Solution | Coût matériel | Gain de performance | Scénarios applicables |
|---|---|---|---|
| Multi-cœur physique | Élevé (nécessite de dupliquer toute l'unité d'exécution) | Presque le double (cas idéal) | Tâches à forte charge soutenue |
| Hyper-threading (SMT) | Faible (nécessite uniquement des registres supplémentaires + une logique de contrôle). | Environ 10%~30% | Utiliser pleinement le temps d'attente |
De nombreux processeurs modernes utilisent les deux technologies en même temps. Par exemple, un Intel Core i7 peut être « 4 cœurs physiques + hyper-threading = 8 cœurs logiques ».
Résumé et vérification
Résumé en une phrase : de nombreux cœurs sont intégrés sur une seule puce pour que plusieurs cœurs de CPU exécutent la même tâche en parallèle. Mais la loi d'Amdahl indique que l'accélération réelle dépend de la proportion de parallélisme dans la tâche — la partie série détermine la limite supérieure de l'accélération ; plus il y a de cœurs, plus les rendements marginaux sont faibles.
questions d'auto-évaluation
- Une tâche est parallélisable à 80 %. Sur un processeur à 4 cœurs, quelle accélération la loi d'Amdahl donne-t-elle ?
Réponse : S=0.2, P=0.8, N=4. Accélération = 1/(0.2 + 0.8/4) = 1/(0.2 + 0.2) = 1/0.4 = 2.5x. Cela signifie aussi que 4 cœurs n'apportent qu'une accélération de 2.5 fois, avec une efficacité de seulement 62.5%.
- Si une tâche peut être presque parallélisée à 100 %, quelle est la limite théorique supérieure ? Pourquoi est-elle encore inatteignable en pratique ?
Réponse : La limite théorique supérieure est l'infini (accélération = nombre de cœurs). Mais en pratique, elle est limitée par : (1) le surcoût de division et de fusion des tâches ; (2) la latence de synchronisation et de communication entre les cœurs ; (3) la contention pour les ressources partagées (comme la bande passante mémoire).
- Pourquoi les processeurs de serveur modernes peuvent-ils avoir 64, voire 128 cœurs, alors que les ordinateurs personnels n'ont généralement que 4 à 16 cœurs ?
Réponse : Les requêtes traitées par les serveurs (comme les requêtes web et les requêtes de base de données) sont naturellement hautement parallèles — chaque requête utilisateur est indépendante, donc la proportion parallélisable est proche de 100 %. En revanche, les tâches quotidiennes sur les ordinateurs personnels (comme le rendu de navigateur et le traitement de documents Office) ont un parallélisme limité ; trop de cœurs seraient inutiles et augmenteraient plutôt la consommation d'énergie et le coût.