The wiki page is under active construction, expect bugs.

Differences

This shows you the differences between two versions of the page.

Link to this comparison view

Both sides previous revisionPrevious revision
Next revision
Previous revision
statnice:bakalar:b4b36pdv [2025/06/01 10:24] – [Ricart-Agrawalův algoritmus] mistrjirkastatnice:bakalar:b4b36pdv [2025/06/04 22:52] (current) – [Logické hodiny a kauzalita] zapleka3
Line 1: Line 1:
-===== Modely a architektury paralelních a distribuovaných systémů; prostředky pro jejich implementaci a základní algoritmy. =====+====== Modely a architektury paralelních a distribuovaných systémů; prostředky pro jejich implementaci a základní algoritmy. ======
 [[https://fel.cvut.cz/cz/education/bk/predmety/47/02/p4702806.html|B4B36PDV]] [[https://cw.fel.cvut.cz/b232/courses/b4b36pdv/lectures/start|Webové stránky předmětu]] [[https://pdv.pages.fel.cvut.cz/lectures/|Nové webové stránky předmětu]] [[https://fel.cvut.cz/cz/education/bk/predmety/47/02/p4702806.html|B4B36PDV]] [[https://cw.fel.cvut.cz/b232/courses/b4b36pdv/lectures/start|Webové stránky předmětu]] [[https://pdv.pages.fel.cvut.cz/lectures/|Nové webové stránky předmětu]]
  
Line 19: Line 19:
     - **Volba lídra v DS** – algoritmy pro volbu lídra a jejich vlastnosti.     - **Volba lídra v DS** – algoritmy pro volbu lídra a jejich vlastnosti.
  
-/* +===== Paralelní systémy/výpočty ===== 
-################################################################################################################################################################################### + 
-################################################################################################################################################################################### +===== 1. Hardwarová podpora pro paralelní výpočty ===== 
-################################################################################################################################################################################### +V téhle kapitole se zaměříme na tojak je moderní hardware navržený takaby zvládal výpočty paralelně – tedy několik operací současněUkážeme si různé úrovně a typy paralelismuod jednoduchého rozdělení instrukcí na více částí (pipelining), přes využití více výpočetních jader (vlákna, procesy), až po extrémní paralelismus na GPU. Pochopení těchto principů je zásadní pro efektivní vývoj výkonných softwarových systémů.
-*/ +
-===== Hardwarová podpora pro paralelní výpočty ===== +
-**(super)skalární architekturypipelining, spekulativní vyhodnocování, vektorové instrukce, vláknaprocesy, GPGPUHierarchie cache pamětí** +
-  * (super)skalární architekturypipelining, spekulativní vyhodnocování, Hierarchie cache paměti: (statnice:bakalar:b0b35apo) +
-  * vlákna, procesy: (statnice:bakalar:b4b35osy)+
  
 ==== Vektorové instrukce ==== ==== Vektorové instrukce ====
-Často nazývané **SIMD** instrukce single instruction, multiple data.+Často nazývané **SIMD** instrukce – *single instruction, multiple data*. 
 + 
 +Vektorové instrukce vykonávají jednu danou operaci na velkém množství dat najednou.   
 +Pracují nad speciálními vektorovými registry – až 512bit.
  
-ektorové instrukce vykonávají jednu danou operaci na velkém množství dat najednou. +  * To znamená, že například můžete násobit dvě celé pole čísel "najednou", místo po jednotlivých prvcích
-Pracují nad specielními vektorovými registery - až 512bit.+  * Umožňují značné zrychlení ve výpočetně náročných paralelizovatelných aplikacích, jako např. násobení matic.
  
-Umožňují značné zrychlení ve výpočetně náročných paralelizovatelných aplikacích, jako např. násobení matic. +Moderní kompilátory jsou schopné automaticky vektorizovat části kódu.   
-Moderní kompilátory jsou schopné automaticky vektorizovat části kódu. +Pro znatelné zrychlení je ale často nutná manuální implementace.
-Pro znatelné zrychlení je ale nutná manuální implementace.+
  
-Narozdíl od paralelizace mezi jádry se nemusí řešit problémy synchronizace a značné latence mezi vlákny.+Na rozdíl od paralelizace mezi jádry se nemusí řešit problémy synchronizace a latence mezi vlákny.  
 Nevýhoda je často velmi komplikovaná implementace. Nevýhoda je často velmi komplikovaná implementace.
  
 Existuje mnoho standardů, nejznámější jsou x86 standardy: Existuje mnoho standardů, nejznámější jsou x86 standardy:
-  * AVX    starší, velmi rozšířený, až 128bit registery +  * AVX – starší, velmi rozšířený, až 128bit registry 
-  * AVX2   - novější, velmi rozšířený, oproti AVX2 přidává nové operace a 256bit registery +  * AVX2 – novější, přidává nové operace a 256bit registry 
-  * AVX512 - oproti AVX2 přidává mnoho dalších operací a 512bit registerynení moc rozšířený,nových procesorů pouze u AMD +  * AVX512 – mnoho dalších operací a 512bit registryméně rozšířený, většinou jen high-end CPU
- +
- +
  
 ==== Instruction-Level Parallelism (ILP) ==== ==== Instruction-Level Parallelism (ILP) ====
-  * **Definice:** ILP udává, kolik *nezávislých* instrukcí může procesor potenciálně spustit paralelně v rámci *jednoho* programu/ vlákna.  +  * **Definice:** ILP udává, kolik *nezávislých* instrukcí může procesor potenciálně spustit paralelně v rámci *jednoho* programu/vlákna. 
-  * **Zdroj ILP:** nezávislé aritmetické operace, nezávislé paměťové přístupy, nezávislé větvení. Množství ILP se výrazně liší dle typu úlohy – numerické simulace mají ILP bohaté, kryptografie spíše skromné+  * **Zdroj ILP:** nezávislé aritmetické operace, nezávislé paměťové přístupy, nezávislé větvení. 
-  * **Využití ILP:** pipelining (překrytí fází), superskalární/vliw architektury (více jednotek), out-of-order execution (dynamické přeuspořádání)   +  * **Využití ILP:** pipelining, superskalární/VLIW architektury, out-of-order execution.
- +
-----+
  
 ==== Pipelining ==== ==== Pipelining ====
-  * **Princip:** Instrukční cesta se dělí na fáze IF → ID → EX → MEM → WB; během jednoho taktu jsou různé instrukce v různých fázích, takže **propustnost** rosteač latence jednotné instrukce zůstává. +  * **Princip:** Instrukční cesta se dělí na fáze IF → ID → EX → MEM → WB.   
-  * **Teoretický zisk:** při ideálních podmínkách 1 instrukce / takt (IPC = 1) i na skalárním jádřeskutečný zisk limitují hazardy a branch-stall cykly.  +    To umožňuje, aby různé instrukce byly zpracovávány současně v různých fázích – zvyšuje se **propustnost**, i když latence jednotlivé instrukce zůstává stejná
-  * **Superpipelining:** 10 stupňů; kratší fáze dovolují vyšší frekvenci, ale zvyšují penalizaci při flushi (chybná predikce, datové konflikty).  +  * **Teoretický zisk:** až 1 instrukce / takt (IPC = 1); limitují ho hazardy a větvení
-  * **Hazardy:**   +  * **Superpipelining:** více než 10 stupňů; vyšší frekvence, ale větší penalizace při chybách predikce. 
-    *  datové (RAW, WAR, WAW) – řešení *forwarding**stall*y*register renaming*,   +  * **Hazardy:** 
-    *  řídicí (branch) – predikce skoků + BTB,   +    * datové – řeší se forwardingemstallypřejmenováním registrů 
-    *  strukturální – duplikace funkčních jednotek.   +    * řídicí – predikce skoků + branch target buffer (BTB) 
- +    * strukturální – řeší se duplikací jednotek
-----+
  
 ==== Skalární vs. superskalární procesory ==== ==== Skalární vs. superskalární procesory ====
 +
 === Skalární procesor === === Skalární procesor ===
-  * **Jednoproudová (1-wide) architektura:** front-end vydá a back-end vykoná **jednu instrukci** za takt (IPC ≤ 1). :contentReference[oaicite:13]{index=13}   +  * 1 instrukce za takt (IPC ≤ 1) 
-  * Pipelining zde paralelizuje **různé fáze** různých instrukcí, nikoli jejich současné vykonání.  +  * jednodušší architektura 
 +  * využívá pipelining
  
 === Superskalární procesor === === Superskalární procesor ===
-  * **N-wide architektura:** fetch/decode i back-end mohou spustit až **N nezávislých instrukcí** v jednom taktu → IPC > 1 (např. 4-wide jádro → max instr/takt). :contentReference[oaicite:14]{index=14}   +  * více instrukcí za takt (IPC > 1) 
-  * Paralelizuje **stejné fáze** různých instrukcí a kombinuje se s pipeliningem; tím se ILP využívá naplno.  +  * N-wide architektura (např. 4-wide = až instrukce/takt) 
 +  * využívá ILP pomocí paralelního vykonávání více instrukcí
  
 ==== Podtypy superskalární architektury ==== ==== Podtypy superskalární architektury ====
-  * **Statická superskalární**   +  * **Statická:** paralelně jen instrukce jdoucí v kódu za sebou; při závislostech nastává stall. 
-    *  Vydává paralelně jen instrukce jdoucí v kódu za sebou; při závislosti nastává stall. Menší řídicí logika, úspornější.  +  * **Dynamická:** out-of-order execution a přejmenování registrů → lepší využití hardwaru.
-  * **Dynamická superskalární**   +
-    *  **Out-of-order execution** **přejmenování registrů** (ROB + Reservation Stations) spouští libovolné připravené instrukce, skryje latenci cache a zvedá IPC. +
-    *  Vyžaduje přesnou predikci skoků a složitější hardwarové řízení; spotřeba i plocha čipu rostou.+
  
-=== Shrnutí rozdílu === +==== Shrnutí rozdílu ==== 
-  * **Skalární CPU** ⇒ 1 instrukce/takt, architektonicky jednodušší  +  * **Skalární CPU** ⇒ 1 instrukce/takt, jednoduché, predikovatelné chování
-  * **Superskalární CPU** ⇒ instrukcí/takt; **statická** verze je jednodušší, **dynamická** (OOO) dosahuje vyššího využití ILP za cenu složitosti  +  * **Superskalární CPU** ⇒ více instrukcí/takt, složitější řízení, vyšší výkon.
-==== GPGPU ====+
  
-* **General-Purpose computing on Graphics Processing Units** (GPGPU) využívá původně grafické akcelerátory jako masivně paralelní výpočetní jednotky+==== GPGPU ==== 
-Typický desktopový GPU (např. NVIDIA Ampere) má tisíce jednoduchých jader sdružených do **streaming multiprocessorů (SM)**, které spouštějí stovky až tisíce vláken v konceptu **warpů** (32 vláken běžících lock-step). +  * **General-Purpose computing on Graphics Processing Units** (GPGPU) využívá původně grafické karty pro obecné výpočty
 +  * GPU obsahují tisíce jednoduchých jader sdružených do **streaming multiprocessorů (SM)**.   
 +    * Ty spouštějí stovky až tisíce vláken najednou ve skupinách (warp – 32 vláken běžících lock-step).
  
 === Architektura a omezení === === Architektura a omezení ===
 +  * **Výpočetní hierarchie:** GPC → SM → block → warp → thread
 +  * **Sdílené prostředky:**
 +    * shared memory + L1 cache (~128 KB)
 +    * max 64 warpů / SM (až 2048 vláken)
 +    * ~255 registrů na vlákno – méně registrů = vyšší obsazenost
 +  * **Paměťová hierarchie:**
 +    * L1/shared: ~33 cyklů
 +    * L2: až 2 TB/s (~200 cyklů)
 +    * HBM2: 1.5 TB/s (~290 cyklů)
 +    * Host ↔ GPU: PCIe/NVLink – důležitá je minimalizace přenosů
  
-* **Hierarchie výpočetních bloků:** GPC $\rightarrow$ SM $\rightarrow$ processing block $\rightarrow$ warp $\rightarrow$ thread. +=== Výpočetní výkon v praxi === 
-* **Sdílené prostředky SM:**+Threadripper 3990X + RTX 4090: 
 +  single-thread49 GFLOPS 
 +  multi-thread CPU: 3.7 TFLOPS 
 +  CPU + GPGPU: **100 TFLOPS**
  
-  128 KB *shared memory* + L1 cache +=== Programovací modely === 
-  * max 64 warpů ≈ 2048 vláken na SM +  * **CUDA C/C++** – pro NVIDIA 
-  * 64 k 32-bit registrů; limit ≈ 255 registrů na vlákno (nižší počet registrů = vyšší obsazenost)  +  * **OpenCL** – otevřený standard 
-* **Paměťová hierarchie GPU:**+  * **SYCL** – moderní C++ model 
 +  * **OpenMP target** – direktivy pro GPU
  
-  * L1/shared: ~33 cyklů +=== Typické úlohy === 
-  * L2: až 2 TB/s (~200 cyklů) +  * lineární algebrastrojové učeníray-tracing, simulace, video encoding…
-  * HBM2: 1.5 TB/s (~290 cyklů) +
-  * Host ↔ GPU: PCIe Gen4 ~32 GB/sNVLink ~50 Gb/s pár signálů +
-  * ⇒ minimalizovat a slučovat přenosy mezi CPU ↔ GPUi za cenu většího lokálního výpočtu. +
  
-=== Výpočetní výkon v praxi ===+=== Výhody === 
 +  * vysoký výkon (tisíce FP jednotek) 
 +  * efektivita (TFLOPS/W) 
 +  * specializované instrukce (Tensor Cores…)
  
-| Konfigurace                   | Single-thread | Multi-thread CPU | CPU + GPGPU    | +=== Nevýhody === 
-| Threadripper 3990X + RTX 4090 | 49 GFLOPS     | 3 .7 TFLOPS      | **100 TFLOPS** |+  paměťová latence 
 +  omezené registry/shared memory 
 +  přenosy mezi host ↔ device 
 +  warp divergence (větvení) 
 +  * vendor lock-in (CUDA)
  
-*Na stejném stroji GPU dodá ~×25 výkon oproti plně vytíženému CPU;+=== Shrnutí === 
 +GPGPU nabízí **řádově vyšší výkon** než běžné CPU, ale vývoj je složitější. Správné řízení paměti a vláken, spolu s vhodným programovacím modelem, je klíčem k úspěchu.
  
-=== Programovací modely ===+==== Vlákna a procesy ==== 
 +  * **Proces** je samostatná jednotka běhu programu, má vlastní adresní prostor (paměť), popisovače souborů atd. 
 +    * Spuštění nového procesu je relativně nákladné. 
 +    * Vhodné pro více zcela oddělených výpočetních úloh (např. mikroservisy, více programů najednou). 
 +  * **Vlákno (thread)** je lehčí jednotka běhu uvnitř procesu. 
 +    * Sdílí paměť a další prostředky s ostatními vlákny procesu. 
 +    * Přepínání mezi vlákny je levnější než mezi procesy. 
 +    * Vhodné pro paralelizaci uvnitř jedné aplikace – více úloh najednou (např. UI, I/O, výpočty). 
 +    * Pozor na **synchronizaci a souběhy (race conditions)** – nutnost použití zámků, mutexů apod.
  
-* **CUDA C/C++** – proprietárníde-facto standard pro NVIDIA. +==== Hierarchie cache pamětí ==== 
-* **OpenCL 3.0** – otevřený standard podporovaný NVIDIAAMD, IntelARM Mali+Moderní CPU mají vícestupňovou **hierarchii cache**, která zajišťuje rychlý přístup k často používaným datům: 
-* **SYCL 2020** – moderní C++20 nad OpenCL/CUDA/Level Zero; přenositelné šablony a paralelní STL+  * **L1 cache** – nejmenší (např. 32 KB)ale nejrychlejší (~4 cykly)zvlášť pro instrukce a data
-* **OpenMP 5.x target/teams** – off-load direktivy do GPUrelativně snadná migrace existujícího kódu+  * **L2 cache** – větší (např. 256 KB), pomalejší (~12 cyklů), většinou sdílena mezi jádry
-Další možnostiVulkan ComputeDirectX 12 ComputeThrust (C++ algoritmy nad CUDA).+  * **L3 cache** – největší (např. 16 MB)ještě pomalejší (~30–40 cyklů), sdílená mezi všemi jádry
 +  **RAM** – mnohem větší, ale o řád(y) pomalejší než cache (~100 ns). 
 +  * **Zásada locality:** datakterá byla použita nedávno nebo jsou blízko soběmají vyšší šanci být v cache – tím se výrazně snižuje průměrná latence paměťových přístupů.
  
-=== Typické úlohy ===+Efektivní využití cache (např. přístup po řádcích, nikoliv po sloupcích v 2D poli) může mít zásadní vliv na výkon.
  
-* lineární algebra (matmulBLAS), strojové eníray-tracingsimulace částic/CFDkryptografievideo-encoding.+===== 2. Komplikace v paralelním programování ===== 
 +Když programujeme paralelně – tedy více věcí běží najednou – můžeme narazit na různé složitostikteré se sekvenčních (jednovláknových) programů vůbec neřeší. Typickými komplikacemi jsou souběh (když dvě vlákna zasahují do stejných dat zároveň)uváznutí (když se navzájem blokují)nebo falešné sdílení (když si vlákna "lezou do cache"i když pracují na různých datech). V téhle kapitole se na tyhle problémy podíváme a vysvětlíme sijak a proč k nim dochází.
  
-=== Výhody ===+==== Race Condition (souběh) ==== 
 +  * **Souběh (race condition)** nastává, když dvě nebo více vláken přistupuje ke **stejným datům** a **alespoň jedno z nich je mění**, přičemž jejich pořadí není řízené (např. zámky, mutexy). 
 +  * To znamená, že výsledek programu **závisí na načasování** – někdy to „vyjde dobře“, jindy ne. 
 +  * Typickým příkladem je inkrementace sdílené proměnné – pokud není chráněná zámkem, může být výsledek menší, než kolik se očekává.
  
-* **Obrovský teoretický throughput** (tisíce FP jednotek). +Například: 
-* **Energetická efektivita**TFLOPS/W často výrazně lepší než CPU. +<code cpp> 
-* **Specializované instrukce** (Tensor Cores, BF16/FP16).+// špatně – race condition 
 +for (int i = 0; i < 1000; i++
 + counter++; 
 +
 +</code>
  
-=== Nevýhody ===+  * Řešení: 
 +    * Použít **synchronizační primitiva** – mutexy, atomické operace, semafory apod. 
 +    * Nebo použít **thread-local** kopie a sloučit je až nakonec.
  
-* **Paměťová latence** mimo L1/shared, nutnost koalesovaného přístupu. +==== Deadlock (uváznutí) ==== 
-* **Omezená velikost registrů a shared memory** $\rightarrow$ ladění obsazenosti. +  * **Uváznutí (deadlock)** nastává, když dvě nebo více vláken čeká na zdroj, který drží to druhé – a žádné se nepohne dál
-* **Drahé přenosy host ↔ device** (PCIe)+  Jednoduše řečeno: vlákno A čeká na zámek, který drží vlákno B, a vlákno B čeká na zámek, který drží vlákno A.
-* **Divergence warpů** (větvení) snižuje výkon – kód musí být datově paralelní+
-**Vendor lock-in** (CUDA) či různé úrovně podpory standardů na HW.+
  
-=== Shrnutí === +Například: 
-GPGPU nabízí **řádově vyšší paralelní výkon** než mainstreamové CPU, ale vyžaduje uvědomělou práci pamětí a thread-level paralelismem. Správná volba programovacího modelu (CUDA ↔ OpenCL/SYCL ↔ OpenMP targetumožní radikální zrychlení numericky-intenzivních úloh při zachování udržitelnosti kódu. +<code cpp> 
-/* +// pseudokód potenciálním deadlockem 
-################################################################################################################################################################################### +thread1: 
-################################################################################################################################################################################### +lock(A
-################################################################################################################################################################################### +lock(B) 
-*+ 
-===== Komplikace v paralelním programování ===== +thread2: 
-**soubě(race condition), uváznutí (deadlock), iluze sdílení (false sharing)** +lock(B) 
-  * souběh (race condition), uváznutí (deadlock)(statnice:bakalar:b4b35osy)+lock(A) 
 +</code> 
 + 
 +  Aby deadlock mohl nastat, musí být splněny tyto 4 podmínky (tzv. Coffmanovy podmínky)
 +    * **Vzájemné vyloučení** – zdroje nejsou sdílené (jen jedno vlákno je může mít)
 +    * **Zadržení a čekání** – vlákno drží jeden zámek a čeká na další. 
 +    * **Neodnímatelnost** – zdroj (zámeknemůže být násilně odebrán. 
 +    * **Cyklické čekání** – existuje cyklus závislostí mezi vlákny. 
 +  * Prevence: 
 +    * Dodržovat **pevné pořadí zamykání zdrojů**. 
 +    * Používat **timeouty** u zámků. 
 +    * Využít algoritmy pro **deadlock detection** a recovery.
  
 ==== False Sharing ==== ==== False Sharing ====
-Nastává když dvě vlákna přistupují k **různým** promněným na stejné cache line. +Nastávákdyž dvě vlákna přistupují k **různým** proměnným, které se ale nachází ve **stejné cache line**
-Když jedno vlákno zapíše do cache svojí variabletak se automaticky invalidují všechna data na stejné cache line. +  Když jedno vlákno zapíše do své proměnné, CPU invaliduje celou cache line – i když druhé vlákno pracuje na jiné proměnné ve stejné linii. 
-Pokud tedy tyto dvě vlákna konstantně tyto dvě promněné měnídochází ke značnému poklesu výkonu.+  * Pokud se to děje častodochází k **velkému zpomalení**, protože cache se neustále synchronizuje mezi jádry. 
 +  * Řešení: 
 +    * **Zarovnat proměnné** tak, aby každá byla na vlastní cache line (např. pomocí `alignas(64)`)
 +    * Přidat „padding“ mezi proměnnékteré používají různá vlákna.
  
 ==== True Sharing (není komplikace) ==== ==== True Sharing (není komplikace) ====
-Nastává když dvě vlákna přistupují **stejné** promně+Nastávákdyž dvě vlákna přistupují ke **stejné** proměnné. 
 +  * Narozdíl od false sharing je to **žádoucí chování**, pokud víme, co děláme – ale musí být správně synchronizované. 
 +  * Typicky jde o sdílené čítače, fronty, stavové proměnné atd.
  
-Narozdíl od false sharing chtěná (pokud umíte programovat) vlastnost, je nutná korektní synchronizace. 
- 
-/* 
-################################################################################################################################################################################### 
-################################################################################################################################################################################### 
-################################################################################################################################################################################### 
-*/ 
    
-===== Podpora paralelního programování v C a C++ ===== +===== 3. Podpora paralelního programování v C a C++ ===== 
-**pthreads, thread, jthread, atomic, mutexlock_guard**+Existuje více úrovní podpory – od nízkoúrovňových knihoven jako `pthreads` v Caž po elegantní moderní rozhraní ve stylu `std::thread``std::jthread``std::mutex` nebo `std::atomic` v C++. Pokud chceme psát vícevláknové programy efektivně a bezpečněje důležité pochopitkdy a jak jednotlivé nástroje použít – a čím se liší.
  
-  * pthreads: POSIX vlákna (pthreads) jsou standardní rozhraní pro práci s vlákny v jazyku CUmožňují vytvářet, spravovat a synchronizovat vlákna v aplikacích. Příklady použití zahrnují vytváření vláken pomocí pthread_create()synchronizaci pomocí mutexů (pthread_mutex_lock(), pthread_mutex_unlock()), podmínkových proměnných (pthread_cond_wait(), pthread_cond_signal()) a semaforů (sem_init(), sem_wait(), sem_post())+==== POSIX vlákna (pthreads) ==== 
-  * thread: C++11 přidává standardní knihovnu pro práci s vlákny, která zahrnuje třídu std::thread pro vytváření a správu vláken. Synchronizace se provádí pomocí mutexů (std::mutex)podmínkových proměnných (std::condition_variablea dalších synchronizačních primitiv. +  * `pthreads` (POSIX Threads) je **standardní rozhraní v jazyku C** pro práci s vlákny. 
-  jthread: C++20 přidává ídu std::jthread, která automaticky spravuje životní cyklus vlákna a umožňuje snadnější použití lambda výrazyNa rozdíl od std::thread se jthread automaticky uvolní přskončení funkce.+  * Poskytuje funkce pro: 
 +    * **vytváření vláken**: `pthread_create()
 +    * **synchronizaci vláken**: 
 +      * **mutexy**: `pthread_mutex_lock()``pthread_mutex_unlock()
 +      * **podmínkové proměnné**: `pthread_cond_wait()``pthread_cond_signal()
 +      * **semafory**: `sem_init()``sem_wait()``sem_post()
 + 
 +Je to výkonný, ale nízkoúrovňový nástroj – programátor musí vše spravovat ručněHodí se pro systémy, kde je důležitá kontrola nad výkonem a kompatibilita se standardem POSIX. 
 + 
 +==== std::thread (C++11) ==== 
 +  * Od C++11 je k dispozici standardní knihovna pro vlákna – `std::thread`. 
 +  * Umožňuje jednoduché vytvoření a správu vláken pomocí objektově orientovaného rozhraní. 
 +    * Vlákno spustíme předáním funkce (nebo lambdy) do konstruktoru. 
 +    * Synchronizace se řeší pomocí `std::mutex``std::condition_variablea dalších primitiv. 
 + 
 +Výhodou oproti `pthreads` je **vyšší přehlednost, typová bezpečnost a integrace s C++ idiomy** (např. RAII, STL, lambdy). 
 + 
 +==== std::jthread (C++20) ==== 
 +  * Od C++20 přibyla nová ída `std::jthread`, která automaticky spravuje vlákno. 
 +    * Při zničení objektu se vlákno **automaticky ukončí** (`join()` nebo `detach()`). 
 +    * Má vestavěnou podporu pro **zrušení vlákna** pomocí `stop_token`. 
 +    * Výborně se hodí pro práci s **RAII přístupem** bezpečnější práci vláknem. 
 + 
 +To znamená, že se snižuje riziko zapomenutého `join()` a tím potencionálních chyb.
  
 ==== Mutex a Lock Guard ==== ==== Mutex a Lock Guard ====
-Mutex (mutual exclusion) je synchronizační primitivumkteré zajišťuje vzájemné vyloučení přístupu k sdíleným prostředkům mezi vlákny. Používá se k ochraně kritických sekcí kóduaby se zabránilo souběžnému ístupu více vláken těmto prostředkům. V C++ se mutexy implementují pomocí třídy std::mutex a jejích variant (např. std::recursive_mutex, std::timed_mutex).+**Mutex** (*mutual exclusion*) je synchronizační nástrojkterý **zajišťuje, že jen jedno vlákno istupuje určité části kódu najednou** – typicky tzv*kritické sekci*. 
 +  * V C++ se používá `std::mutexjeho varianty: 
 +    * `std::recursive_mutex``std::timed_mutex`, …
  
-Ukázkový kód:+Ukázka (ruční zamykání/odemykání):
 <code cpp> <code cpp>
 #include <iostream> #include <iostream>
Line 190: Line 253:
         mtx.lock();         mtx.lock();
         // Kritická sekce         // Kritická sekce
-        std::cout << "Thread is executing critical section." <<code std::endl;+        std::cout << "Thread is executing critical section." << std::endl;
         mtx.unlock();         mtx.unlock();
     };     };
Line 204: Line 267:
 </code> </code>
  
-Moderní c++ umožnuje použít **std::lock_guard** pro automatické zamykání odemykání mutexuTo zjednodušuje správu mutexů a zabraňuje zapomenutí odemknout mutex v ípadě výjimky nebo předčasného ukončení funkce. +*Moderní C++* doporučuje použít `std::lock_guard`, který mutex **automaticky zamkne odemkne** – tím se předejde chybám (např. zapomenutí `unlock()` i výjimce):
-<code cpp>+
  
 +<code cpp>
 #include <iostream> #include <iostream>
 #include <thread> #include <thread>
Line 228: Line 291:
     return 0;     return 0;
 } }
- 
 </code> </code>
  
 ==== Atomic ==== ==== Atomic ====
-Atomic je typ datového typukterý zajišťuje atomické operace na sdílených proměnných mezi vlákny. V C++ se atomické operace implementují pomocí třídy std::atomic. Atomic proměnné zajišťujíže operace na nich jsou prováděny jako nedělitelný celekcož zabraňuje problémům se souběhem+**Atomic** proměnné umožňují provádět operacekteré jsou **nedělitelné (atomické)** – tím pádem **bezpečné vůči souběhu**, aniž by bylo potřeba používat mutex. 
-podstatě je to jako mutexale bez zamykání a odemykání. Atomic proměnné jsou rychlejší než mutexy (Pokud to hw podporuje), protože nevolají do jádra pro mutex, pokud hw nepodporuje atomické operacetak se použije mutex. Atomic proměnné jsou v podstatě implementovány jako atomické instrukce na procesoru, které zajišťují, že operace na těchto proměnných jsou prováděny jako nedělitelný celek. VV praxi jsou na hardweru většinou (jsem si téměř jistý žvždy) implementovány jako instrukce nad normálními proměnnými, nic jako datový typ není na hw.+  * V C++ se používá `std::atomic<T>` – kde `T` může být např`int``bool`pointer apod
 +    pozadí to využívá **atomické instrukce CPU** (pokud jsou dostupné)takže jsou **rychlejší než mutexy**. 
 +    * Pokud HW atomické instrukce neumífallback je přes mutex. 
 + 
 +V praxi jsou tedy `std::atomic` velmi efektivní pro jednoduché sdílené proměnné – například čítače:
  
 <code cpp> <code cpp>
Line 261: Line 327:
 </code> </code>
  
 +  * Jinými slovy – `std::atomic` je ideální, pokud chceš **rychlou synchronizaci bez složitých zámků**, ale nepotřebuješ složitou logiku jako čekání, notifikace, podmínky apod.
  
- +===== 4. Podpora paralelního programování v OpenMP ===== 
-/* +OpenMP je velmi populární knihovna pro paralelizaci v jazycích C, C++ a Fortran. Umožňuje snadno spouštět více vláken pomocí direktiv vkládaných přímo do zdrojového kódu. V této kapitole se podíváme na to, jak funguje základní model *fork-join*jak se v OpenMP definují úlohy (tasky), a jaké máme k dispozici prostředky pro synchronizaci. Ukážeme si i různé varianty implementací OpenMP a přehled direktivkteré programátorům umožňují jemné řízení paralelismu.
-################################################################################################################################################################################### +
-################################################################################################################################################################################### +
-################################################################################################################################################################################### +
-*/ +
-===== Podpora paralelního programování v OpenMP ===== +
-**sériově-paralelní model uspořádání vláken (fork-join)paralelizovatelná úloha (task region), různé implementace specifikace. Direktivy: parallelfor, sections, task, barrier, critical, atomic.**+
  
 ==== Sériově-paralelní model uspořádání vláken (fork-join) ==== ==== Sériově-paralelní model uspořádání vláken (fork-join) ====
-Fork-join model je základní exekuční schéma OpenMP. Program začíná jedním *master* vláknem, které po vstupu do direktivy **`parallel`** vytvoří tým dalších vláken (*fork*). Všechna vlákna spolupracují v daném paralelním regionu a na jeho konci se implicitní bariérou zase spojí do jediného vláka (*join*).   +Fork-join model je základní exekuční schéma OpenMP. Program začíná jedním *master* vláknem, které po vstupu do direktivy **`parallel`** vytvoří tým dalších vláken (*fork*). Všechna vlákna spolupracují v daném paralelním regionu a na jeho konci se implicitní bariérou zase spojí do jediného vlákna (*join*). 
-Výhody: jednoduchá správa vlákensdílená paměť a do jisté míry edvídatelná synchronizace.+  Výhody: 
 +    * jednoduchá správa vláken 
 +    * sdílená paměť 
 +    * edvídatelné chování a synchronizace
  
-<markdown> +Ukázkový kód: 
-- **Explicitní synchronizace uvnitř regionu** +<code cpp>
-  - `#pragma omp barrier` – ruční bariéra. +
-  - `single` / `master` – sekci vykoná pouze jedno vlákno. +
-  - `ordered` – zachování pořadí iterací ve smyčce. +
-  - `critical(name)` nebo `atomic` – sériový přístup k proměnné či úseku. +
- +
-- **Omezení serializace**   +
-  Přidej klauzuli `nowait`, pokud bariéru na konci *work-sharing* konstrukce nepotřebuješ. +
- +
-- **Vnořená paralelizace**   +
-  Výchozí stav je vypnutý (`OMP_NESTED=TRUE` nebo `omp_set_max_active_levels()` ji zapne). +
- +
-- **Tasky a datové závislosti**   +
-  `task`, `taskgroup`, `depend(in|out|inout: A)` umožní jemnější DAG nad stávajícím fork-join skeletonem. +
- +
- +
-Ukázkový kód (C)+
- +
-```c+
 #include <omp.h> #include <omp.h>
 #include <stdio.h> #include <stdio.h>
Line 307: Line 353:
     return 0;     return 0;
 } }
-```` +</code>
-</markdown> +
-Poznámka: **Fork-join v OpenMP vytváří vlákna, nikoli samostatné procesy**. Implementace obvykle stojí na POSIX/pthreads; celý tým sdílí stejnou adresní prostor a paměť. (Procesy se používají jen ve speciálních hybridních modelech např. MPI+OpenMP.)+
  
-===== Různé implementace OpenMP specifikace ===== +  * Poznámka: OpenMP vytváří **vlákna**, nikoliv procesy – všechna sdílí paměť. Vlákna jsou obvykle implementována pomocí POSIX/pthreads.
-Běžně používané kompilátory a knihovny OpenMP (stav jaro 2025):+
  
-<markdown> +Další možnosti a doplňky modelu: 
-| Implementace | Kompilátor / knihovna | Úroveň podpory | Poznámky | +  * `#pragma omp barrier` – ruční synchronizační bod. 
-|--------------|----------------------|----------------|----------| +  `single`, `master` – sekci vykoná pouze jedno vlákno. 
-**GCC 14+ (libgomp)** | `-fopenmp| 5.2 (téměř úplná) | Dlouhodobá open-source volba| +  * `ordered– zachování pořadí iterací ve smyčce
-**LLVM/Clang 18+ (libomp)** | `-fopenmp| 5.2 (většina) | Dobrá diagnostika, funguje i na Windows| +  * `critical`, `atomic` – zajištění sériového přístupu ke sdíleným proměnným
-**Intel oneAPI (icx/icpx + iomp5)** | `-qopenmp| 5.2 | Vysoce optimalizovaný runtime, podporuje offload na GPU| +  * `nowait– zabrání implicitní bariéře na konci paralelního bloku
-| **IBM XL / LLVM-based*`-qsmp=omp| 5.1 | Optimalizace pro POWER| +  * `OMP_NESTED=TRUEnebo `omp_set_max_active_levels()` – aktivuje vnořenou paralelizaci
-**AMD AOCC 4.x** | Clang + libomp | 5.1 | Laděno pro Zen architekturu+  `task`, `taskgroup`, `depend(...)` – jemné řízení závislostí mezi úlohami (viz níže).
-| **Cray/AMD ROCm (OpenMP Offload)** | Clang | 5.2 | Zaměřeno na akcelerátory a HPC. | +
-</markdown>+
  
-==== Přehled hlavních direktiv OpenMP ====  +==== Paralelizovatelná úloha (task region) ==== 
-<markdown>+Pomocí direktivy `task` lze definovat úlohy, které mohou být nezávisle spouštěny paralelně. Oproti `parallel for`, které rozděluje smyčku, `task` umožňuje paralelizovat **libovolné části kódu**, včetně rekurze a nepravidelné struktury výpočtu. 
 +  * Tasky lze organizovat do `taskgroup`. 
 +  * Pomocí `depend(in|out|inout: var)` lze řídit pořadí a závislosti. 
 +  * `detach`, `priority` – umožňují pokročilou kontrolu provádění.
  
-* **`parallel`** – vytvoří tým vláken; podporuje klauzule `if`, `num_threads`, `default`, `shared`, `private`, … +Příklad: 
-* **`for` / `do`** – paralelizuje smyčku; plánování (`schedule(static|dynamic|guided)`), `collapse(n)`, `reduction`, `nowait`. +<code cpp> 
-* **`sections` / `section`** – spustí nezávislé bloky kódu souběžně. +#pragma omp task depend(out: A
-* **`task`** – vytvoří paralelizovatelnou úlohu, volitelně s `depend`, `priority`, `detach`. +  // vypočítáme A 
-* **`barrier`** – explicitní synchronizační bod všech vláken týmu. +}
-* **`critical [ (name]`** – sekce, do které může současně vstoupit jen jedno vlákno; pojmenování odděluje nezávislé kritické sekce. +
-* **`atomic`** – nejlehčí synchronizace pro jednoduché operace na sdílené proměnné (`update`, `read`, `write`, …).+
  
-</markdown>+#pragma omp task depend(in: A) { 
 +  // použijeme A 
 +
 +</code>
  
-==== Příklad zapojení všeho dohromady ==== +==== Různé implementace OpenMP specifikace ==== 
-Ukázka kombinuje `parallel`, `for`, tasky a závislosti:+OpenMP je pouze specifikace – jednotlivé kompilátory ji implementují v různé míře.
  
-<code c>+Přehled nejznámějších implementací (stav 2025):* 
 +  * **GCC 14+ (libgomp)** – `-fopenmp`, verze 5.2 (téměř úplná), open-source klasika. 
 +  * **Clang 18+ (libomp)** – `-fopenmp`, verze 5.2 (většina), funguje i na Windows. 
 +  * **Intel oneAPI (icx/icpx + iomp5)** – `-qopenmp`, verze 5.2, velmi výkonný runtime, podpora GPU. 
 +  * **IBM XL (LLVM-based)** – `-qsmp=omp`, verze 5.1, optimalizace pro POWER. 
 +  * **AMD AOCC 4.x** – laděno pro AMD Zen, Clang + libomp. 
 +  * **Cray/ROCm** – zaměřeno na GPU/offload (OpenMP target). 
 + 
 +==== Přehled hlavních direktiv OpenMP ==== 
 +**`#pragma omp parallel`**   
 +  * Vytvoří tým vláken. 
 +  * Podporuje: `num_threads`, `if`, `default`, `shared`, `private`, … 
 +**`for` / `do`**   
 +  * Paralelizace smyčky. 
 +  * Možnosti: `schedule(static|dynamic|guided)`, `collapse(n)`, `reduction`, `nowait`. 
 +**`sections` / `section`**   
 +  * Spustí nezávislé bloky kódu paralelně (např. různé funkce najednou). 
 +**`task`**   
 +  * Vytvoří úlohu; možnost specifikace závislostí přes `depend(...)`. 
 +  * Další klauzule: `priority`, `detach`. 
 +**`barrier`**   
 +  * Explicitní synchronizační bod – všechna vlákna musí dojít na toto místo. 
 +**`critical [(name)]`**   
 +  * Zamezí více vláknům vstup do stejné sekce kódu najednou. 
 +  * Jméno odděluje různé nezávislé sekce. 
 +**`atomic`**   
 +  * Odlehčená synchronizace pro jednoduché operace s proměnnou (inkrementace, zápis, čtení…). 
 + 
 +==== Příklad zapojení všeho dohromady ==== 
 +<code cpp>
 #include <omp.h> #include <omp.h>
 #include <stdio.h> #include <stdio.h>
Line 352: Line 426:
         {         {
             int sum = 0;             int sum = 0;
-             + 
-            #pragma omp parallel for reduction(+\:sum) nowait+            #pragma omp parallel for reduction(+ : sum) nowait
             for (int i = 0; i < 1e6; ++i)             for (int i = 0; i < 1e6; ++i)
                 sum += i;                 sum += i;
-            +
             printf("Sum done: %d\n", sum);             printf("Sum done: %d\n", sum);
         }         }
Line 364: Line 438:
             printf("Post-processing...\n");             printf("Post-processing...\n");
         }         }
-    }   // implicit taskgroup -> čekáme na všechny tasky+    }   // implicit taskgroup – čeká se na všechny tasky
     return 0;     return 0;
 +}
 +</code>
 + 
 +Tento příklad ukazuje vytvoření tasku se závislostí `depend(out: sum)`, paralelní redukci vnitřní smyčky, a následný task, který čeká na výsledek (`depend(in: sum)`).
  
-} </code> 
  
 +===== 5. Techniky dekompozice programu =====
 +Paralelní programy často potřebují být rozděleny na menší části – a to nejen podle funkcí, ale hlavně podle **práce**, kterou je třeba vykonat. Tato kapitola se věnuje technikám, jak takovou práci rozdělit mezi vlákna nebo úlohy (tasky), a jak přitom řešit problémy jako nevyvážená zátěž nebo závislosti mezi úlohami. Pochopení těchto principů ti pomůže psát efektivnější a škálovatelnější paralelní kód.
  
-/+==== Statické × dynamické rozdělení práce ==== 
-################################################################################################################################################################################### +  **Statické rozdělení** – práce (např. iterace smyčky) se rozdělí předem mezi všechna vlákna. 
-################################################################################################################################################################################### +    * Např. pomocí `schedule(static[, chunk])` v OpenMP. 
-################################################################################################################################################################################### +    * Výhodou je **nízký overhead** (prakticky žádná synchronizace). 
-*/+    * Nevýhodou je, že **u nepravidelných úloh může být některé vlákno přetížené**, zatímco jiné skončí dříve a zahálí. 
 +  * **Dynamické rozdělení** – práce se přiděluje **za běhu** podle toho, které vlákno je volné. 
 +    * Používá se `schedule(dynamic[, chunk])`. 
 +    * Výhodou je lepší **vyvážení zátěže**. 
 +    * Nevýhodou je **vyšší režie kvůli synchronizaci**. 
 +  * **Guided plánování** – kompromis mezi statickým a dynamickým: 
 +    * `schedule(guided[, chunk])` začíná velkými bloky a postupně zmenšuje chunk velikost. 
 +    * Vhodné pro případy, kdy práce trvá různě dlouho a časem ubývá. 
 +  * Další možnosti: 
 +    * `schedule(runtime)` – výběr plánování je ponechán na hodnotě proměnné `OMP_SCHEDULE`. 
 +    `schedule(auto)` – nechá výběr plánu na implementaci OpenMP.
  
-===== Techniky dekompozice programu ===== +==== Thread-pool a fronta úkolů (work-stealing) ==== 
-**statické a dynamické rozdělení práceThread-pool fronta úkolů. Balancování a závislosti (dependencies).**+  OpenMP (od verze 3.0) podporuje **tasky**, které jsou plánovány na vlákna z **persistentního thread-poolu**. 
 +    * Vlákna jsou vytvořena v prvním paralelním regionu **zůstávají aktivní** i pro další úseky programu. 
 +  * Každé vlákno má vlastní **lokální deque** (obousměrnou frontu), do které si vkládá nové úkoly. 
 +  * **Work-stealing**: pokud vlákno nemá co dělat, zkusí si **„ukrást“ úkol** z cizí fronty. 
 +    * Tento přístup pomáhá **automaticky vyvažovat zátěž**. 
 +    * Push/pop na vlastní frontě jsou většinou **lock-free**; zámky se používají jen při krádeži.
  
-<markdown> +==== Balancování zátěže ==== 
-### Statické × dynamické rozdělení práce +Vyrovnané rozdělení práce je klíčem k výkonu paralelního programu. 
-**Statické rozdělení** (`schedule(static[,chunk])`) přiřadí iterace/vlákna předem, minimální overhead, ale hrozí nevyváženost u nepravidelné práce  +  **smyček**: 
-**Dynamické rozdělení** (`schedule(dynamic[,chunk])`přiděluje iterace za běhu podle aktuálního vytížení; víc synchronizace, ale lepší vyrovnání  +    * Volba správného `schedule(...)` plánu
-**Guided** (`schedule(guided[,chunk])`) začíná velkými bloky a postupně zmenšuje, kompromis mezi overheadem a vyvážením.   +    Velikost `chunkuovlivňuje granularitu přidělené práce
-Další volby: `runtime` (čte z `OMP_SCHEDULE`), `auto` (nechá výběr na runtime).+    * `collapse(n)` – slučuje více vnořených smyček do jednéčímž zvyšuje počet iterací pro paralelizaci. 
 +  * U **tasků**: 
 +    * Work-stealing pomáhá s dynamickým plánováním. 
 +    * Pomocí `priority(expr)` lze zvýhodnit důležité úkoly. 
 +    * Heuristiky runtime mohou **omezit vznik malých tasků** (např. jejich inline-ing→ lepší škálování.
  
-### Thread-pool a fronta úkolů (work-stealing+==== Závislosti (dependencies==== 
-- První paralelní region vytvoří **persistentní tým** vláken – thread-pool – který se recykluje pro všechny další regiony.   +Tasky mohou mít mezi sebou **závislosti**, které určují pořadí provádění. V OpenMP se deklarují pomocí `depend(...)`. 
-- Každé vlákno udržuje **lokální deque** s tasky; když jeho fronta zeje prázdnotou, **ukradne** (stealsúkol z cizí → vyrovnání zátěže bez centrální zámky  +  * Typy závislostí: 
-- Fronty se většinou zamykají jen při krádežitakžběžné push/pop jsou lock-free a škálují.+    `depend(in: X)` – task **potřebuje** data `X`. 
 +    `depend(out: X)` – task **produkuje** data `X`. 
 +    * `depend(inout: X)` – task `X` čte i zapisuje
 +    * Další pokročilé typy: `mutexinoutset``depobj` – pro specializované scénáře.
  
-### Balancování zátěže +Ukázkový kód: 
-- **Smyčky**volba plánu (`static`, `dynamic`, `guided`), velikost chunku, případně `collapse(n)` pro součtové smyčky.   +<code cpp> 
-- **Tasky**: work-stealing, `priority(expr)`, hromadné throttling limity a heuristiky kompilátoru (např. inline-ing malých tasků přes `if(too_small)`).+#pragma omp task depend(out:A
 +build_A();
  
-### Závislosti (dependencies) +#pragma omp task depend(in:A) depend(out:B) 
-- Tasks mohou deklarovat závislosti pomocí `depend` – `in`, `out`, `inout`, `mutexinoutset`, `depobj`.   +build_B();
-  ```c +
-  #pragma omp task depend(out:A) +
-  build_A(); +
-  #pragma omp task depend(in:A) depend(out:B) +
-  build_B(); +
-  #pragma omp task depend(in:B) +
-  use_B(); +
-````+
  
-* Runtime tvoří orientovaný acyklický graf; když jsou všechny závislosti splněné, task se stane „ready“ a fronty si ho mohou volně rozebrat.+#pragma omp task depend(in:B) 
 +use_B(); 
 +</code>
  
-  </markdown>+  * OpenMP runtime si z těchto závislostí **sestaví DAG (acyklický graf)**. 
 +  * Jakmile jsou všechny vstupní závislosti splněny, task je označen jako **„ready“** a zařadí se do fronty. 
 + 
 +Tato metoda umožňuje velmi jemné řízení výpočtu a paralelizace i **nepravidelných nebo stromových struktur** (např. kompilátory, plánovače, grafy). 
 + 
 +===== 6. Techniky dekompozice programu na příkladech ===== 
 +Abychom lépe pochopili různé způsoby paralelizace, ukážeme si je na konkrétních příkladech z oblasti řazení, lineární algebry a strojového učení. Uvidíme, jak lze algoritmy jako quick sort nebo násobení matic přirozeně rozdělit do paralelních částí a jaké techniky přitom použít – např. tasky, `collapse`, blokové zpracování či závislosti.
  
-===== Techniky dekompozice programu na příkladech ===== 
 ==== Řazení ==== ==== Řazení ====
 **quick sort a merge sort** **quick sort a merge sort**
  
 === Quick sort === === Quick sort ===
-<markdown> +  * **Proč „rozděl a panuj“?**   
-**Proč „rozděl a panuj“?**   +    *Divide*: vybereme pivot a jedním průchodem rozdělíme pole na část < pivot a část ≥ pivot.   
-  *Divide*: vyber pivot a pole jedním průchodem **rozděl** na část ↙︎ < pivot a část ↗︎ ≥ pivot.   +    *Conquer*: obě části rekurzivně řadíme – ideální místo pro vytvoření dvou paralelních úloh (*tasků*).   
-  *Conquer*: obě části se **řadí rekurzivně** – ideální příležitost spustit dva *taskysouběžně.   +    *Combine*: není třeba – dělení samo zajistí správné pořadí. 
-  *Combine*: žádné explicitní slévání není potřeba – rozdělení zaručilo správné pořadí. +  Paralelizace: po operaci `partition` spustíme levé větvení jako `omp task`, pravé běží ve vlákně dál
-Paralelizace: po operaci `partition` spustíme řazení levé části v `omp task`, zatímco aktuální vlákno pokračuje pravou částí (viz kód)+ 
-- Zdrojový kód: +<code cpp>
-```cpp+
 void qs(std::vector<int>& vec, int from, int to) { void qs(std::vector<int>& vec, int from, int to) {
     if (to - from <= base_size) {     if (to - from <= base_size) {
Line 440: Line 540:
     }     }
 } }
-````+</code>
  
-</markdown>+=== Merge sort === 
 +  * **Proč „rozděl a panuj“?**   
 +    * *Divide*: rekurzivní dělení pole na půlky.   
 +    * *Conquer*: každou půlku řadíme paralelně.   
 +    * *Combine*: dvě seřazené části spojíme lineárním `merge`. 
 +  * Paralelizace: obě rekurze běží jako tasky, po jejich dokončení proběhne `inplace_merge`.
  
-=== Merge sort ===  +<code cpp>
-<markdown> +
- +
-* **Proč „rozděl a panuj“?** +
- +
-  * *Divide*: pole se **rozpůlí** na levé a pravé podpole, dokud nedojdeme k malému úseku. +
-  * *Conquer*: každé z půlek se **třídí rekurzivně**, často opět ve dvou paralelních *tascích*. +
-  * *Combine*: dvě seřazené půlky se **slijí** lineárním `merge`, čímž vznikne seřazené pole větší velikosti. +
-* Paralelizace: dvě rekurzivní výzvy běží souběžně; po `taskwait` proběhne lineární slévání. +
-* Zdrojový kód: +
- +
-```cpp+
 void ms(std::vector<int>& vec, int from, int to) { void ms(std::vector<int>& vec, int from, int to) {
     if (to - from <= base_size) {     if (to - from <= base_size) {
Line 472: Line 566:
                        vec.begin() + to);                        vec.begin() + to);
 } }
-``` +</code>
- +
-</markdown>+
  
 ==== Numerická lineární algebra a strojové učení ==== ==== Numerická lineární algebra a strojové učení ====
Line 480: Line 572:
  
 === Paralelní násobení matice × vektor (SpMV/Dense MV) === === Paralelní násobení matice × vektor (SpMV/Dense MV) ===
-<markdown> +  * **Princip:** každý prvek $y_i$ je skalární součin řádku $A_i$ a vektoru $x$.   
-**Princip:** každý výstupní prvek $y_i$ je skalární součin $i$-tého řádku matice $A$ a vektoru $x$.   +    *Divide*: rozdělíme po řádcích.   
-  *Divide*: rozdělíme práci po řádcích (každý řádek je nezávislý).   +    *Conquer*: každý řádek počítá vlákno nezávisle.   
-  *Conquer*: vlákna počítají dot-product paralelně.   +    *Combine*: nepotřebujeme – každý výstupní `y[i]` je unikátní. 
-  *Combine*: žádná synchronizace není potřeba, každý řádek zapisuje vlastní $y_i$.+  * U husté matice: nejlepší přístup po řádcích (row-major).   
 +  * U řídké: paralelizace přes nenulové řádky (např. CSR formát).
  
-- **Paměťové locality:**   +<code cpp>
-  - U husté matice je nejvýhodnější iterovat *po řádcích* (souvislý přístup k paměti).   +
-  - U řídké (CSR) se iteruje přes neprázdné hodnoty; princip paralelizace je stejný. +
- +
-- **Ukázkový kód:** paralelizace přes `omp parallel for`; žádná redukce není nutná, protože každé vlákno píše do unikátního prvku `y[i]`. +
-</markdown> +
-<code c>+
 void matvec_dense(const std::vector<double>& A,   // ROWS × COLS, row-major void matvec_dense(const std::vector<double>& A,   // ROWS × COLS, row-major
                   const std::vector<double>& x,                   const std::vector<double>& x,
Line 507: Line 594:
  
 === Paralelní násobení dvou matic === === Paralelní násobení dvou matic ===
-<markdown> +  * **Naivní přístup**: každý prvek $c_{ij}$ samostatně – špatná cache-lokalita
-**Naivní prvek-po-prvku ($c_{ij}$)** škáluje, ale má špatnou cache-lokalitu (každé vlákno prochází celou druhou matici)+  
-**Blokové (tiling) rozdělení:**   +  * **Blokové (tiling) násobení**: 
-  1. *Divide* výslednou matici $C$ na bloky $B_{pq}$ o velikosti např64 × 64.   +    *Divide*: rozdělíme výstupní matici $C$ na bloky $B_{pq}$. 
-  2. *Conquer* — každý blok počítá jedno vlákno nebo task (`collapse(2)` či ruční tasking)  +    *Conquer*každý blok počítá vlákno/task – např. pomocí `collapse(2)`. 
-     ```c +    *Combine*není třeba – bloky jsou oddělené.
-     #pragma omp parallel for collapse(2) schedule(dynamic) +
-     for (int bi = 0; bi < N; bi += BS) +
-       for (int bj = 0; bj < N; bj += BS) +
-         for (int bk = 0; bk < N; bk += BS) +
-           multiply_block(A, B, C, bi, bj, bk, BS); +
-     ``` +
-  3. *Combine* není potřeba – bloky zapisují do oddělených částí $C$.+
  
-- **Work-stealing runtime** OpenMP při dynamickém rozvrhu zajistíže vlákna s prázdnou frontou převezmou volné bloky → lepší vyvážení. +<code cpp> 
-- **Prakticky:** pro výkon se používají knihovny pro matematické výpočty BLAS (OpenBLAS, MKL, libflames vysoce optimalizovaným blokováním a SIMD. +#pragma omp parallel for collapse(2) schedule(dynamic) 
-</markdown>+for (int bi = 0; bi < N; bi += BS) 
 +  for (int bj = 0; bj < N; bj += BS) 
 +    for (int bk = 0; bk < N; bk += BS) 
 +      multiply_block(AB, C, bi, bj, bk, BS); 
 +</code> 
 + 
 +  Pro vysoký výkonpoužívat BLAS knihovny (OpenBLAS, MKLse SIMD a tilingem.
  
 === Paralelní řešení systému lineárních rovnic (Gaussova eliminace) === === Paralelní řešení systému lineárních rovnic (Gaussova eliminace) ===
-<markdown> +  * **Gaussova eliminace (LU)**: v kroku $k$ odstraňujeme hodnoty pod pivotem $A_{kk}$. 
-**Gauss / LU**: pro každý krok $k$ lze *paralelněprovést eliminaci řádků $k+1..n-1$.   +    Paralelizujeme řádky $i = k+1..n-1$. 
-  - Závisí na pivotu $A_{kk}$, takže v kroku $k$ musí být ukončené všechny změny z kroku $k-1$.   +    * Nutná synchronizace mezi kroky – `taskwaitnebo sekvenční závislosti.
-  - `#pragma omp parallel forna vnější smyčce řádků, `nowait` nelze kvůli závislostem.+
  
-- **Ukázkový kód (bez otočných pivotů):** +<code cpp>
-</markdown> +
-<code c>+
 void gauss_elim(std::vector<double>& A) {         // ROWS × COLS, row-major void gauss_elim(std::vector<double>& A) {         // ROWS × COLS, row-major
     for (int k = 0; k < ROWS - 1; ++k) {     for (int k = 0; k < ROWS - 1; ++k) {
Line 545: Line 628:
 } }
 </code> </code>
-<markdown> 
-- **Iterativní metody (CG, GMRES, Jacobi)** se paralelizují ještě lépe: operují hlavně s mat-vec a vektorovými redukcemi, kde OpenMP poskytuje hotové `reduction(+:)` a `atomic`. 
  
-**GPU / accelerator off-load:** OpenMP 5.x pomocí `target teams distribute parallel for→ +  * **Iterativní metody (Jacobi, CG, GMRES)**: 
-  jádro se provede na GPU, host kontroluje synchronizaci. +    Lépe paralelizovatelné než Gauss – hlavně `matvec`, vektorové operace, redukce. 
-</markdown>+    * OpenMP poskytuje `reduction(+:)a `atomic` pro synchronizaci.
  
-===== Úvod do distribuovaných systémů (DS==== +  * **Off-load na GPU (OpenMP 5.x)**: 
-**charakteristiky DS, čas a typy selhání v DS**+    Pomocí `target teams distribute parallel for` lze výpočet přenést na GPU. 
 +    Host kontroluje synchronizaciale výpočet běží plně na zařízení.
  
-DS je soubor nezávislých, autonomních výpočetních jednotek propojených komunikační sítí. Výpočetní jednotky mezi sebou komunikují formou posílání zpráv za účelem určité formy spolupráce. 
  
-Výpočetní jednotky nemají sdílenou paměť, nesdílejí globální hodiny a selhávají na sobě nezávisle. 
  
-Kadý proces má své lokální hodiny, které nemusí ukazovat přesný čas (synchronizace je možná pouze s určitou přesností). 
  
-Je obtížné uvažovat o čase nejen kvůli absenci globálních hodin ale obecně nelze dát limit na komunikaci a délku výpočtu.+===== Distribuované výpočty/systémy ==== 
 + 
 +===== 1. Úvod do distribuovaných systémů (DS) ===== 
 +Distribuované systémy (DS) jsou o tom, jak spolupracují **nezávislé výpočetní jednotky**, které spolu **nesdílejí paměť ani hodiny**, ale komunikují pomocí zpráv. Jsou všude kolem nás – od cloudových služeb po blockchain nebo velké paralelní výpočty. Tato kapitola představuje základní charakteristiky DS, problematiku času a přehled typických typů selhání, se kterými musí každý návrh systému počítat. 
 + 
 +*Distribuovaný systém je*: 
 +  * soustava **autonomních procesů** spojených sítí, 
 +  * komunikace probíhá **pouze zprávami** (message passing), 
 +  * **žádná sdílená paměť**, **žádné globální hodiny**, 
 +  * procesy mohou **selhávat nezávisle**, 
 +  * každý proces má **lokální hodiny**, které mohou běžet různou rychlostí. 
 + 
 +Kvůli těmto vlastnostem je velmi těžké se v systému spolehlivě orientovat v čase – nemůžeme dát pevný limit na doručení zprávy nebo délku výpočtu
 + 
 +==== Asynchronní × Synchronní DS ==== 
 +Tyto modely popisují „jaký svět“ předpokládáme při návrhu distribuovaného algoritmu – tedy **jaké vlastnosti sítí, hodin a chování procesů** budeme brát jako dané.
  
-==== Asynchronní x Synchronní DS ==== 
-Popisují model světa ve kterém DS existuje. rozdělení synchroní a asynchroní popisuje množinu předpokladů, které budeme používat při psaní distribuovaného algoritmu.  
 === Synchronní model === === Synchronní model ===
-  * **Známe horní hranice** +  * Známe **horní hranice** pro: 
-    * maximální zpoždění zprávy +    * zpoždění zpráv, 
-    * maximální čas mezi dvěma kroky procesu +    * rychlost výpočtu, 
-    * maximální drift lokálních hodin +    * odchylku mezi hodinami. 
-  * Algoritmy můžeme psách v **fázích/kolech** a spolehlivě používat timeouty +  * Můžeme navrhovat algoritmy ve **fázích (kolech)** a spolehlivě používat timeouty. 
-  * praxi čistě synchroní sétě skoro neexistují+  * Takto předvídatelné sítě se v praxi téměř **nevyskytují** – je to spíš teoretický model. 
 === Asynchronní model === === Asynchronní model ===
-  * **Žádné limity** – zpráva může bloudit libovolně dlouho, proces může +  * **Žádné záruky** – zpráva může zůstat „viset“ libovolně dlouho, proces se může libovolně zdržet, hodiny se mohou rozjet
-    běžet tak pomalu, jak chce, hodiny se mohou rozejít+  * Timeouty **nejsou spolehlivé** – nelze říct, jestli se proces jen zpozdil“ nebo opravdu spadl“. 
-  * Timeout tedy **nemůže spolehlivě rozlišit** „zpomalení“ od pádu“. +  * Například slavný důkaz **FLP** ukazuje, že **v asynchronním modelu není možný deterministický konsensus**, pokud může padnout byť jen jeden proces.
-  * V tomto modelu je např. prokázáno (FLP), že deterministický konsensus +
-    s možností jediného pádu procesu **nelze** vyřešit. +
-=== Částečně synchronní model (real-world kompromis) === +
-  * Systém se může chovat asynchronně, ale po nejpozději neznámém čase **stabilně** do režimu, kdy už limity platí. +
-  * Většina produkčních protokolů (Raft, PBFT …) předpokládá právě tenhle model – jsou robustní vůči výkyvům, ale nakonec se „chytí“.  +
-  +
  
 +=== Částečně synchronní model ===
 +  * Realistický kompromis:
 +    * Systém se může nějakou dobu chovat asynchronně, **ale nakonec se „ustálí“** a začne dodržovat limity.
 +  * Většina reálných protokolů (např. **Raft**, **PBFT**) počítá právě s tímto modelem.
 +  * Výhoda: **odolnost vůči výpadkům i výkyvům**, ale **zaručená dohoda** nakonec proběhne.
  
-  - Selhání procesu +==== Typy selhání v distribuovaných systémech ==== 
-    crash / fail-stop - proces přestane vykonávatz algoritmus +V DS musíme počítat s tím, že **něco selže** – často i nepozorovaně. Rozlišujeme:
-    libovolné (byzantské) selhání - proces může pracovat déle a odpovídat na zprávy, ale vykonává chybný algoritmus +
-  - Selhání kanálu +
-    ztráta zprávy - zpráva se ztratí nedorazí do cílového procesu +
-    * partitioning - procesy jsou rozdělené do disjunktních množin, komunikace v nich je možná, ale mezi nimi ne +
-  - Selhání časování - doba odezvy procesu nebo přenosu zprávy vybočila z dohodnutého časového rozmezí+
  
-Předpoklady na komunikační kanál+  * **Selhání procesu**
-Spolehlivé doručování, žádná duplikacežádné vytvářenígarantované pořadí doručování+    * *crash / fail-stop* – proces přestane fungovat (nic už neposílá). 
 +    * *byzantské (libovolné) selhání* – proces dál fungujeale **chybně** (např. posílá nesmyslynebo se chová škodlivě). 
 +  * **Selhání kanálu**: 
 +    * *ztráta zprávy* – zpráva se ztratí v síti. 
 +    * *partitioning* – síť se rozdělí na **oddělené oblasti**, které spolu **nemohou komunikovat**. 
 +  * **Selhání časování**: 
 +    * odezva procesu nebo zprávy překročí **očekávané časové limity** – může jít o zpoždění i výpadek.
  
-===== Detekce selhání v DS ===== +==== Předpoklady na komunikační kanál ==== 
-**detektory selhání a jejich vlastnosti**+Distribuované algoritmy často **předpokládají vlastnosti komunikační vrstvy** – obvykle se uvažuje tzv. „ideální, ale pomalá“ síť:
  
-**úplnost** - každé selhání je časem detekováno alespoň jedním bezvadným procesem+  * **spolehlivé doručení** – žádné ztráty zpráv, 
 +  * **žádné duplikace** – zprávy se nedoručují víckrát, 
 +  * **žádné falešné zprávy** – proces nemůže dostat zprávu, kterou nikdo neposlal, 
 +  * **zachování pořadí** – zprávy mezi dvěma procesy dorazí ve stejném pořadí, v jakém byly odeslány.
  
-**přesnost** - nedochází k mylné detekci+Tato vlastnosti se často **simulují vyšší vrstvou (např. TCP)**, ale ne vždy – a je třeba s tím počítat při návrhu protokolu.
  
-nelze dosáhnout obou vlastností současně, je vyžadována  100% úplnost $\lt$ 100% přesnost.+===== 2. Detekce selhání v DS ===== 
 +V distribuovaných systémech je běžné, že některé procesy selžou – aby systém mohl dál správně fungovat, ostatní uzly se to musí nějak **včas a spolehlivě dozvědět**. K tomu slouží tzv. **detektory selhání**, které monitorují ostatní uzly (např. pomocí heartbeat zpráv) a označují ty, které nereagují, jako havarované.
  
 +Jenže v distribuovaném prostředí nemáme jistotu, jestli uzel selhal, nebo je jen dočasně pomalý (např. síťová prodleva) – a proto je detekce selhání **zásadně nejistá**. Tato kapitola rozebírá vlastnosti detektorů selhání, různé typy heartbeat protokolů a praktický algoritmus SWIM.
  
 +==== Vlastnosti detektorů selhání ====
 +  * **Úplnost (completeness)**  
 +    * Každé skutečné selhání je **časem detekováno** alespoň jedním bezchybným uzlem.
 +  * **Přesnost (accuracy)**  
 +    * Detektor **nemá falešné poplachy** – neoznačí proces za havarovaný, pokud ve skutečnosti běží.
  
-Frekvence selhání roste lineárně s počtem procesů ve skupině (selhání jsou běžná)+Nelze zaručit obě vlastnosti zároveň (dle FLP výsledku) – proto většina systémů **upřednostňuje úplnost**, i když občas dojde k mylné detekci.  
  
-Dva podprotokoly - detekce selhání a šíření informace o selhání+Jinými slovy: **lepší je omylem někoho považovat za mrtvého, než si nevšimnout, že skutečně spadl**.
  
-Průběh detekce: +  * Frekvence selhání v praxi roste **lineárně s počtem uzlů** – tj. v systému se stovkami uzlů je žné, že které z nich selžou v každém okamžiku.
-  - havárie procesu $p_j$ +
-  - detekce selhání procesu $p_j$ jakým procesem $p_k$ +
-  - proces $p_k$ rozšiřuje informaci o selhání procesu $p_j$ do ostatních procesů+
  
-Základní protokoly pro detekci selhání:+==== Průběh detekce ==== 
 +  - Proces $p_j$ selže. 
 +  - Jiný proces $p_k$ jeho selhání **zjistí** (např. nepřišel heartbeat). 
 +  - Proces $p_k$ **šíří informaci** o selhání $p_j$ dalším uzlům – podle typu protokolu. 
 + 
 +==== Typy detekčních protokolů ====
  
 === Centralizovaný heartbeat === === Centralizovaný heartbeat ===
-Heartbeat jsou odesílány periodickykaždých $\mathcal{T}$ jednotek času jednomu vybranému procesu $p_j$+  * Každý uzel **periodicky posílá heartbeat** jednomu vybranému procesu $p_j$ každých $\mathcal{T}$ jednotek času
 +  * $p_j$ si udržuje čítač a čas posledního přijetí od každého uzlu. 
 +  * Pokud nedorazí heartbeat během $\tau$ → $p_i$ je považován za selhaný.
  
-Heartbeat má pořadové čísloto odeslání heartbeatu je inkrementován lokální čítač pro každý proces+**Vlastnosti:** 
 +  * Jednoduchá implementace. 
 +  * **Nezjistí selhání $p_j$ samotného** – není nikdokdo by ho hlídal. 
 +  * $p_j$ může být **přetížen**, pokud systém obsahuje hodně uzlů.
  
-Pokud není heartbeat od $p_i$ přijat v $p_j$ časovém limitu $\tau$, je $p_i$ označen jako havarovaný+=== Kruhový heartbeat === 
 +  * Každý proces periodicky posílá heartbeat **sousedovi** kruhu. 
 +  * Lze použít **jednosměrný** nebo **obousměrný** kruh.
  
-Je úplný pro všechny uzly kromě $p_j$selhání $p_j$ není detekováno a při velkém počtu uzlů může být $p_j$ přetížen.+**Vlastnosti:** 
 +  * Není žádný centrální bod → lepší škálování. 
 +  * **Není úplný**pokud selže jeden (v jednosměrném) nebo dva (v obousměrném) uzly. 
 +  * Nutno **udržovat kruhovou topologii** – složitější údržba.
  
 +=== All-to-all heartbeat ===
 +Každý uzel **periodicky odesílá heartbeat všem ostatním**.
  
-=== Kruhový heartbeat === +**Vlastnosti:** 
-Heartbeats jsou odesílány periodicky sousedům každého procesu (jednostranně nebo oboustranně).+  * **Vysoká úplnost** – každý je hlídán všemi. 
 +  * Rovnoměrná zátěž mezi uzly. 
 +  * **Nízká přesnost** – síťové zpoždění může vést k falešnému označení za mrtvého. 
 +  * **Velké zatížení sítě** – škáluje špatně pro stovky uzlů.
  
-  * není centrální uzel +=== SWIM protokol ===   
-  není úplné při selhání více procesů (stačí jeden u jednosměrného, 2 u obousměrného) +**Scalable Weakly-consistent Infection-style Membership**
-  je třeba udržovat kruhovou hierarchii+
  
-=== All to all heartbeat === +Moderní přístup k detekci, který **škáluje a je adaptivní**.
-každý proces periodicky odesílá heartbeat všem ostatním procesům +
-  rovnoměrná zátěž uzlů +
-  * je úplný +
-  vysoké zatížení uzlů +
-  nízká přesnost (může dojít k označení více uzlů za havarované při zpoždění zprávy)+
  
-=== SWIM === +Každý proces $p_i$ periodicky: 
-**Scalable weakly consistent infection-style proces group membership protocol**+  Posílá `ping` náhodnému uzlu $p_j$ a čeká na `ack`. 
 +  - Pokud žádná odpověď nepřijde, požádá $\mathcal{K}$ jiných uzlů, aby se zeptaly místo něj (`ping_req`). 
 +  - Tyto uzly pingnou $p_j$, a pokud odpověď dostanou, pošlou ji zpět $p_i` jako `ping_ack`. 
 +  - Pokud nikdo z $\mathcal{K}$ nedostane odpověď → $p_j$ je označen za mrtvého.
  
-  proces $p_i$ periodicky odesílá zprávu $\texttt{ping}$ náhodně vybranému uzlu $p_j$ a čeká na odpověď $\texttt{ack}$ +**Vlastnosti:** 
-  * pokud ji nedostance, odešle zprávu $\texttt{ping\_req}$ $\mathcal{K}$ náhodně vybraným uzlům +  * **Přesnost** lze nastavit volbou $\mathcal{K}$ – roste s $\mathcal{K}$, chybovost klesá exponenciálně
-  * tyto uzly se zeptají $p_j$ pomocí zprávy $\texttt{ping}$ a pokud dostanou odpověď, odešlou ji $p_i$ jako $\texttt{ping\_ack}+  * **Úplnost** je zaručena. 
-  * pokud ani jeden z $\mathcal{K}$ uzlů nedostane odpověď, označí $p_i$ $p_j$ jako havarovaný+  * **Průměrný čas detekce**: 
 +  * $\frac{e}{e - 1} \mathcal{T}$ 
 +    * průměrná délka detekčního cyklu v SWIM protokolu 
 +  * Dobře funguje i ve velkých systémech (1000+ uzlů).
  
-přesnost lze nastavit volbou $\mathcal{K}$, klesá exponenciálně s rostoucím $\mathcal{K}$ 
  
-úplnost je zaručenaprůměrný čas detekce je $\frac{e}{e - 1}\mathcal{T}$+===== 3. Čas a kauzalita v DS ===== 
 +V běžném programu máme přesné hodinykteré říkají, kdy se co stalo. V distribuovaném systému ale žádné „společné“ hodiny nemáme – každý uzel má své vlastní a běží jinak rychle. Proto je v DS mnohem důležitější **pořadí a závislosti událostí** než konkrétní čas. V této kapitole si vysvětlíme, jak fungují fyzické a logické hodiny, co znamená synchronizace a jak se sleduje kauzalita mezi událostmi.
  
-===== Čas a kauzalita v DS ===== +==== Problémy s časem v DS ==== 
-**uspořádání událostí DS, fyzické hodiny a jejich synchronizacelogické hodiny a jejich synchronizace**+  * **Clock slew** – rozdíl ve skutečném čase mezi dvěma procesy. 
 +  * **Clock drift** – rozdíl rychlosti běhu hodin (jeden proces má hodiny „rychlejší“ nebo „pomalejší“ než druhý). 
 +  * To znamenáže hodiny **nelze úplně sladit**, jen přiblížit.
  
-clock slew - rozdíl v času hodin dvou procesů, mají li dvoje hodiny nenulovou mimoběžnost, jsou nesynchronizované+==== Synchronizace hodin ==== 
 +Synchronizace může být:
  
-clock drift rozdíl rychlosti hodin dvou procesů+=== Externí synchronizace === 
 +  * Každý proces $p_i$ má své hodiny $\mathcal{C}_i$ udržované v rozmezí $\delta$ od nějakého **externího referenčního času** $\mathcal{S}$. 
 +    * Např. atomové hodiny, UTC, NTP servery. 
 +    * $|\mathcal{C}_i \mathcal{S}| \leq \delta$ 
 +      * Každý uzel se snaží držet intervalu $\delta$ od globálního času 
 +  * Typický příklad: **NTP (Network Time Protocol)**.
  
-synchronizace +=== Interní synchronizace === 
-  * externí +  * Zajišťuje, že hodiny **každého páru procesů** se liší nanejvýš o $\delta$. 
-    čas $\mathcal{C}_i$ hodin každého procesu $p_i$ je udržován v rozmezí $\delta$ od času $\mathcal{S}$ externích referenčních hodin (tj. $|\mathcal{C}_i - \mathcal{S}| \leq \delta$) +    * Formálně: $|\mathcal{C}_i - \mathcal{C}_j| \leq \delta$ 
-    externí hodiny mohou být napojeny na UTC nebo atomové hodiny +      Rozdíl mezi hodinami dvou uzlů je nejvýše $\delta$ 
-    * například NTP +  * Není vázaná na žádný „reálný čas“, ale zajistí, že uzly se navzájem „drží při sobě“
-  * interní +  Např. **Berkeley algorithm**.
-    * každý pár procesů $(p_i, p_j)$ má hodnoty času svých hodin v rozmezí $\delta$v každém okamžiku (tj$|\mathcal{C}_i - \mathcal{C}_j| \leq \delta$) +
-    například Berkeley algoritmus+
  
-Externí synchronizace typu Kolik je hodin? -> odešle odpověď -> nastaví čas, má chybu nastavení špatného času kvůli komunikační prodlevě (latenci)+==== Praktické algoritmy pro synchronizaci ====
  
-Cristianův algoritmus +=== Cristianův algoritmus === 
-$$ \mathcal{C}_i \coloneq t + \frac{\mathcal{T}_\text{RT}-l_{\text{min}+l'_{min}}}{2}$$ +  * Klient se zeptá serveru: *„Kolik je hodin?“* 
-chyba je omezenatj maximálně $\frac{\mathcal{T}_\text{RT} - l_{\text{min}} - l'_{\text{min}}}{2}$+  * Server odpovía klient si upraví svůj čas podle:
  
-lokální čas lze posunou libovolně dopředuale ne dozadu, je možné zvýšit nebo snížit rychlost hodin+$\mathcal{C}_i := t + \frac{\mathcal{T}_{RT} - l_{\text{min}} - l'_{\text{min}}}{2}$ 
 +  * RTT mínus odhadnutá minimální latence tam i zpětděleno dvěma
  
-NTP +Očekávaná **chyba synchronizace** je: $\leq \frac{\mathcal{T}_{RT- l_{\text{min}} - l'_{\text{min}}}{2}$
-  * servery uspořádány do hierarchie stromu, uzly synchronizují čas se svými rodiči a někdy s dalšími servery na stejné úrovni +
-  mezi zprávami se spočte offset, který se použije pro výpočet komunikační latence +
-  * $o = \frac{(t_1^{r- t_2^{r}+t_2^{s} - t_1^{s})}{2}$+
  
-Logické hodiny 
  
-nepoužívají ímo aktuální čas ale časovou značku+Lokální čas lze zvyšovat okamžitě, ale **nelze ho vracet zpět** – místo toho se mění rychlost ibývání.
  
-kauzální vztah - první událost může ovlivnit druhou+=== NTP === 
 +  * Servery tvoří **stromovou hierarchii** (stratum 0, 1, 2, …). 
 +  * Uzly synchronizují čas s rodiči i některými sousedy. 
 +  * Latence se odhaduje pomocí offsetu:
  
-Relace stalo se před +$o = \frac{(t_1^{r- t_2^{r+ t_2^{s} - t_1^{s})}{2}$ 
-  - Jsou-li $\mathcal{A}$ a $\mathcal{B}$ události ve stejném procesu $p$ a pokud $\mathcal{A}$ nastala před $\mathcal{B}$, pak $\mathcal{A} \rightarrow \mathcal{B}$ +  * vypočtený odhad ofsetu mezi hodinami klienta serveru
-  Je-li $\mathcal{A}$ odeslání zprávy a $\mathcal{B}$ je přijetí této zprávy, pak $\mathcal{A} \rightarrow \mathcal{B}$ +
-  - Je-li $\mathcal{A} \rightarrow \mathcal{B}$ $\mathcal{B} \rightarrow \mathcal{C}$, pak $\mathcal{A} \rightarrow \mathcal{C}$ (tranzitivita)+
  
-Kauzální nezávislost +==== Logické hodiny a kauzalita ==== 
-  * relace stalo se před zavádí částečné uspořádání událostí, potenciální kauzální závislosti +Logické hodiny nejsou o „skutečném čase“, ale o **pořadí událostí**. Nepotřebují žádnou synchronizaci – pouze konzistentní značkování událostí podle „co mohlo ovlivnit co“.
-  $e_1$ -> $e_2$: potenciálně kauzálně závislé události ($e_2$ mohlo být ovlivněno $e_1$, ale nemuselo) +
-  $e_1$ || $e_2$: současné události (kauzální vztah určitě není)+
  
-Lamportovy logické hodiny - každý proces má své logické hodiny, které se aktualizují podle ijímání zpráv+=== Kauzální vztah – relace „stalo se před“ (→) === 
 +Událost $\mathcal{A}$ **mohla ovlivnit** událost $\mathcal{B}$, pokud:
  
-Synchronizace logických hodin +  * $\mathcal{A} \rightarrow \mathcal{B}– pokud obě nastaly ve stejném procesu a $\mathcal{A}$ byla dřív, 
-  - po každé události která se odehraje v $p_ise hodiny $\mathcal{C}_iinkrementují o 1 +  * nebo $\mathcal{A}$ je odeslání zprávy a $\mathcal{B}$ je její ijetí
-  - každé zprávě $m$ odeslané procesem $p_i$ je přiřazená časová značka $ts(m) = \mathcal{C}_i$ +  * nebo (tranzitivně): $\mathcal{A} \rightarrow \mathcal{B} \rightarrow \mathcal{C}$ ⇒ $\mathcal{A} \rightarrow \mathcal{C}$
-  - kdykoliv proces $p_j$ přijme zprávu $m$tak: +
-    - upraví své lokální hodiny $\mathcal{C}_jna $max\{\mathcal{C}_j, ts(m)\}$ a poté +
-    - provede krok 1 předtím než předá $m$ aplikaci (<= přijetí zprávy je událost) +
  
-Lamportovy hodiny neimplikují kauzalitu! +=== Kauzální nezávislost === 
-  * Platí: jestliže $e_1 \rightarrow e_2$, pak $\mathcal{C}(e_1\lt \mathcal{C}(e_2)+  * Události $e_1$, $e_2$ jsou **současné** (nezávislé)pokud:  $$ e_1 \parallel e_2 $
-  * Neplatíjestliže $\mathcal{C}(e_1) \lt \mathcal{C}(e_2)$, pak $e_1 → e_2$+  * Jinými slovy: žádná z nich nemohla ovlivnit druhou.
  
-Vektorové hodiny - každý proces si udržuje vektor celočíselných hodin ostatních procesů+=== Lamportovy logické hodiny === 
 +Každý proces si udržuje **číselnou hodnotu svých hodin**: 
 +  * Při každé lokální události: $\mathcal{C}_i := \mathcal{C}_i + 1$ 
 +  * Při odeslání zprávy $m$:  zpráva dostane časovou značku $ts(m) := \mathcal{C}_i$ 
 +  * Při přijetí zprávy $m$ procesem $p_j$:  $$ \mathcal{C}_j := \max(\mathcal{C}_j, ts(m)) + 1 $$
  
-===== Globální stav v DS a jeho výpočet ===== +Důležité:   
-**řez distribuovaného výpočtualgoritmus pro distribuovaný globální snapshot, stabilní vlastnosti DS**+  Jestliže $e_1 \rightarrow e_2$pak $\mathcal{C}(e_1) < \mathcal{C}(e_2)$ 
 +  Ale: $\mathcal{C}(e_1) < \mathcal{C}(e_2)$ **neznamená**, že $e_1 \rightarrow e_2$
  
-globální stav je množina lokálních stavů procesů v DS a stavu všech komunikačních kanálů v jednom okamžiku+⇒ Lamportovy hodiny **respektují kauzalitu**, ale **neumí ji zpětně ověřit**.
  
-globální snapshot je záznam globálního stavu+=== Vektorové hodiny === 
 +Abychom **přesně zachytili kauzalitu**, každý proces si udržuje **vektor hodin**, kde každý prvek reprezentuje „jaký čas vím o ostatních“:
  
-například +  Vektor $V_i$ proces $p_i$ má délku $n$ (počet procesů). 
-  garbage collection +  * Při každé lokální události: $V_i[i] += 1$ 
-  * deadlock detection +  * Při odeslání zprávy se posílá kopie $V_i$ 
-  * detekce ukončení výpočtu +  * Při přijetí zprávy $m$ se provede prvek po prvku: $$ V_i[j] := \max(V_i[j], V_m[j]) \quad \text{pro všechna } j $$  a poté $V_i[i] += 1$
-  * checkpointing+
  
-Řez - časová hranice v každém procesu a v každém komunikačním kanále +Událost $e_1$ edchází $e_2$ **ve vektorovém čase**, pokud: $$ V(e_1)[k] \leq V(e_2)[k] \ \forall k \text{ a alespoň jedna nerovnost je ostrá} $$
-  * události které nastanou ed řezem jsou v řezu +
-  události které nastanou po něm jsoui mimo řez+
  
-Konzistentní řez - řez $\mathcal{Ř}$ je konzistentní pokud splňuje kauzalitutj. pokud pro každý pár událostí $e$, $f$ v systému platí: +To už umí odhalit **kauzální závislost i nezávislost**. 
-$$ f \in \mathcal{Ř} \land e \rightarrow f \implies e \in \mathcal{Ř}$$ + 
-tj. pokud řez obsahuje nějakoiu událostobsahuje všechny, které ji edcházejí dle relace **stalo se před** (tj. nelze aby v řezu byl důsledek a nebyla tam íčina)+ 
 +===== 4. Globální stav v DS a jeho výpočet ===== 
 +V distribuovaném systému neexistuje globální hodina ani centrální pohled na „okamžitý stav celého systému“. Přesto někdy potřebujeme **získat konzistentní snímek stavu** všech procesů a kanálů – třeba pro detekci uváznutí, garbage collection nebo checkpointing. Tato kapitola vysvětluje, co znamená **řez distribuovaného výpočtu**, jak funguje **Chandy-Lamportův algoritmus** pro získání globálního snapshotu a co jsou **stabilní vlastnosti** systému. 
 + 
 +==== Co je globální stav a snapshot ==== 
 + 
 +**Globální stav** je: 
 +  * množina **lokálních stavů všech procesů** + 
 +  * stav **všech komunikačních kanálů**   
 +  * … ve stejném (logickém) okamžiku 
 + 
 +**Globální snapshot** je **záznam** takového stavu, který může být analyzován mimo běžící systém (např. logicky, offline). 
 + 
 +Použití: 
 +  * **garbage collection** – rozhodnutí, že objekt už není dosažitelný 
 +  * **detekce deadlocku** 
 +  * **ukončení výpočtu** 
 +  * **checkpointing** – ukládání stavu pro případ obnovení 
 + 
 +==== Řez distribuovaného výpočtu ==== 
 + 
 +**Řez (cut)** definuje: 
 +  * **časový okamžik** v každém procesu a 
 +  * **hranicí** mezi tím, co se „už stalo“, a tím, co „teprve nastane“ 
 + 
 +Události, které nastanou **před řezem**, do něj patří; zbytek je mimo. 
 + 
 +=== Konzistentní řez === 
 +Řez $\mathcal{Ř}$ je **konzistentní**, pokud: $$ f \in \mathcal{Ř} \land e \rightarrow f \implies e \in \mathcal{Ř} $$   
 + 
 +Jinými slovy: **jestliže obsahuje důsledekmusí obsahovat i příčinu**.   
 +  Nelze, aby byl v řezu příjem zprávy, ale ne její odeslání. 
 + 
 +Konzistentní řez je tedy **logický okamžik**, který by mohl být zpozorován „zvenčí“.
  
-konzistentní řez = logický okamžik 
  
 {{statnice:bakalar:rezy.png?500}} {{statnice:bakalar:rezy.png?500}}
Line 744: Line 899:
 {{statnice:bakalar:rezy2.png?500}} {{statnice:bakalar:rezy2.png?500}}
  
-Chamdy-Lamport algoritmus pro distribuovaný globální snapshot +==== Algoritmus pro distribuovaný snapshot (Chandy-Lamport) ==== 
-  - (Vytváření snapshotu je distribuované.) +Chandy-Lamport algoritmus umožňuje **distribuovaným procesům získat konzistentní snapshot**, aniž by existovalo globální hodiny. 
-  - Speciální zpráva: ZNAČKA ▪ + 
-  - Jeden (libovolný) z procesů iniciuje vytvoření snapshotů. +**Základní princip:** 
-  - Procesy reagují na příjem zprávy ZNAČKA ▪ +  * Procesy si **lokálně zaznamenají** svůj stav
-  - výsledkem je konzistentní řez+  * Stav kanálů mezi nimi se zjistí pomocí **speciální zprávy**`ZNAČKA ▪`. 
 +  * Algoritmus je **asynchronní** a **neblokující** – každý proces koná na základě přijatých značek. 
 + 
 +**Průběh:** 
 +  - Jeden proces iniciuje snapshot – uloží si stav a pošle `ZNAČKA ▪` všem sousedům
 +  - Když jiný proces obdrží `ZNAČKA ▪` poprvé: 
 +      * uloží svůj stav, 
 +      * pošle `ZNAČKA ▪` dál, 
 +      * vše, co do té doby přišlo na kanálu, **patří do snapshotu**. 
 +  - Pokud ijde `ZNAČKA ▪` na kanál, u kterého už máme stav procesu, ale ne kanálu → zprávy mezi tím patří do snapshotu. 
 +Výsledkem je **konzistentní globální snapshot**.
  
 {{statnice:bakalar:03_globalni_snapshot_2023.pdf|ilustrace algoritmu slide 15-21}} {{statnice:bakalar:03_globalni_snapshot_2023.pdf|ilustrace algoritmu slide 15-21}}
  
-stabilní vlastnost je taková vlastnost, že jakmile je ve výpočtu jednou splněna, zůtává splněna navždy +==== Stabilní vlastnosti ====
-  * například výpočet skončil, nastalo uváznutí, objekt je sirotek+
  
- nestabilní vlastnost je například //ve výpočtu není žádný havarovaný proces//+**Stabilní vlastnost** je taková, že když začne platit, **zůstává platná navždy**. 
 +  * Příklady: 
 +    * výpočet skončil 
 +    * došlo k uváznutí 
 +    * objekt je sirotek (už nikdy nebude dosažen)
  
-Chandy-Lamport snapshotů algoritmus lze použít pro detekci stabilních globálních vlastností: +**Nestabilní vlastnost**: 
-  * Je daná stabilní vlastnost V splněna v globálním snapshotu zachyceným snapshot algoritmem? +  * např. „žádný proces neselhal“ – může přestat platit. 
-    - ANO - Vlastnost $\mathcal{V}$ bude ve výpočtu splněna i ve fyzickém okamžiku doběhnutí snapshot algoritmu+ 
-    - NE - Vlastnost $\mathcal{V}$ nemohla být ve výpočtu splněna ani ve fyzickém okamžiku zahájení snapshot algoritmu.+=== Kontrola stabilní vlastnosti pomocí snapshotu === 
 +Chandy-Lamport algoritmus lze využít pro **detekci stabilních vlastností**
 + 
 +Chceme vědět, jestli nějaká stabilní vlastnost $\mathcal{V}$ v systému **nastala**. 
 +  * Pokud **platí v globálním snapshotu**, pak: 
 +    * Platila už i ve skutečném systému v okamžiku **ukončení** snapshotu. 
 +  * Pokud **neplatí v snapshotu**, pak: 
 +    * Nemohla platit ani **v okamžiku zahájení** snapshotu. 
 + 
 +Snapshot tedy vytváří **spolehlivý časový úsek**, během kterého můžeme ověřit trvalost vybraných vlastností.
  
 ===== Vzájemné vyloučení procesů v DS ===== ===== Vzájemné vyloučení procesů v DS =====
 **algoritmy pro vyloučení procesů a jejich vlastnosti** **algoritmy pro vyloučení procesů a jejich vlastnosti**
 +
 +V distribuovaných systémech nemáme sdílenou paměť ani centrální plánovač. Pokud chceme zajistit, že do tzv. **kritické sekce** vstupuje vždy jen jeden proces, potřebujeme speciální algoritmy pro **vzájemné vyloučení**. Tato kapitola popisuje základní problém, požadavky na jeho řešení a několik klíčových algoritmů včetně jejich výhod a nevýhod.
  
 ==== Problém vzájemného vyloučení ==== ==== Problém vzájemného vyloučení ====
-<markdown> +  * Kritická sekce je část programudo které smí vstoupit daný okamžik právě jeden proces – např. pro aktualizaci sdíleného stavu. 
-*Kritická sekceje část kódu u které potřebujeme zaručitže ji každém okamžiku vykonává maximálně jeden proces +  V distribuovaném prostředí je nutné **synchronizovat přístup přes zprávy**. 
-- Definujeme funkce *enter()*exit()* pro vstup a výstup do kritické sekce +  * Algoritmus poskytuje dvě základní operace: 
-- Na úrovni operačního systému umíme problém řešit pomocí synchronizačních nástrojů, v distribuovaném systému potřebuje algoritmus +    * `enter()` – požádá o vstup do kritické sekce 
-</markdown>+    * `exit()` – opustí kritickou sekci 
 ==== Požadavky na algoritmus pro vyloučení procesů ==== ==== Požadavky na algoritmus pro vyloučení procesů ====
-<markdown>+  * **Bezpečnost**: nikdy není více než jeden proces v kritické sekci současně. 
 +  * **Živost**: každý požadavek na vstup do kritické sekce je časem uspokojen. 
 +  * **Uspořádání (volitelné)**: pokud jeden požadavek kauzálně předchází jinému, měl by být obsloužen dříve. 
 +  * Předpoklady: 
 +    * procesy neselhávají 
 +    * komunikace je spolehlivá (FIFO, bez ztrát, bez duplicit) 
 +    * systém je **asynchronní** s konečnou latencí
  
-- *Bezpečnost* - Nejvýše jeden proces v kritické sekci +==== Analýza výkonnosti algoritmů ==== 
-- *Živost* - Každý požadavek na vstup kritické sekce je časem uspokojen +  * **Komunikační zátěž** – kolik zpráv je potřeba pro každý vstup/výstup do KS 
-- *Uspořádání* (není nutné) - Předchází-li žádost jednoho procesu do kritické sekce kauzálně žádosti jiného procesu, tento proces by se měl do kritické sekce dostat dříve +  * **Zpoždění klienta** – čas od požadavku po vstuppokud nikdo jiný nečeká 
-- Uvažujeme navíc, že procesy neselhávají a komunikační kanály jsou perfektní (FIFO, zprávy se neduplikují, nevznikají, neztrácejí) a uvažujeme asynchronní systém s konečnou latencí +  * **Synchronizační zpoždění** – čas mezi výstupem jednoho procesu a vstupem dalšího
-</markdown> +
- +
-==== Analýza výkonnosti algoritmů pro vzájemné vyloučení ==== +
-<markdown> +
- +
-*Komunikační zátěž* = počet zpráv poslaných přkaždém vstupu a výstupu do KS +
-*Zpoždění klienta* = zpoždění procesu při vstupu do KS za předpokladuže jiné procesy na vstup nečekají +
-*Synchronizační zpoždění* = interval mezi vystoupením jednoho procesu z KS vstoupením dalšího +
-</markdown>+
  
 ==== Centralizovaný algoritmus ==== ==== Centralizovaný algoritmus ====
-<markdown>+  * Jeden proces slouží jako **koordinátor** a spravuje **token**. 
 +  * Proces žádá koordinátora o token; po jeho získání vstoupí do kritické sekce. 
 +  * Po výstupu token vrací. 
 +  * Token je předáván podle fronty požadavků.
  
-- Je zvolen jeden koordinátor +**Vlastnosti:** 
-- Ten spravuje speciální token, který držiteli umožní vstup do KS a frontu požadavků na vstup do KS +  Bezpečnost i živost zaručena. 
-- Před vstupem do KS je poslán požadavek na token koordinátorovi, po přijetí tokenu algoritmus vstupuje do kritické sekce +  Komunikační zátěž2 zprávy (žádost + token) pro vstup, 1 pro výstup. 
-- Po výstupu je token vrácen koordinátorovi +  Zpoždění klienta2 latence. 
-- Koordinátor po přijetí požadavku na token předá token pokud jej drží, pokud jej nedrží, tak tento požadavek přidá do fronty +  Synchronizační zpoždění2 latence. 
-- Ve chvíli kdy koordinátor obdrží token tak zkontroluje frontu zda tam není žádný požadavek a případně jej obslouží +  * Nevýhoda: **koordinátor je single point of failure**.
-Bezpečnost je zaručena, a živost za našich předpokladů také +
-Komunikační zátěž 2 zprávy vstup, 1 výstup +
-Zpoždění klienta 2 latence +
-Synchronizační zpoždění 2 latence +
-</markdown>+
  
 ==== Kruhový algoritmus ==== ==== Kruhový algoritmus ====
-<markdown>+  * Procesy jsou uspořádány v logickém kruhu. 
 +  * Jeden **token** obíhá dokola. 
 +  * Proces vstupuje do kritické sekce až po přijetí tokenu. 
 +  * Pokud token nepotřebuje, rovnou ho předá dál.
  
-- N procesů v kruhu +**Vlastnosti:** 
-- Proces může poslat zprávu následníkovi +  Komunikační zátěž: až $N$ zpráv při čekání na token. 
-- Mezi procesy koluje jeden token +  Zpoždění klienta0 až $N$ latencí. 
-- Před vstupem proces čeká dokud neobdrží token +  Synchronizační zpoždění1 až $N-1$ latencí. 
-- Po vstupu proces pošle token následníkovi +  * Výhoda: žádný centrální uzel. 
-- Pokud proces obdrží token a nečeká na vstup, tak jej hned předá dále +  * Nevýhoda: token může „zabloudit“ nebo se ztratit.
-Komunikační zátěž $N$ vstup, $1$ výstup +
-Zpoždění klienta - $0až $N$ zpráv +
-Synchronizační zpoždění - $1až $N-1$ komunikačních latencí +
-</markdown>+
  
 ==== Ricart-Agrawalův algoritmus ==== ==== Ricart-Agrawalův algoritmus ====
-<markdown> +  * Nepoužívá token – pracuje pomocí **multicastu logických hodin**. 
-Nepoužívá token, ale kauzalitu multicast +  Každý proces si udržuje
-- Nižší synchronizační zpoždění než kruhový algoritmus a nepoužívá centrální proces +    * stav (*RELEASED**WANTED**HELD*
-Každý proces si udržuje stav, který může nabývat 3 hodnot: WANTED, HELD, RELEASED (u všech procesů je inicializován na RELEASED+    * frontu odložených žádostí 
-- Před vstupem do KS+  * Pro vstup do kritické sekce
- - Je nastaven stav na WANTED +    * proces pošle `REQUESTvšem, zaznamená čas 
- - Poslán multicast REQUEST všem ostatním procesům +    * čeká na odpovědi `OKod všech 
- - Čeká na odpověď +  * Ostatní procesy odpoví podle kauzálního (Lamportova) času 
- - Po přijetí OK od všech ostatních procesů je stav změněn na HELD a proces vstupuje do KS + 
-- Po přijetí REQUEST +<code cpp> 
- - Pokud je stav WANTED a je časová značka přijaté zprávy nižší než čas ve kterém začal stav WANTED, je příslušný proces uložený do seznamu čekajících požadavků, pokud je vyšší tak to zahodím +// schéma odpovědi na REQUEST(K) 
- - Jinak je posláno OK +if (stav == HELD || (stav == WANTED && (můj_čas < jejich_čas || (můj_čas == jejich_čas && můj_id < jejich_id)))) { 
-Po výstupu z KS je stav nastaven na RELEASED a všem procesům ze seznamu je posláno OK +    odlož požadavek; 
-- V nejhorším ípadě je nutno čekat než všech $(N-1)$ pošle OK +} else { 
-- Je zachováno pořadí, požadavky s nižší lamportovou značkou mají přednost +    pošli OK; 
-Komunikační zátěž – $2(N-1)$ při vstupuaž $(N-1)$ při výstupu +
-Zpoždění klienta – **2** latence +</code> 
-Synchronizační zpoždění – 1 latence + 
-</markdown>+  * Po ukončení práce kritické sekci proces: 
 +    epne stav na RELEASED 
 +    * pošle OK na všechny odložené požadavky 
 + 
 +**Vlastnosti:** 
 +  * Komunikační zátěž
 +    * $2(N-1)$ zpráv při vstupu (REQUEST + OK) 
 +    * až $(N-1)$ zpráv při výstupu (zpracování fronty) 
 +  Zpoždění klienta2 latence 
 +  Synchronizační zpoždění1 latence 
 +  * Výhoda: **zcela decentralizovaný**, deterministický 
 +  * Nevýhoda: **vysoký počet zpráv**
  
 ===== Volba lídra v DS ===== ===== Volba lídra v DS =====
 **algoritmy pro volbu lídra a jejich vlastnosti** **algoritmy pro volbu lídra a jejich vlastnosti**
-<markdown> + 
-Cílem je, aby se všechny procesy shodly na jednom „koordinátorovi“ (lídrovi).   +V distribuovaném systému často potřebujeme zvolit jeden proces, který bude působit jako koordinátor – například pro synchronizaci, správu zámků nebo organizaci úkolů. Jelikož uzly mohou selhávat, musí volba probíhat robustně a bez centrální autority. 
-Předpokládáme možnost selhání (crash) procesů nebo spojení; zprávy se ale doručují spolehlivě.   + 
-- Výsledek nesmí záviset na tomkdo volbu odstartoval, a více souběžných voleb se musí slít do jedné (konvergence). +Cílem algoritmu pro **volbu lídra** je zajistit, aby
-</markdown>+  * všechny živé procesy se **shodly na jednom lídrovi**, 
 +  * nezáleželo na tom, kdo volbu zahájil, 
 +  * více paralelních voleb nakonec **konvergovalo k jedné**. 
 + 
 +Předpokládáme
 +  * spolehlivé doručování zpráv (žádné ztráty ani duplicity), 
 +  * možnost selhání procesů (typicky crash), 
 +  * procesy mají unikátní ID (číselnéa dokážou je mezi sebou porovnat.
  
 ==== Kruhový algoritmus (Ring) ==== ==== Kruhový algoritmus (Ring) ====
-<markdown> +  * Procesy jsou logicky uspořádány do kruhu (např. podle ID). Každý zná svého následníka a umí ho kontaktovat. 
-Procesy tvoří logický kruh uspořádaný podle ID. Každý zná svého **následníka** a umí zjistit, zda soused žije.   +  Při podezření na pád lídra (např. po timeoutu) proces $P_i$ zahájí volby: 
-**Start**: proces $P_i$ detekuje pád lídra a pošle zprávu `ELECTION(i)` svému následníkovi.   +    * pošle `ELECTION(i)` svému následníkovi. 
-**Přijetí `ELECTION(j)`** u $P_i$   +    pokud příjemce dostane `ELECTION(j)`
-  - pokud $j > i$předá zprávu dál beze změny;   +      * pokud $j > i$předá dál beze změny, 
-  pokud $j < i$nahradí $j$ svým ID a pošle `ELECTION(i)`;   +      pokud $j < i$nahradí vlastním ID a pošle dál, 
-  pokud $j = i$zpráva oběhla kruh ⇒ $P_i$ má nejvyšší ID ⇒ je vítěz+      pokud $j = i$zpráva oběhla kruh ⇒ $P_i$ má nejvyšší ID ⇒ vítě
-- $P_i$ vyšle `ELECTED(i)` kolem kruhuvšichni si uloží, že koordinátor je $i$ a předají dál, dokud zpráva neoběhne kruh+  * Nový lídr pak vyšle `ELECTED(i)` kolem kruhu → všichni si uloží nového lídra
-**Komplexita**: $\mathcal{O}(n)$ zpráv a čas, kde $n$ je počet živých procesů. Funguje i více paralelními volbami, protože nejvyšší ID zvítězí. + 
-</markdown>+**Vlastnosti:** 
 +  * Komunikační složitost: $\mathcal{O}(n)$ 
 +  * Funguje správně i při více paralelních volbách 
 +  * Zvítězí proces s nejvyšším ID 
 +  * Nevyžaduje globální znalost všech procesů
  
 ==== Algoritmus Bully ==== ==== Algoritmus Bully ====
-<markdown> +  Procesy jsou propojeny do **úplné sítě** – každý zná každého. 
-**Assumpce**: úplná síť (každý zná všechny), procesy se dají porovnat dle ID; detekce pádu je „rychlá“ (time-outs).   +  Pokud $P_i$ zjistí, že koordinátor mlčí (timeout), zahájí volby: 
-**Kroky (z pohledu procesu $P_i$)**   +  Pošle `ELECTION` všem **procesům s vyšším ID** 
-  1. $P_i$ zjistí, že koordinátor neodpovídá.   +  Pokud žádný neodpoví → $P_i$ se **prohlásí lídrem** 
-  2. Pošle `ELECTION` *všem* procesům s **vyšším** ID.   +  - Pošle `COORDINATOR(i)` všem **nižším ID** 
-  3. Pokud **nikdo** neodpoví během timeoutu → $P_i$ se prohlásí koordinátorem, odešle `COORDINATOR(i)` všem procesům s nižším ID a volba končí.   +  Pokud někdo odpoví `OK` → $P_i$ čeká na `COORDINATOR(k)` od silnějšího 
-  4. Pokud přijde **alespoň jedno** `OK` od vyššího ID → $P_i$ čeká na zprávu `COORDINATOR(k)`; pokud po čase nedorazírestartuje volby  +  - Pokud neobdrží oznámení včasznovu zahájí volby 
-  5. Po přijetí `COORDINATOR(k)` si každý proces uloží $k$ jako nového lídra.+ 
 +**Zprávy:** 
 +  * `ELECTION` – „ozvi se, pokud žiješ a jsi silnější“ 
 +  * `OK` – potvrzení, že někdo silnější žije 
 +  * `COORDINATOR(k)` – oznámení o novém lídrovi
  
-**Typy zpráv**   +**Vlastnosti:** 
-  - `ELECTION` – „vyhlašuji volbyreaguj, pokud žiješ a máš vyšší ID“.   +  * Zaručuje, že vítězí **nejvyšší živé ID** 
-  - `OK` – potvrzení, žvyšší proces existuje; potlačí menší ID.   +  * Horší složitost: $\mathcal{O}(n^2)$ zpráv v nejhorším případě 
-  - `COORDINATOR(k)` – oznámení výsledku všem.  +  * Rychlejší než kruh, pokud je vyšší ID blízko 
 +  * Vyžaduje spolehlivou detekci pádů (není odolný vůči partitioning)
  
-- **Vlastnosti**   
-  - Garantuje, že zvítězí **nejvyšší živé ID** (“bully” přetlačí ostatní).   
-  - Nejhorší případ $\mathcal{O}(n^2)$ zpráv (kaskáda voleb, když padá více procesů).   
-  - Rychlejší konvergence než kruh, pokud selže jen koordinátor a vyšší ID je blízko.   
-  - Netoleruje síťové rozdělení (“split brain”) – vyžaduje spolehlivé detekce crashů. 
-</markdown> 
  
Navigation

Playground

QR Code
QR Code statnice:bakalar:b4b36pdv (generated for current page)