σμimplied vol · KKmax(S−K,0)udβρᵢⱼfat tailsPnL · drawdownyield · TdSₜ = μSₜ dt + σSₜ dWₜC = S·N(d₁) − K·e⁻ʳᵀ·N(d₂)σₚ² = wᵀΣwd₁ = [ln(S/K) + (r + σ²⁄2)T] ⁄ σ√T∂V/∂t + ½σ²S²·∂²V/∂S² + rS·∂V/∂S − rV = 0Sharpe = (Rₚ − R_f) ⁄ σₚVaR₉₉ = μ − 2.33σρ = Cov(X,Y) ⁄ σₓσᵧE[Rₚ] = Σ wᵢ·E[Rᵢ]φ(x) = e^(−x²/2) ⁄ √2πE[Rᵢ] = R_f + βᵢ(E[Rₘ] − R_f)dXₜ = θ(μ − Xₜ)dt + σ dWₜσ²ₜ = ω + α·ε²ₜ₋₁ + β·σ²ₜ₋₁C − P = S − K·e⁻ʳᵀΔ = ∂V/∂S Γ = ∂²V/∂S²p = (e^(rΔt) − d) ⁄ (u − d)rₜ = ln(Pₜ ⁄ Pₜ₋₁)E[Xₜ₊₁ | ℱₜ] = Xₜf* = (bp − q) ⁄ bz = (x − μ) ⁄ σP(N=k) = λᵏe^(−λ) ⁄ k!Θ = −∂V/∂Tν = S·φ(d₁)·√Tmin wᵀΣw s.t. wᵀμ = μ*∫Σ∂∞λ
uantCraft AcademyFeuilles de routeParcours
Ctrl K
AO
+1,2 k apprenants
ConnexionRejoindre ↗
Feuilles de routeParcours
AO
+1,2 k apprenants
Connexion
  • English
  • Français
  • Español
  • العربية
  • Português
  • Deutsch
  • 日本語
  • 中文
σμimplied vol · KKmax(S−K,0)udβρᵢⱼfat tailsPnL · drawdownyield · TdSₜ = μSₜ dt + σSₜ dWₜC = S·N(d₁) − K·e⁻ʳᵀ·N(d₂)σₚ² = wᵀΣwd₁ = [ln(S/K) + (r + σ²⁄2)T] ⁄ σ√T∂V/∂t + ½σ²S²·∂²V/∂S² + rS·∂V/∂S − rV = 0Sharpe = (Rₚ − R_f) ⁄ σₚVaR₉₉ = μ − 2.33σρ = Cov(X,Y) ⁄ σₓσᵧE[Rₚ] = Σ wᵢ·E[Rᵢ]φ(x) = e^(−x²/2) ⁄ √2πE[Rᵢ] = R_f + βᵢ(E[Rₘ] − R_f)dXₜ = θ(μ − Xₜ)dt + σ dWₜσ²ₜ = ω + α·ε²ₜ₋₁ + β·σ²ₜ₋₁C − P = S − K·e⁻ʳᵀΔ = ∂V/∂S Γ = ∂²V/∂S²p = (e^(rΔt) − d) ⁄ (u − d)rₜ = ln(Pₜ ⁄ Pₜ₋₁)E[Xₜ₊₁ | ℱₜ] = Xₜf* = (bp − q) ⁄ bz = (x − μ) ⁄ σP(N=k) = λᵏe^(−λ) ⁄ k!Θ = −∂V/∂Tν = S·φ(d₁)·√Tmin wᵀΣw s.t. wᵀμ = μ*∫Σ∂∞λ
uantCraft AcademyFeuilles de routeParcours
Ctrl K
AO
+1,2 k apprenants
ConnexionRejoindre ↗
Feuilles de routeParcours
AO
+1,2 k apprenants
Connexion
  • English
  • Français
  • Español
  • العربية
  • Português
  • Deutsch
  • 日本語
  • 中文
Parcours Cache locality›Module 3 · La machine au sens large›Sujet 3.6·~55 minAvancé

Prédiction de branchement

Position dans le parcoursModule 3 sur 3 · Sujet 6 sur 7
3.6 Prédiction de branchement
—Score du lab— / 100XP total—Série—

6 min de lecture3 exemples de code

  1. 01Pourquoi le processeur devine
  2. 02Des prédicteurs qui se souviennent
  3. 03Ce que coûte une erreur, et pourquoi trier aide
  4. 04Ce que vous allez construire

Un processeur n'attend pas de savoir dans quel sens va un if : il devine et commence à travailler. Une bonne devinette est gratuite, une mauvaise jette le travail, soit environ 15 cycles perdus. La fréquence des bonnes devinettes dépend des données : la même boucle sur les mêmes rendements SPY coûte 8,3 fois moins de cycles une fois les rendements triés.

01

Pourquoi le processeur devine

Un processeur fonctionne comme une chaîne de montage : pendant qu'une instruction s'exécute, les suivantes sont déjà lues et décodées. Un if casse la chaîne, car l'instruction suivante dépend de sa réponse, qui n'est pas encore prête. Le processeur fait donc une prédiction de branchement et avance dessus. Si elle est juste, rien n'est perdu. Si elle est fausse, le travail fait sur le mauvais chemin est jeté et la chaîne redémarre, ce qui coûte environ 15 cycles sur un cœur moderne, autant qu'une visite au cache L2. Nous notons le résultat d'un branchement True quand il est pris. Dans une boucle de trading, un branchement est par exemple if daily_return > 0, et le flux de ses résultats est simplement un True ou un False par jour.

main.pyPython
def taken_sequence(values, threshold):    return [bool(value > threshold) for value in values]def always_taken_mispredicts(outcomes):    return sum(1 for outcome in outcomes if not outcome)returns = [0.004, -0.012, 0.007, 0.001, -0.003, 0.009]outcomes = taken_sequence(returns, 0)print(outcomes)print(always_taken_mispredicts(outcomes), "misses out of", len(outcomes))
[True, False, True, True, False, True]
2 misses out of 6

Sur six jours, quatre ont eu un rendement positif : le branchement est donc pris quatre fois et non pris deux fois. Le prédicteur le plus simple, toujours pris, se trompe exactement sur les deux résultats False. Il a raison aussi souvent que le branchement est pris, et il ignore l'historique.

02

Des prédicteurs qui se souviennent

Les vrais processeurs utilisent l'historique. Le prédicteur à 1 bit se souvient de ce que le branchement a fait la dernière fois et devine la même chose. Il est bon quand les résultats viennent par séries, mais il paie deux fois chaque résultat isolé : une fois à son arrivée et une fois à la reprise de la série. Le prédicteur à 2 bits corrige cela avec un compteur de 0 à 3 qui s'arrête aux deux bouts. Il devine « pris » à 2 et 3, avance d'un cran vers chaque résultat, et il lui faut donc deux résultats d'affilée contre sa devinette pour changer d'avis. Nous partons de « non pris » pour le 1 bit et de 1 pour le compteur.

main.pyPython
def one_bit_mispredicts(outcomes):    guess = False    wrong = 0    for outcome in outcomes:        if guess != outcome:            wrong += 1        guess = outcome    return wrongdef two_bit_mispredicts(outcomes):    counter = 1    wrong = 0    for outcome in outcomes:        if (counter >= 2) != outcome:            wrong += 1        counter = min(3, counter + 1) if outcome else max(0, counter - 1)1    return wrongruns = [True] * 4 + [False] * 4 + [True] * 4stray = [True] * 6 + [False] + [True] * 6for name, stream in (("runs", runs), ("one stray", stray)):    print(name, "| 1-bit", one_bit_mispredicts(stream), "| 2-bit", two_bit_mispredicts(stream))
runs | 1-bit 3 | 2-bit 5
one stray | 1-bit 3 | 2-bit 2
  1. 1Le compteur avance d'un cran vers le résultat et s'arrête à 0 et 3 : c'est ce qui le rend saturant.

Sur les séries de quatre, les deux prédicteurs se trompent trois fois (la première devinette et les deux changements). Avec un seul False isolé parmi des résultats pris, le 1 bit se trompe trois fois et le 2 bits une seule, car le compteur passe de 3 à 2 et devine encore « pris ». C'est pourquoi les vrais processeurs préfèrent les compteurs. Gardez en tête l'autre face : quand les résultats n'ont aucun motif, aucun prédicteur ne peut faire mieux que deviner la réponse la plus fréquente.

La prédiction concerne les données, pas le code

Le même if est bon marché sur des données prévisibles et coûteux sur des données aléatoires. Ne réécrivez pas un branchement pour le rendre « plus rapide » avant de savoir à quoi ressemblent ses résultats sur les vraies données ; mesurez d'abord les erreurs.

03

Ce que coûte une erreur, et pourquoi trier aide

Pour transformer les erreurs en temps, on les additionne : chaque branchement coûte un cycle de base et chaque erreur ajoute une pénalité de 15. Voici l'expérience qui donne son nom à ce cours : prenons les 4 000 premiers rendements quotidiens de SPY et exécutons le branchement return > 0 deux fois, sur les rendements dans l'ordre des dates et sur les mêmes rendements triés. Trier ne change rien aux nombres, seulement à leur ordre, mais cela transforme les résultats d'un pile ou face en deux longues séries : tous les False d'abord et tous les True ensuite.

main.pyPython
def cycles(n_branches, mispredicts, penalty=PENALTY, base=BASE_CYCLES):    return base * n_branches + penalty * mispredictsdef sorted_stream(values, threshold):    return taken_sequence(sorted(values), threshold)returns = [float(r) for r in load_bars()["close"].pct_change().dropna()][:4000]for name, stream in (("unsorted", taken_sequence(returns, 0)), ("sorted", sorted_stream(returns, 0))):    wrong = two_bit_mispredicts(stream)    print(name, "| always", always_taken_mispredicts(stream), "| 1-bit", one_bit_mispredicts(stream), "| 2-bit", wrong, "|", cycles(len(stream), wrong), "cycles")
unsorted | always 1800 | 1-bit 2035 | 2-bit 1950 | 33250 cycles
sorted | always 1800 | 1-bit 1 | 2-bit 2 | 4030 cycles

Dans l'ordre des dates, 2 200 des 4 000 rendements sont positifs : le branchement est pris 55 % du temps et il passe d'une réponse à l'autre presque sans motif. Toujours pris se trompe 1 800 fois, le 1 bit 2 035 fois et le 2 bits 1 950 fois. Les prédicteurs à mémoire font même pire que le simple pari, car ils suivent chaque bascule. Triés, les résultats sont 1 800 False suivis de 2 200 True, et le 2 bits ne se trompe que 2 fois. Le coût passe de 33 250 à 4 030 cycles, 8,3 fois moins, avec exactement les mêmes données et la même boucle.

DéfinitionPénalité d'erreur de prédiction

Les cycles perdus quand le processeur s'est trompé sur un branchement : il jette le travail fait sur le mauvais chemin et remplit de nouveau la chaîne, environ 15 cycles sur un cœur moderne.

La même astuce en NumPy et en C++

Une comparaison vectorisée comme returns > 0 n'a pas de branchement par élément, elle n'en souffre donc pas du tout. En C++, une boucle avec un if sur des données aléatoires est le cas classique où trier les données d'abord, ou écrire la condition comme de l'arithmétique, la rend plusieurs fois plus rapide.

Ce que disent les vraies données4 000 rendements quotidiens SPY, branchement return > 0, pénalité de 15 cycles
Toujours pris, ordre des dates1,800erreurs sur 4 000
2 bits, ordre des dates1,950erreurs, pire que toujours pris
2 bits, trié2erreurs : une série, puis l'autre
Gain du tri8.3×cycles en moins (33 250 → 4 030)
Cycles du prédicteur à 2 bits
Ordre des dates33250
Trié4030
04

Ce que vous allez construire

Le labo suit le cours : les résultats d'un branchement, les trois prédicteurs, le coût en cycles, le flux trié et une étude des rendements SPY dans l'ordre des dates et triés.

  • Fonction · questionS'appuie sur
  • taken_sequence(values, threshold)Que fait le branchement pour chaque valeur ?De l'arithmétique simple
  • always_taken_mispredicts(outcomes)Combien de fois « toujours pris » se trompe-t-il ?De l'arithmétique simple
  • one_bit_mispredicts(outcomes)Et un prédicteur qui retient le dernier résultat ?De l'arithmétique simple
  • two_bit_mispredicts(outcomes)Et un compteur saturant ?De l'arithmétique simple
  • cycles(n_branches, mispredicts)Combien cela coûte-t-il en tout ?De l'arithmétique simple
  • sorted_stream(values, threshold)Et si les valeurs étaient triées d'abord ?taken_sequence
  • study(bars)Combien le tri fait-il gagner sur SPY ?sorted_streamtwo_bit_mispredictscycles

À retenir

  • Le processeur devine chaque `if`. Une bonne devinette est gratuite ; une mauvaise coûte environ 15 cycles, et un prédicteur à mémoire ne bat le simple pari que si les résultats viennent par séries.
  • Un compteur bat un seul bit. Le compteur à 2 bits ignore un résultat isolé là où le 1 bit paie deux fois.
  • L'ordre change le coût. Les mêmes 4 000 rendements coûtent 33 250 cycles dans l'ordre des dates et 4 030 triés, car le tri transforme un pile ou face en deux séries.
Vérification rapide
Un branchement a les résultats True ×4, False ×4, True ×4. Le prédicteur à 1 bit part de « non pris ». Combien de fois se trompe-t-il ?
Choisissez une réponse.

Sujet suivant : le projet final réunit toute la compétence. On vous donne une table de ticks et une charge qui la lit ; vous la réorganisez en lignes, en colonnes ou en blocs, comptez les défauts de cache de chaque disposition avec le cache que vous avez construit, les chiffrez avec le modèle de latence du premier sujet et prouvez quelle disposition gagne.

Lab : ce qui est noté

7 tâches · 100 pts
  • Résultats d'un branchement5 pts
  • Toujours pris10 pts
  • Un prédicteur à 1 bit15 pts
  • Un compteur saturant à 2 bits20 pts
  • Le coût en cycles10 pts
  • Valeurs triées10 pts
  • L'étude sur la table SPY30 pts
Explorateur · lab-cl-12
▾ lab-cl-12
▾ data
…
bash · dans le navigateur
learner@quantcraft:~/lab-cl-12$
Python pas encore chargéPythonLn 1, Col 1Spaces: 4UTF-8

Réinitialiser ce sujet ?

Tout le contenu de lab-cl-12 revient aux fichiers de départ et le terminal est vidé. Votre solution actuelle sera perdue : cette action est irréversible.

Sera réinitialisé
  • main.py → code de départ
  • Historique du terminal et sortie des tests
  • Modifications non soumises
Sera conservé
  • Meilleur score du lab
  • Votre XP et votre série
  • Progression du cours et notes