σμ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 Lock-free›Module 4 · Tests et sûreté mémoire›Sujet 4.1·~55 minAvancé

Tester le code concurrent : détecteurs de courses et vérification de modèles

Position dans le parcoursModule 4 sur 4 · Sujet 1 sur 3
4.1 Tester le code concurrent : détecteurs de courses et vérification de modèles
—Score du lab— / 100XP total—Série—

6 min de lecture4 exemples de code

  1. 01Une trace et une question
  2. 02Lockset : quel verrou garde cette variable ?
  3. 03Happens-before : ce qui pourrait vraiment se chevaucher
  4. 04Quelle est la taille de l'espace ?
  5. 05Ce que vous allez construire

Exécuter tous les ordonnancements prouve un petit programme, mais cela coûte le nombre d'entrelacements. Les vrais outils prennent deux raccourcis. Un détecteur de courses observe une exécution et signale les variables qui pourraient se heurter dans un autre ordonnancement. Un vérificateur de modèles retient les états déjà vus. Pour deux threads de 10 étapes, c'est 184 756 ordonnancements contre 121 états.

01

Une trace et une question

Un détecteur de courses n'exécute pas de threads. Il lit une trace : chaque lecture et écriture de données partagées, chaque acquisition et libération d'un verrou. Nos programmes deviennent des listes de telles opérations, et interleave transforme un programme et un ordonnancement en la trace de cette exécution. Deux threads qui lisent et écrivent chacun x sans verrou donnent les quatre événements ci-dessous. Dans cet ordonnancement le thread 0 finit avant que le thread 1 commence, si bien qu'un test verrait la bonne réponse. La question du détecteur est autre : les deux threads auraient-ils pu se chevaucher ?

main.pyPython
def interleave(programs, schedule):    position = [0] * len(programs)    trace = []    for tid in schedule:        kind, name = programs[tid][position[tid]]        position[tid] += 1        trace.append((tid, kind, name))    return traceunlocked = [[("read", "x"), ("write", "x")], [("read", "x"), ("write", "x")]]trace = interleave(unlocked, [0, 0, 1, 1])print(trace)
[(0, 'read', 'x'), (0, 'write', 'x'), (1, 'read', 'x'), (1, 'write', 'x')]
02

Lockset : quel verrou garde cette variable ?

Le premier détecteur applique une discipline : toute variable partagée devrait être gardée par un verrou tenu à chaque accès. La règle du lockset suit les verrous que chaque thread détient et, pour chaque variable, fait l'intersection des ensembles de verrous détenus lors de ses accès. Si au moins deux threads ont touché la variable, que l'un l'a écrite et que l'intersection est vide, aucun verrou unique ne la protège : on signale une course potentielle. Le compteur sans verrou est signalé ; celui avec verrou ne l'est pas. La règle est bon marché et peut donner une fausse alerte. Dans le passage de témoin ci-dessous, le thread 0 écrit x puis le transmet au thread 1 par le verrou. Aucun verrou n'était tenu lors de l'écriture, donc la règle proteste, alors que les accès sont en fait ordonnés.

main.pyPython
def lockset_races(trace):    held = {}    accesses = {}    for tid, kind, name in trace:        locks = held.setdefault(tid, set())        if kind == "acquire":            locks.add(name)        elif kind == "release":            locks.discard(name)        else:            accesses.setdefault(name, []).append((tid, kind == "write", frozenset(locks)))    racy = []    for name, history in accesses.items():        threads = {tid for tid, _, _ in history}        writes = any(is_write for _, is_write, _ in history)        common = frozenset.intersection(*(locks for _, _, locks in history))        if len(threads) >= 2 and writes and not common:            racy.append(name)    return sorted(racy)locked = [[("acquire", "m"), ("read", "x"), ("write", "x"), ("release", "m")]] * 2guarded = interleave(locked, [0, 0, 0, 0, 1, 1, 1, 1])handoff = [(0, "write", "x"), (0, "acquire", "m"), (0, "release", "m"), (1, "acquire", "m"), (1, "release", "m"), (1, "read", "x")]print("unlocked:", lockset_races(trace))print("locked:  ", lockset_races(guarded))print("handoff: ", lockset_races(handoff))
unlocked: ['x']
locked:   []
handoff:  ['x']
DéfinitionRègle lockset d'Eraser

Signaler une variable quand deux threads y accèdent, que l'un écrit, et qu'aucun verrou n'est tenu à tous ses accès. Elle trouve un manque de discipline de verrouillage à partir d'une seule exécution, avec de fausses alertes là où l'ordre vient d'autre chose qu'un verrou commun.

03

Happens-before : ce qui pourrait vraiment se chevaucher

Le second détecteur pose la question exacte : existe-t-il une chaîne d'ordonnancements du premier accès au second ? Il existe deux sortes d'arêtes, l'ordre à l'intérieur d'un thread et la paire libération puis acquisition du même verrou. La relation happens-before est la fermeture des deux. Une horloge vectorielle par thread enregistre ce que le thread a vu ; une libération range l'horloge dans le verrou et une acquisition prend le maximum point par point avec elle. Un accès est une course si un accès antérieur conflictuel d'un autre thread a une époque que l'horloge courante n'a pas vue. Dans le passage de témoin, la libération et l'acquisition relient l'écriture à la lecture, donc pas de course. Il ne signale que les vraies courses, mais seulement dans les accès qu'il a observés.

main.pyPython
def hb_races(trace):    clocks = {}    lock_clocks = {}    history = {}    racy = set()    for tid, kind, name in trace:        clock = clocks.setdefault(tid, {tid: 1})        if kind == "acquire":            for u, value in lock_clocks.get(name, {}).items():                clock[u] = max(clock.get(u, 0), value)        elif kind == "release":            lock_clocks[name] = dict(clock)            clock[tid] += 1        else:            is_write = kind == "write"            for u, was_write, epoch in history.get(name, []):                if u != tid and (is_write or was_write) and clock.get(u, 0) < epoch:                    racy.add(name)            history.setdefault(name, []).append((tid, is_write, clock[tid]))    return sorted(racy)print("unlocked:", hb_races(trace))print("locked:  ", hb_races(guarded))print("handoff: ", hb_races(handoff))
unlocked: ['x']
locked:   []
handoff:  []
Utilisez les deux, dans cet ordre

Les vrais outils comme ThreadSanitizer utilisent happens-before parce qu'il ne donne pas de fausse alerte sur la trace qu'il voit. La règle du lockset est la façon la moins coûteuse de trouver une variable que personne ne garde jamais, et un bon premier contrôle. Aucune des deux ne remplace un vérificateur de modèles pour une structure sans verrou, dont la justesse ne vient pas du tout des verrous.

04

Quelle est la taille de l'espace ?

Un vérificateur de modèles visite le même programme mais retient les états. Dans nos programmes un état est seulement l'avancement de chaque thread, donc avec des threads de n étapes il y a (n1 + 1) × (n2 + 1) × ... états, quel que soit le nombre d'ordonnancements qui y mènent. Les threads compteurs ci-dessous donnent les chiffres pour les vraies données, avec les ordonnancements et les états côte à côte.

main.pyPython
def count_states(lengths):    total = 1    for n in lengths:        total *= n + 1    return totaldef counter_programs(ups, locked):    programs = []    for count in ups:        program = []        for _ in range(count):            program += ([("acquire", "m")] if locked else []) + [("read", "x"), ("write", "x")] + ([("release", "m")] if locked else [])        programs.append(program)    return programsreturns = [float(r) for r in load_bars()["close"].pct_change().dropna()][:6]ups = [sum(1 for r in returns[start::2] if r > 0) for start in (0, 1)]for name, lock in (("unlocked", False), ("locked", True)):    programs = counter_programs(ups, lock)    lengths = [len(p) for p in programs]    trace = interleave(programs, serial_schedule(lengths))    print(name, len(trace), "events", lockset_races(trace), hb_races(trace), count_schedules(lengths), "schedules,", count_states(lengths), "states")
unlocked 8 events ['x'] ['x'] 28 schedules, 21 states
locked 16 events [] [] 1820 schedules, 65 states
Ce que disent les vraies donnéesdeux threads comptant les jours de hausse des 6 premiers rendements SPY (1 et 3)
Compteur sans verrouxsignalé par les deux détecteurs
Compteur avec verrounonesignalé par aucun
Ordonnancements du programme avec verrou1,820à vérifier un par un
États du programme avec verrou65à retenir en tout

Une seule trace en série suffit aux deux détecteurs pour trouver le verrou manquant, et le programme avec verrou est propre. L'avantage du vérificateur de modèles sur la force brute est le facteur entre 1 820 et 65, et il croît sans limite : pour 10 et 10 étapes, c'est 184 756 contre 121. C'est pourquoi la vérification de modèles peut couvrir des programmes que l'énumération simple n'atteindrait jamais, alors qu'un détecteur de courses coûte un passage sur une exécution.

DéfinitionRéduction de l'espace d'états

Retenir un état la première fois qu'il est atteint et ne jamais le développer deux fois. De nombreux ordonnancements atteignent le même état, donc le coût passe du nombre d'ordonnancements au nombre d'états.

05

Ce que vous allez construire

Le labo suit le cours : la trace d'une exécution, la règle du lockset, la règle happens-before avec horloges vectorielles, le décompte des états, les programmes des threads compteurs, et l'étude des deux détecteurs et des deux tailles d'espace sur les jours de hausse de SPY.

  • Fonction · questionS'appuie sur
  • interleave(programs, schedule)Que s'est-il passé, dans quel ordre ?De l'arithmétique simple
  • lockset_races(trace)Quelles variables n'ont aucun verrou commun ?De l'arithmétique simple
  • hb_races(trace)Quelles variables ont des accès non ordonnés ?De l'arithmétique simple
  • count_states(lengths)Combien d'états faut-il retenir ?De l'arithmétique simple
  • counter_programs(ups, locked)Que font les threads compteurs ?De l'arithmétique simple
  • study(bars)Les détecteurs s'accordent-ils sur SPY ?counter_programsinterleavelockset_raceshb_racescount_states

À retenir

  • Un détecteur lit une exécution. Une trace en série où rien n'a mal tourné révèle quand même le verrou manquant.
  • Lockset est bon marché et bruyant, happens-before est exact. Un passage de témoin par verrou est une fausse alerte pour lockset et ordonné pour les horloges vectorielles.
  • Retenez les états, pas les ordonnancements. 1 820 ordonnancements contre 65 états, et 184 756 contre 121 pour deux threads de 10 étapes.
Vérification rapide
Le thread 0 écrit x puis libère le verrou m ; le thread 1 acquiert m puis lit x. L'écriture précède la libération. Le détecteur à horloges vectorielles signale-t-il une course sur x ?
Choisissez une réponse.

Sujet suivant : le code sans verrou a un danger de plus qu'aucun détecteur ni vérificateur ne peut réparer après coup : quand un thread libère un nœud qu'un autre thread lit encore. Les pointeurs de danger et la récupération par époques bâtissent les deux façons classiques de libérer la mémoire en sûreté, et l'explorateur montre un usage après libération sous chaque ordonnancement que vous n'avez pas empêché.

Lab : ce qui est noté

6 tâches · 100 pts
  • La trace d'une exécution10 pts
  • La règle du lockset25 pts
  • Happens-before avec horloges vectorielles30 pts
  • Les états d'un vérificateur de modèles5 pts
  • Les threads compteurs5 pts
  • L'étude sur la table SPY25 pts
Explorateur · lab-lf-09
▾ lab-lf-09
▾ data
…
bash · dans le navigateur
learner@quantcraft:~/lab-lf-09$
Python pas encore chargéPythonLn 1, Col 1Spaces: 4UTF-8

Réinitialiser ce sujet ?

Tout le contenu de lab-lf-09 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