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/05/31 13:27] – [GPGPU] 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
-==== GPGPU ====+
  
-* **General-Purpose computing on Graphics Processing Units** (GPGPU) využívá původně grafické akcelerátory jako masivně paralelní výpočetní jednotky+==== Instruction-Level Parallelism (ILP) ==== 
-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 konceptu **warpů** (32 vláken běžících lock-step). +  * **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é operacenezávislé paměťové přístupy, nezávislé větvení. 
 +  * **Využití ILP:** pipelining, superskalární/VLIW architektury, out-of-order execution.
  
-=== Architektura a omezení ===+==== Pipelining ==== 
 +  * **Princip:** Instrukční cesta se dělí na fáze IF → ID → EX → MEM → WB.   
 +    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á. 
 +  * **Teoretický zisk:** až 1 instrukce / takt (IPC = 1); limitují ho hazardy a větvení. 
 +  * **Superpipelining:** více než 10 stupňů; vyšší frekvence, ale větší penalizace při chybách predikce. 
 +  * **Hazardy:** 
 +    * datové – řeší se forwardingem, stally, přejmenováním registrů 
 +    * řídicí – predikce skoků + branch target buffer (BTB) 
 +    * strukturální – řeší se duplikací jednotek
  
-* **Hierarchie výpočetních bloků:** GPC $\rightarrow$ SM $\rightarrow$ processing block $\rightarrow$ warp $\rightarrow$ thread. +==== Skalární vssuperskalární procesory ====
-* **Sdílené prostředky SM:**+
  
-  * 128 KB *shared memory* + L1 cache +=== Skalární procesor === 
-  * max 64 warpů ≈ 2048 vláken na SM +  * 1 instrukce za takt (IPC ≤ 1) 
-  * 64 k 32-bit registrů; limit ≈ 255 registrů na vlákno (nižší počet registrů = vyšší obsazenost)  +  * jednodušší architektura 
-**Paměťová hierarchie GPU:**+  využívá pipelining
  
-  * L1/shared: ~33 cyklů +=== Superskalární procesor === 
-  * L2: až 2 TB/s (~200 cyklů+  * více instrukcí za takt (IPC > 1
-  * HBM2: 1.5 TB/s (~290 cyklů+  * N-wide architektura (např4-wide = až 4 instrukce/takt
-  * Host ↔ GPU: PCIe Gen4 ~32 GB/s, NVLink ~50 Gb/s pár signálů +  * využívá ILP pomocí paralelního vykonávání více instrukcí
-  * ⇒ minimalizovat a slučovat přenosy mezi CPU ↔ GPU, i za cenu většího lokálního výpočtu. +
  
-=== Výpočetní výkon v praxi ===+==== Podtypy superskalární architektury ==== 
 +  * **Statická:** paralelně jen instrukce jdoucí v kódu za sebou; při závislostech nastává stall. 
 +  * **Dynamická:** out-of-order execution a přejmenování registrů → lepší využití hardwaru.
  
-| Konfigurace                   | Single-thread | Multi-thread CPU | CPU + GPGPU    | +==== Shrnutí rozdílu ==== 
-| Threadripper 3990X + RTX 4090 | 49 GFLOPS     | 3 .7 TFLOPS      | **100 TFLOPS** |+  * **Skalární CPU** ⇒ 1 instrukce/takt, jednoduché, predikovatelné chování. 
 +  * **Superskalární CPU** ⇒ více instrukcí/takt, složitější řízení, vyšší výkon.
  
-*Na stejném stroji GPU dodá ~×25 výkon oproti plně vytíženému CPU;+==== GPGPU ==== 
 +  * **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).
  
-=== Programovací modely ===+=== 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ů
  
-* **CUDA C/C++** – proprietární, de-facto standard pro NVIDIA. +=== Výpočetní výkon v praxi === 
-* **OpenCL 3.0** – otevřený standard podporovaný NVIDIA, AMD, Intel, ARM Mali. +* Threadripper 3990X + RTX 4090: 
-* **SYCL 2020** – moderní C++20 nad OpenCL/CUDA/Level Zero; přenositelné šablony a paralelní STL. +  * single-thread: 49 GFLOPS 
-* **OpenMP 5.x target/teams** – off-load direktivy do GPU, relativně snadná migrace existujícího kódu. +  * multi-thread CPU: 3.7 TFLOPS 
-* Další možnosti: Vulkan Compute, DirectX 12 Compute, Thrust (C++ algoritmy nad CUDA).+  * CPU + GPGPU: **100 TFLOPS** 
 + 
 +=== Programovací modely === 
 +  * **CUDA C/C++** – pro NVIDIA 
 +  * **OpenCL** – otevřený standard 
 +  * **SYCL** – moderní C++ model 
 +  * **OpenMP target** – direktivy pro GPU
  
 === Typické úlohy === === Typické úlohy ===
- +  * lineární algebra, strojové učení, ray-tracing, simulace, video encoding
-* lineární algebra (matmul, BLAS), strojové učení, ray-tracing, simulace částic/CFD, kryptografie, video-encoding.+
  
 === Výhody === === Výhody ===
- +  vysoký výkon (tisíce FP jednotek) 
-* **Obrovský teoretický throughput** (tisíce FP jednotek). +  * efektivita (TFLOPS/W) 
-**Energetická efektivita**: TFLOPS/často výrazně lepší než CPU. +  specializované instrukce (Tensor Cores)
-**Specializované instrukce** (Tensor Cores, BF16/FP16).+
  
 === Nevýhody === === Nevýhody ===
- +  paměťová latence 
-* **Paměťová latence** mimo L1/shared, nutnost koalesovaného přístupu. +  omezené registry/shared memory 
-* **Omezená velikost registrů a shared memory** $\rightarrow$ ladění obsazenosti. +  * přenosy mezi host ↔ device 
-**Drahé přenosy host ↔ device** (PCIe). +  * warp divergence (větvení) 
-**Divergence warpů** (větvení) snižuje výkon – kód musí být datově paralelní. +  vendor lock-in (CUDA)
-**Vendor lock-in** (CUDA) či různé úrovně podpory standardů na HW.+
  
 === Shrnutí === === Shrnutí ===
-GPGPU nabízí **řádově vyšší paralelní výkon** než mainstreamové CPU, ale vyžaduje uvědomělou práci pamětí a thread-level paralelismemSprávná volba programovacího modelu (CUDA ↔ OpenCL/SYCL ↔ OpenMP targetumožní radikální zrychlení numericky-intenzivních úloh i zachování udržitelnosti kódu+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 vhodným programovacím modelem, je klíčem k úspěchu. 
-/+ 
-################################################################################################################################################################################### +==== Vlákna 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). 
-===== Komplikace v paralelním programování ===== +  * **Vlákno (thread)** je lehčí jednotka běhu uvnitř procesu. 
-**souběh (race condition), uváznutí (deadlock), iluze sdílení (false sharing)** +    * Sdílí paměť a další prostředky s ostatními vlákny procesu. 
-  * souběh (race condition), uváznutí (deadlock): (statnice:bakalar:b4b35osy)+    * 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. 
 + 
 +==== Hierarchie cache pamětí ==== 
 +Moderní CPU mají vícestupňovou **hierarchii cache**, která zajišťuje rychlý ístup k často používaným datům: 
 +  * **L1 cache** – nejmenší (např. 32 KB), ale nejrychlejší (~4 cykly), zvlášť pro instrukce a data
 +  **L2 cache** – větší (např. 256 KB), pomalejší (~12 cyklů), většinou sdílena mezi jádry. 
 +  * **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:** data, která 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ů. 
 + 
 +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. 
 + 
 +===== 2. Komplikace v paralelním programování ===== 
 +Když programujeme paralelně – tedy více věcí běží najednou – můžeme narazit na různé složitosti, které se u 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 si, jak a proč k nim dochází. 
 + 
 +==== 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á. 
 + 
 +Například: 
 +<code cpp> 
 +// špatně – race condition 
 +for (int i = 0; i < 1000; i++) { 
 + counter++; 
 +
 +</code> 
 + 
 +  * Řešení: 
 +    * Použít **synchronizační primitiva** – mutexy, atomické operace, semafory apod. 
 +    * Nebo použít **thread-local** kopie a sloučit je až nakonec. 
 + 
 +==== Deadlock (uváznutí) ==== 
 +  * **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. 
 +  * Jednoduše řečenovlákno A čeká na zámek, který drží vlákno B, a vlákno B čeká na zámek, který drží vlákno A. 
 + 
 +Například: 
 +<code cpp> 
 +// pseudokód s potenciálním deadlockem 
 +thread1: 
 +lock(A) 
 +lock(B) 
 + 
 +thread2: 
 +lock(B) 
 +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ámek) nemůž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ímeco děláme – ale musí být správně synchronizované. 
-Narozdíl od false sharing chtěná (pokud umíte programovat) vlastnostje nutná korektní synchronizace.+  * Typicky jde o sdílené čítače, fronty, stavové proměnné atd.
  
-/* 
-################################################################################################################################################################################### 
-################################################################################################################################################################################### 
-################################################################################################################################################################################### 
-*/ 
    
-===== 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 149: 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 163: 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 187: 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 220: 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 266: 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 311: 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 323: 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 399: 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 431: 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 439: 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 466: 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 504: 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 ====
  
-Asynchronní x Synchronní DS +===== 1. Úvod do distribuovaných systémů (DS) ===== 
-Asynchronní nemají žádné limity na rychlost vykonávání procesů ani trvání enosů zpráv a časovému driftu lokálních hodin(sychronní opak)+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 edstavuje základní charakteristiky DS, problematiku času přehled typických typů selhání, se kterými musí každý návrh systému počítat.
  
-  - Selhání procesu +*Distribuovaný systém je*: 
-    crash / fail-stop - proces přestane vykonávatz algoritmus +  soustava **autonomních procesů** spojených sítí, 
-    libovolné (byzantskéselhání - proces může pracovat déle a odpovídat na zprávyale vykonává chybný algoritmus +  * komunikace probíhá **pouze zprávami** (message passing), 
-  - Selhání kanálu +  * **žádná sdílená paměť****žádné globální hodiny**
-    ztráta zprávy - zpráva se ztratí a nedorazí do cílového procesu +  * procesy mohou **selhávat nezávisle**, 
-    partitioning - procesy jsou rozdělené do disjunktních množinkomunikace v nich je možale mezi nimi ne +  * každý proces má **lokální hodiny**, které mohou běžet různou rychlostí.
-  - 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: +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.
-Spolehlivé doručování, žádná duplikace, žádné vytváření, garantované pořadí doručování+
  
-===== Detekce selhání v DS ===== +==== Asynchronní × Synchronní DS ==== 
-**detektory selhání jejich vlastnosti**+Tyto modely popisují „jaký svět“ předpokládáme při návrhu distribuovaného algoritmu – tedy **jaké vlastnosti sítí, hodin chování procesů** budeme brát jako dané.
  
-**úplnost** - každé selhání je časem detekováno alespoň jedním bezvadným procesem+=== Synchronní model === 
 +  Známe **horní hranice** pro: 
 +    * zpoždění zpráv, 
 +    * rychlost výpočtu, 
 +    * odchylku mezi hodinami. 
 +  * Můžeme navrhovat algoritmy ve **fázích (kolech)** a spolehlivě používat timeouty. 
 +  * Takto předvídatelné sítě se v praxi téměř **nevyskytují** – je to spíš teoretický model.
  
-**přesnost** - nedochází k mylné detekci+=== Asynchronní model === 
 +  * **Žádné záruky** – zpráva může zůstat „viset“ libovolně dlouho, proces se může libovolně zdržet, hodiny se mohou rozjet. 
 +  * Timeouty **nejsou spolehlivé** – nelze říct, jestli se proces „jen zpozdil“ nebo opravdu „spadl“. 
 +  * 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.
  
-nelze dosáhnout obou vlastností současně, je vyžadována  100% úplnost a $\lt$ 100% přesnost.+=== Čá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.
  
 +==== Typy selhání v distribuovaných systémech ====
 +V DS musíme počítat s tím, že **něco selže** – a často i nepozorovaně. Rozlišujeme:
  
 +  * **Selhání procesu**:
 +    * *crash / fail-stop* – proces přestane fungovat (nic už neposílá).
 +    * *byzantské (libovolné) selhání* – proces dál funguje, ale **chybně** (např. posílá nesmysly, nebo 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.
  
-Frekvence selhání roste lineárně s počtem procesů ve skupině (selhání jsou běžná)+==== Předpoklady na komunikační kanál ==== 
 +Distribuované algoritmy často **předpokládají vlastnosti komunikační vrstvy** – obvykle se uvažuje tzv. „ideální, ale pomalá“ síť:
  
-Dva podprotokoly - detekce selhání a šíření informace o selhání+  * **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.
  
-Průběh detekce: +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.
-  - havárie procesu $p_j$ +
-  - detekce selhání procesu $p_j$ ně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í:+===== 2. Detekce selhání v DS ===== 
 +V distribuovaných systémech je běžné, že některé procesy selžou – a 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é.
  
-=== Centralizovaný heartbeat === +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.
-Heartbeat jsou odesílány periodicky, každých $\mathcal{T}$ jednotek času jednomu vybranému procesu $p_j$+
  
-Heartbeat má pořadové číslo, to odeslání heartbeatu je inkrementován lokální čítač pro každý proces+==== 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ěží.
  
-Pokud není heartbeat od $p_i$ přijat v $p_j$ v časovém limitu $\tau$je $p_i$ označen jako havarovaný+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.  
  
-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.+Jinými slovy: **lepší je omylem někoho považovat za mrtvéhonež si nevšimnout, že skutečně spadl**. 
 + 
 +  * Frekvence selhání v praxi roste **lineárně s počtem uzlů** – tj. v systému se stovkami uzlů je běžné, že některé z nich selžou v každém okamžiku. 
 + 
 +==== 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 === 
 +  * 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 ijetí od každého uzlu. 
 +  * Pokud nedorazí heartbeat během $\tau$ → $p_i$ je považován za selhaný.
  
 +**Vlastnosti:**
 +  * Jednoduchá implementace.
 +  * **Nezjistí selhání $p_j$ samotného** – není nikdo, kdo by ho hlídal.
 +  * $p_j$ může být **přetížen**, pokud systém obsahuje hodně uzlů.
  
 === Kruhový heartbeat === === Kruhový heartbeat ===
-Heartbeats jsou odesílány periodicky sousedům každého procesu (jednostranně nebo oboustranně).+  * Každý proces periodicky posílá heartbeat **sousedovi** v kruhu. 
 +  * Lze použít **jednosměrný** nebo **obousměrný** kruh.
  
-  není centrální uzel +**Vlastnosti:** 
-  * není úplné při selhání více procesů (stačí jeden jednosměrného, 2 u obousměrného+  * Není žádný centrální bod → lepší škálování. 
-  * je třeba udržovat kruhovou hierarchii+  * **Není úplný**, pokud selže jeden (v jednosměrném) nebo dva (v obousměrnémuzly. 
 +  * Nutno **udržovat kruhovou topologii** – složitější údržba.
  
-=== All to all heartbeat === +=== All-to-all heartbeat === 
-každý proces periodicky odesílá heartbeat všem ostatním procesům +Každý uzel **periodicky odesílá heartbeat všem ostatní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 === +**Vlastnosti:** 
-**Scalable weakly consistent infection-style proces group membership protocol**+  * **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ů.
  
-  * proces $p_i$ periodicky odesílá zprávu $\texttt{ping}$ náhodně vybranému uzlu $p_j$ a čeká na odpověď $\texttt{ack}$ +=== SWIM protokol ===   
-  * pokud ji nedostance, odešle zprávu $\texttt{ping\_req}$ $\mathcal{K}$ náhodně vybraným uzlům +**Scalable Weakly-consistent Infection-style Membership**
-  tyto uzly se zeptají $p_j$ pomocí zprávy $\texttt{ping}$ a pokud dostanou odpověď, odešlou ji $p_i$ jako $\texttt{ping\_ack}$ +
-  pokud ani jeden z $\mathcal{K}$ uzlů nedostane odpověď, označí $p_i$ $p_j$ jako havarovaný+
  
-esnost lze nastavit volbou $\mathcal{K}$klesá exponenciálně s rostoucím $\mathcal{K}$+Moderní ístup k detekcikterý **škáluje a je adaptivní**.
  
-úplnost je zaručena, průměrný čas detekce je $\frac{e}{e 1}\mathcal{T}$+Každý proces $p_i$ periodicky: 
 +  - 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.
  
-===== Čas a kauzalita v DS ===== +**Vlastnosti:** 
-**uspořádání událostí v DS, fyzické hodiny a jejich synchronizacelogické hodiny a jejich synchronizace**+  * **Přesnost** lze nastavit volbou $\mathcal{K}$ – roste s $\mathcal{K}$chybovost klesá exponenciálně. 
 +  * **Úplnost** je zaručena. 
 +  * **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ů).
  
-clock slew - rozdíl v času hodin dvou procesů, mají li dvoje hodiny nenulovou mimoběžnost, jsou nesynchronizované 
  
-clock drift - rozdíl rychlosti hodin dvou procesů+===== 3. Čas a kauzalita DS ===== 
 +V běžném programu máme přesné hodiny, které ří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.
  
-synchronizace +==== Problémy s časem v DS ==== 
-  * externí +  * **Clock slew** – rozdíl ve skutečném čase mezi dvěma procesy
-    * č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$) +  * **Clock drift** – rozdíl v rychlosti běhu hodin (jeden proces má hodiny „rychlejší“ nebo „pomalejší“ než druhý). 
-    externí hodiny mohou být napojeny na UTC nebo atomové hodiny +  To znamená, že hodiny **nelze úplně sladit**, jen přiblížit.
-    například NTP +
-  interní +
-    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)+==== Synchronizace hodin ==== 
 +Synchronizace může být:
  
-Cristianův algoritmus +=== Externí synchronizace === 
-$$ \mathcal{C}_i \coloneq t + \frac{\mathcal{T}_\text{RT}-l_{\text{min}+l'_{min}}}{2}$+  * Každý proces $p_imá své hodiny $\mathcal{C}_i$ udržované v rozmezí $\delta$ od nějakého **externího referenčního času** $\mathcal{S}$. 
-chyba je omezenatj maximálně $\frac{\mathcal{T}_\text{RT} - l_{\text{min}} - l'_{\text{min}}}{2}$+    * Např. atomové hodinyUTC, NTP servery. 
 +    * $|\mathcal{C}_i - \mathcal{S}\leq \delta$ 
 +      * Každý uzel se snaží držet v intervalu $\delta$ od globálního času 
 +  * Typický příklad: **NTP (Network Time Protocol)**.
  
-lokální čas lze posunou libovolně dopředu, ale ne dozaduje možné zvýšit nebo snížit rychlost hodin+=== Interní synchronizace === 
 +  * Zajišťuje, že hodiny **každého páru procesů** se liší nanejvýš o $\delta$. 
 +    * Formálně: $|\mathcal{C}_i - \mathcal{C}_j| \leq \delta$ 
 +      * Rozdíl mezi hodinami dvou uzlů je nejvýše $\delta$ 
 +  * Není vázaná na žádný „reálný čas, ale zajistí, že uzly se navzájem „drží při sobě“. 
 +  * Např. **Berkeley algorithm**.
  
-NTP +==== Praktické algoritmy pro synchronizaci ====
-  * 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+=== Cristianův algoritmus === 
 +  * Klient se zeptá serveru: *„Kolik je hodin?“* 
 +  * Server odpoví, a klient si upraví svůj čas podle:
  
-nepoužívají přímo aktuální čas ale časovou značku+$\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ět, děleno dvěma
  
-kauzální vztah první událost může ovlivnit druhou+Očekávaná **chyba synchronizace** je: $\leq \frac{\mathcal{T}_{RT} l_{\text{min}} - l'_{\text{min}}}{2}$
  
-Relace stalo se před 
-  - 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}$ 
-  - 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}$ a $\mathcal{B} \rightarrow \mathcal{C}$, pak $\mathcal{A} \rightarrow \mathcal{C}$ (tranzitivita) 
  
-Kauzální nezávislost +Lokální čas lze zvyšovat okamžitě, ale **nelze ho vracet zpět** – místo toho se mění rychlost přibývání.
-  * relace stalo se před zavádí částečné uspořádání událostí, potenciální kauzální závislosti +
-  * $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é hodinykteré se aktualizují podle přijímání zpráv+=== NTP === 
 +  * Servery tvoří **stromovou hierarchii** (stratum 01, 2, …). 
 +  * Uzly synchronizují čas s rodiči i některými sousedy. 
 +  * Latence se odhaduje pomocí offsetu:
  
-Synchronizace logických hodin +$o = \frac{(t_1^{r} - t_2^{r+ t_2^{s} - t_1^{s})}{2}$ 
-  - po každé události která se odehraje v $p_i$ se hodiny $\mathcal{C}_i$ inkrementují +  * vypočtený odhad ofsetu mezi hodinami klienta serveru
-  - každé zprávě $m$ odeslané procesem $p_i$ je přiřazená časová značka $ts(m) = \mathcal{C}_i$ +
-  kdykoliv proces $p_j$ přijme zprávu $m$, tak: +
-    - upraví své lokální hodiny $\mathcal{C}_j$ na $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! +==== Logické hodiny a kauzalita ==== 
-  * Platí: jestliže $e_1 \rightarrow e_2$pak $\mathcal{C}(e_1) \lt \mathcal{C}(e_2)$ +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“.
-  Neplatí: jestliže $\mathcal{C}(e_1) \lt \mathcal{C}(e_2)$, pak $e_1 → e_2$+
  
-Vektorové hodiny - každý proces si udržuje vektor celočíselných hodin ostatních procesů+=== Kauzální vztah – relace „stalo se před“ (→) === 
 +Událost $\mathcal{A}$ **mohla ovlivnit** událost $\mathcal{B}$, pokud:
  
-===== Globální stav v DS jeho výpočet ===== +  * $\mathcal{A} \rightarrow \mathcal{B}$ – pokud obě nastaly ve stejném procesu $\mathcal{A}$ byla dřív, 
-**řez distribuovaného výpočtualgoritmus pro distribuovaný globální snapshot, stabilní vlastnosti DS**+  nebo $\mathcal{A}$ je odeslání zprávy a $\mathcal{B}$ je její přijetí, 
 +  nebo (tranzitivně): $\mathcal{A} \rightarrow \mathcal{B} \rightarrow \mathcal{C}$ ⇒ $\mathcal{A} \rightarrow \mathcal{C}$
  
-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+=== Kauzální nezávislost === 
 +  * Události $e_1$, $e_2$ jsou **současné** (nezávislé), pokud:  $$ e_1 \parallel e_2 $$ 
 +  * Jinými slovy: žádná z nich nemohla ovlivnit druhou.
  
-globální snapshot je záznam globálního stavu+=== 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 $$
  
-například +Důležité:   
-  * garbage collection +  * Jestliže $e_1 \rightarrow e_2$, pak $\mathcal{C}(e_1) < \mathcal{C}(e_2)$ 
-  * deadlock detection +  * Ale: $\mathcal{C}(e_1) < \mathcal{C}(e_2)$ **neznamená**, že $e_1 \rightarrow e_2$
-  * detekce ukončení výpočtu +
-  checkpointing+
  
-Řez - časová hranice v každém procesu a v každém komunikačním kanále +⇒ Lamportovy hodiny **respektují kauzalitu**, ale **neumí ji zpětně ověřit**.
-  události které nastanou před řezem jsou v řezu +
-  události které nastanou po něm jsoui mimo řez+
  
-Konzistentní řez - řez $\mathcal{Ř}$ je konzistentní pokud splňuje kauzalitutjpokud pro každý pár událostí $e$, $fv systému platí: +=== Vektorové hodiny === 
-$$ f \in \mathcal{Ř} \land e \rightarrow f \implies e \in \mathcal{Ř}$$ +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“: 
-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)+ 
 +  * Vektor $V_i$ proces $p_i$ má délku $n$ (počet procesů). 
 +  * Při každé lokální události: $V_i[i] += 1$ 
 +  * Při odeslání zprávy se posílá kopie $V_i$ 
 +  * 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 $$  a poté $V_i[i] += 1$ 
 + 
 +Událost $e_1$ př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á} $$ 
 + 
 +To už umí odhalit **kauzální závislost i nezávislost**. 
 + 
 + 
 +===== 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 checkpointingTato 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ř. logickyoffline). 
 + 
 +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 685: 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 `OK` od všech 
- - Čeká na odpověď +  * Ostatní procesy odpoví podle kauzálního (Lamportova) času 
- - Po přijetí OK je stav změ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ů +// 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)