diff --git a/pytania/OBRONA_MAGISTERSKA_ODPOWIEDZI.md b/pytania/OBRONA_MAGISTERSKA_ODPOWIEDZI.md index c4aea80..382b3a2 100644 --- a/pytania/OBRONA_MAGISTERSKA_ODPOWIEDZI.md +++ b/pytania/OBRONA_MAGISTERSKA_ODPOWIEDZI.md @@ -468,197 +468,95 @@ Przykład — kluczowa różnica: decrease-key: ### Pseudokod (Python) -**Dijkstra:** +**Dijkstra** (graph = słownik sąsiedztwa, np. `{'A': [('B',2), ('C',4)]}`): - def dijkstra(graph, start): # graph = słownik, klucz = wierzchołek, - # wartość = lista par (sąsiad, waga) - # np. graph = {'A': [('B',2), ('C',4)], - # 'B': [('D',3)], ...} - # start = wierzchołek startowy, np. 'A' - - d = {v: float('inf') for v in graph} # d = słownik odległości (distance) - # Klucz: wierzchołek v - # Wartość: najkrótsza DOTYCHCZAS ZNANA - # odległość od start do v - # Na początku: ∞ (nieskończoność) dla - # wszystkich — bo jeszcze niczego - # nie odkryliśmy - # float('inf') = Python-owa nieskończoność, - # każda liczba jest od niej mniejsza - - d[start] = 0 # odległość od startu do samego siebie = 0 - - visited = set() # visited = zbiór ZAMKNIĘTYCH wierzchołków - # (już przetworzonych, nie wracamy do nich) - # set() = pusty zbiór Pythona — O(1) lookup - - for _ in range(len(graph)): # powtórz V razy (raz na każdy wierzchołek) - # W każdej iteracji wybieramy JEDEN - # wierzchołek o min d[v] i go przetwarzamy - - # --- Szukanie minimum w tablicy d --- → O(V) na każde szukanie - u = None # u = wierzchołek o najmniejszej odległości - # (jeszcze nie odwiedzony) - for v in graph: # przejrzyj WSZYSTKIE wierzchołki - if v not in visited: # pomiń już odwiedzone - if u is None or d[v] < d[u]: # jeśli v ma mniejszą odległość - u = v # zapamiętaj go jako kandydata - # Po tej pętli: u = wierzchołek z min d, spośród nieodwiedzonych - # To jest O(V) — przeszukujemy całą tablicę! - - if d[u] == float('inf'): # jeśli minimum to ∞, reszta jest - break # nieosiągalna — koniec - - visited.add(u) # oznacz u jako odwiedzony (zamknięty) - # NIE WRACAMY do u — to jest ZACHŁANNOŚĆ - # Dijkstry (i dlatego ujemne wagi psują!) - - for v, w in graph[u]: # iteruj po sąsiadach wierzchołka u - # v = sąsiad (vertex), w = waga krawędzi u→v - # np. graph['A'] = [('B',2), ('C',4)] - # → v='B', w=2, potem v='C', w=4 - - if d[u] + w < d[v]: # RELAKSACJA: czy droga do v PRZEZ u - # jest krótsza niż dotychczas znana? - # d[u] = koszt dotarcia do u - # w = koszt krawędzi u→v - # d[u]+w = koszt drogi start→u→v - # d[v] = dotychczasowy najlepszy koszt do v - - d[v] = d[u] + w # TAK, jest krótsza → zaktualizuj! - # (tablica d pełni tu rolę kolejki - # priorytetowej — po prostu szukamy - # minimum w niej w każdej iteracji) - - return d # zwróć słownik najkrótszych odległości - # np. {'A': 0, 'B': 2, 'C': 4, 'D': 5} - # Złożoność: O(V²) — V szukań min × O(V) każde + def dijkstra(graph, source): + dist = {v: float('inf') for v in graph} + dist[source] = 0 + visited = set() + for _ in range(len(graph)): + current = None # szukaj nieodwiedzonego wierzchołka o min dist — O(V) + for v in graph: + if v not in visited and (current is None or dist[v] < dist[current]): + current = v + if dist[current] == float('inf'): + break # reszta nieosiągalna + visited.add(current) # zamknij — NIE wracamy (zachłanność) + for neighbor, weight in graph[current]: # relaksacja sąsiadów + if dist[current] + weight < dist[neighbor]: + dist[neighbor] = dist[current] + weight + return dist # O(V²) z tablicą ![Przejście grafu algorytmem Dijkstry — krok po kroku](img/dijkstra_traversal.png) -**Bellman-Ford:** +**Bellman-Ford** (vertices = lista wierzchołków, edges = lista krotek (src, dst, weight)): - def bellman_ford(vertices, edges, start): # vertices = lista wierzchołków, np. ['A','B','C','D'] - # edges = lista krawędzi, każda to (skąd, dokąd, waga) - # np. [('A','B',2), ('A','C',4), ('B','D',3), ...] - # start = wierzchołek startowy - # UWAGA: format inny niż w Dijkstrze! - # Dijkstra: graf jako słownik sąsiedztwa - # B-F: explicite lista krawędzi + def bellman_ford(vertices, edges, source): + dist = {v: float('inf') for v in vertices} + dist[source] = 0 + for _ in range(len(vertices) - 1): # V−1 iteracji (najdłuższa ścieżka = V−1 krawędzi) + for src, dst, weight in edges: # relaksuj WSZYSTKIE krawędzie + if dist[src] + weight < dist[dst]: + dist[dst] = dist[src] + weight + for src, dst, weight in edges: # V-ta iteracja: wykrywanie cyklu ujemnego + if dist[src] + weight < dist[dst]: + return None # cykl ujemny! + return dist # O(V·E) - d = {v: float('inf') for v in vertices} # d = słownik odległości — identycznie - # jak w Dijkstrze. Klucz = wierzchołek, - # wartość = najkrótsza znana odległość. - # Na starcie: ∞ dla wszystkich. +Przykład — graf z ujemnymi wagami (Dijkstra daje ZŁY wynik, B-F poprawny): - d[start] = 0 # odległość do siebie = 0 + Graf: S→A(2), A→C(3), S→B(5), B→A(−4) - for _ in range(len(vertices) - 1): # powtórz V−1 razy (V = liczba wierzchołków) - # DLACZEGO V−1? Bo najdłuższa najkrótsza - # ścieżka (bez cykli) ma co najwyżej V−1 - # krawędzi. Po k iteracjach mamy poprawne - # odległości dla ścieżek o ≤ k krawędziach. - # _ = zmienna, której nie używamy (konwencja) + Dijkstra: + 1. S(0): dist[A]=2, dist[B]=5 + 2. A(2) zamknięty: dist[C]=5 + 3. B(5): B→A = 5−4 = 1 < 2, ALE A już zamknięty → POMIJA! + Wynik: A=2, C=5 ← BŁĄD (prawidłowe: A=1, C=4) - for u, v, w in edges: # w KAŻDEJ iteracji przejrzyj WSZYSTKIE krawędzie - # u = początek krawędzi, v = koniec, w = waga - # To jest brute-force — stąd O(V·E) + Bellman-Ford — relaksuje WSZYSTKIE krawędzie, V−1 = 3 razy: + Start: dist = [S:0, A:∞, B:∞, C:∞] - if d[u] + w < d[v]: # RELAKSACJA — identyczna jak w Dijkstrze: - # czy droga start→u→v jest krótsza niż d[v]? + Iteracja 1: + S→A: 0+2=2 < ∞ → A=2 + A→C: 2+3=5 < ∞ → C=5 + S→B: 0+5=5 < ∞ → B=5 + B→A: 5−4=1 < 2 → A=1 ← ujemna waga poprawia! - d[v] = d[u] + w # TAK → zaktualizuj + Iteracja 2: + A→C: 1+3=4 < 5 → C=4 ← propagacja poprawionego A - # --- Wykrywanie cyklu ujemnego --- - for u, v, w in edges: # dodatkowe (V-te) przejście po krawędziach - # Jeśli NADAL da się poprawić odległość, - # to znaczy, że istnieje cykl ujemny! - # (po V−1 iteracjach powinno być stabilne) + Iteracja 3: brak zmian → stabilne. + Wynik: [S:0, A:1, B:5, C:4] ← POPRAWNE - if d[u] + w < d[v]: # nadal można polepszyć? → cykl ujemny! - return None # zwróć None = sygnał "cykl ujemny wykryty" - - return d # zwróć słownik odległości (jak Dijkstra) + Wykrywanie cyklu ujemnego — dodaj krawędź C→B(−3): + Cykl B→A→C→B = −4 + 3 + (−3) = −4 < 0. + Po V−1 iteracjach dist nadal maleje → V-ta iteracja: + dist[src] + weight < dist[dst] → return None ![Przejście grafu algorytmem Bellmana-Forda — krok po kroku](img/bellman_ford_traversal.png) -**A*:** +**A\*** (graph jak Dijkstra; heuristic = h(v) → oszacowanie odl. do celu): - def a_star(graph, start, goal, h): # graph = słownik sąsiedztwa (jak Dijkstra) - # start = wierzchołek startowy - # goal = wierzchołek DOCELOWY (cel) - # → to jedyna różnica od Dijkstry: - # szukamy ścieżki do JEDNEGO celu - # h = FUNKCJA heurystyczna: h(v) zwraca - # oszacowanie odległości od v do goal - # np. h = lambda v: odl_euklidesowa(v, goal) - - d = {start: 0} # d = słownik g(n) = faktyczny koszt - # dotarcia od start do n - # Tu trzymamy TYLKO odkryte wierzchołki - # (nie inicjalizujemy ∞ dla reszty) - - f = {start: h(start)} # f = słownik f(n) = g(n) + h(n) - # f to szacunkowy ŁĄCZNY koszt ścieżki: - # dotychczasowy koszt g + heurystyka h - # Sortujemy po f (nie po g!) — to kieruje - # przeszukiwanie W STRONĘ CELU - # Na starcie: f(start) = 0 + h(start) - - came_from = {} # came_from = słownik "skąd przyszliśmy" - # Klucz: wierzchołek v - # Wartość: wierzchołek, z którego dotarliśmy do v - # Służy do ODTWORZENIA ścieżki po znalezieniu celu - # np. came_from = {'B':'A', 'D':'B'} - # → ścieżka: A → B → D - - visited = set() # visited = zbiór zamkniętych wierzchołków - # (już przetworzonych) - - while f: # dopóki są odkryte, nieprzetworzone wierzchołki - # (f zawiera tylko te, do których dotarliśmy) - - # --- Szukanie minimum f w tablicy --- → O(V) - u = min(f, key=f.get) # u = wierzchołek o najniższym f(n) - # min() przeszukuje WSZYSTKIE klucze w f - # key=f.get → porównuj po wartościach f[v] - # Równoważne: for v in f: if f[v] < f[best]... - del f[u] # usuń u z open set (przetwarzamy go teraz) - - if u == goal: break # ZNALEZIONO CEL! → przerwij - # Kluczowa optymalizacja A*: - # Dijkstra przetwarza WSZYSTKIE wierzchołki, - # A* KOŃCZY gdy dotrze do celu - - visited.add(u) # oznacz u jako przetworzony - - for v, w in graph[u]: # iteruj po sąsiadach u - # v = sąsiad, w = waga krawędzi u→v - - if v in visited: # jeśli v już przetworzony → pomiń + def a_star(graph, source, goal, heuristic): + cost_so_far = {source: 0} # g(n) — faktyczny koszt dotarcia + priority = {source: heuristic(source)} # f(n) = g(n) + h(n) + came_from = {} # do odtworzenia ścieżki + visited = set() + while priority: + current = min(priority, key=priority.get) # wierzchołek o min f(n) + del priority[current] + if current == goal: + break # dotarliśmy — A* kończy (Dijkstra przetworzyłby wszystko) + visited.add(current) + for neighbor, weight in graph[current]: + if neighbor in visited: continue - - g_new = d[u] + w # g_new = potencjalny nowy koszt dotarcia do v - # (koszt do u + krawędź u→v) - - if v not in d or g_new < d[v]: # jeśli v jeszcze nie odkryty - # LUB znaleźliśmy krótszą drogę - - d[v] = g_new # zaktualizuj g(v) = faktyczny koszt do v - - f[v] = g_new + h(v) # zaktualizuj f(v) = g(v) + h(v) - # f kieruje przeszukiwanie: - # niskie f = „obiecujący" wierzchołek - # (blisko celu wg heurystyki) - - came_from[v] = u # zapamiętaj: do v dotarliśmy z u - # (do odtworzenia ścieżki) - - return came_from, d.get(goal) # came_from = mapa do odtworzenia ścieżki - # d.get(goal) = koszt najkrótszej ścieżki - # do celu (None jeśli nieosiągalny) - # Złożoność: O(V²) z tablicą, ale w praktyce - # dużo szybciej dzięki heurystyce + new_cost = cost_so_far[current] + weight + if neighbor not in cost_so_far or new_cost < cost_so_far[neighbor]: + cost_so_far[neighbor] = new_cost + priority[neighbor] = new_cost + heuristic(neighbor) + came_from[neighbor] = current + return came_from, cost_so_far.get(goal) # ścieżka + koszt ![Przejście grafu algorytmem A* — krok po kroku](img/astar_traversal.png) diff --git a/pytania/__pycache__/generate_q9_all_diagrams.cpython-314.pyc b/pytania/__pycache__/generate_q9_all_diagrams.cpython-314.pyc new file mode 100644 index 0000000..cacd13b Binary files /dev/null and b/pytania/__pycache__/generate_q9_all_diagrams.cpython-314.pyc differ diff --git a/pytania/generate_q20_diagrams.py b/pytania/generate_q20_diagrams.py new file mode 100644 index 0000000..a4e1241 --- /dev/null +++ b/pytania/generate_q20_diagrams.py @@ -0,0 +1,1172 @@ +#!/usr/bin/env python3 +""" +Generate ALL diagrams for PYTANIE 20: Analityka danych strumieniowych. +Monochrome, A4-printable PNGs (300 DPI). +""" + +import matplotlib +matplotlib.use('Agg') +import matplotlib.pyplot as plt +import matplotlib.patches as mpatches +from matplotlib.patches import FancyBboxPatch +import numpy as np +import os + +DPI = 300 +BG = 'white' +LN = 'black' +FS = 8 +FS_TITLE = 11 +FS_SMALL = 6.5 +FS_LABEL = 9 +OUTPUT_DIR = os.path.join(os.path.dirname(os.path.abspath(__file__)), 'img') +os.makedirs(OUTPUT_DIR, exist_ok=True) + +GRAY1 = '#E8E8E8' +GRAY2 = '#D0D0D0' +GRAY3 = '#B8B8B8' +GRAY4 = '#F5F5F5' +GRAY5 = '#C0C0C0' + + +def draw_box(ax, x, y, w, h, text, fill='white', lw=1.2, fontsize=FS, + fontweight='normal', ha='center', va='center', rounded=True, + edgecolor=LN, linestyle='-'): + if rounded: + rect = FancyBboxPatch((x, y), w, h, boxstyle="round,pad=0.05", + lw=lw, edgecolor=edgecolor, facecolor=fill, + linestyle=linestyle) + else: + rect = mpatches.Rectangle((x, y), w, h, lw=lw, edgecolor=edgecolor, + facecolor=fill, linestyle=linestyle) + ax.add_patch(rect) + ax.text(x + w/2, y + h/2, text, ha=ha, va=va, fontsize=fontsize, + fontweight=fontweight, wrap=True) + + +def draw_arrow(ax, x1, y1, x2, y2, lw=1.2, style='->', color=LN): + ax.annotate("", xy=(x2, y2), xytext=(x1, y1), + arrowprops=dict(arrowstyle=style, color=color, lw=lw)) + + +def save_fig(fig, name): + path = os.path.join(OUTPUT_DIR, name) + fig.savefig(path, dpi=DPI, bbox_inches='tight', facecolor=BG, pad_inches=0.15) + plt.close(fig) + print(f" Saved: {path}") + + +def draw_table(ax, headers, rows, x0, y0, col_widths, row_h=0.4, + header_fill=GRAY2, row_fills=None, fontsize=FS, header_fontsize=None): + if header_fontsize is None: + header_fontsize = fontsize + n_cols = len(headers) + # Header + cx = x0 + for j, hdr in enumerate(headers): + draw_box(ax, cx, y0, col_widths[j], row_h, hdr, fill=header_fill, + fontsize=header_fontsize, fontweight='bold', rounded=False) + cx += col_widths[j] + # Rows + for i, row in enumerate(rows): + cy = y0 - (i + 1) * row_h + cx = x0 + fill = GRAY4 if (i % 2 == 0) else 'white' + if row_fills and i < len(row_fills): + fill = row_fills[i] + for j, cell in enumerate(row): + fw = 'bold' if j == 0 else 'normal' + draw_box(ax, cx, cy, col_widths[j], row_h, cell, fill=fill, + fontsize=fontsize, fontweight=fw, rounded=False) + cx += col_widths[j] + + +# ============================================================ +# 1. Batch vs Streaming concept +# ============================================================ +def gen_batch_vs_streaming(): + fig, axes = plt.subplots(2, 1, figsize=(9, 5)) + fig.suptitle('Batch vs Streaming — dwa modele przetwarzania', + fontsize=FS_TITLE, fontweight='bold') + + # Batch + ax = axes[0] + ax.set_xlim(0, 12) + ax.set_ylim(0, 3) + ax.set_aspect('auto') + ax.axis('off') + ax.set_title('BATCH (wsadowe)', fontsize=FS_LABEL, fontweight='bold') + + # Data collected + draw_box(ax, 0.5, 0.8, 3.0, 1.4, 'Zbierz WSZYSTKIE\ndane\n(godziny / dni)', + fill=GRAY1, fontsize=FS, fontweight='bold') + draw_arrow(ax, 3.5, 1.5, 4.5, 1.5, lw=2) + draw_box(ax, 4.5, 0.8, 2.5, 1.4, 'Analiza\n(batch job)', + fill=GRAY2, fontsize=FS, fontweight='bold') + draw_arrow(ax, 7.0, 1.5, 8.0, 1.5, lw=2) + draw_box(ax, 8.0, 0.8, 2.5, 1.4, 'Wynik\n(jednorazowy)', + fill=GRAY3, fontsize=FS, fontweight='bold') + ax.text(11.0, 1.5, 'min-h', fontsize=FS, va='center', fontweight='bold') + + # Streaming + ax = axes[1] + ax.set_xlim(0, 12) + ax.set_ylim(0, 3) + ax.set_aspect('auto') + ax.axis('off') + ax.set_title('STREAMING (strumieniowe)', fontsize=FS_LABEL, fontweight='bold') + + # Events flowing + events_x = [0.5, 1.5, 2.5, 3.5] + for i, ex in enumerate(events_x): + draw_box(ax, ex, 1.0, 0.8, 0.8, f'e{i+1}', fill=GRAY4, + fontsize=FS, fontweight='bold', rounded=False) + if i < len(events_x) - 1: + draw_arrow(ax, ex + 0.8, 1.4, ex + 1.0, 1.4, lw=1) + + ax.text(4.8, 1.4, '...', fontsize=FS_LABEL, va='center') + draw_arrow(ax, 5.2, 1.4, 5.8, 1.4, lw=2) + + draw_box(ax, 5.8, 0.8, 2.8, 1.4, 'Analiza\nCIĄGŁA\n(event-by-event)', + fill=GRAY2, fontsize=FS, fontweight='bold') + draw_arrow(ax, 8.6, 1.5, 9.3, 1.5, lw=2) + draw_box(ax, 9.3, 0.8, 2.0, 1.4, 'Wyniki\nciągłe', + fill=GRAY3, fontsize=FS, fontweight='bold') + ax.text(11.5, 0.5, 'ms-s', fontsize=FS, va='center', fontweight='bold') + + # Arrow marking infinity + ax.annotate('', xy=(0.2, 1.4), xytext=(-0.3, 1.4), + arrowprops=dict(arrowstyle='->', lw=1.5, color=LN)) + ax.text(0.0, 2.3, '∞ zdarzeń', fontsize=FS_SMALL, ha='center', style='italic') + + fig.tight_layout(rect=[0, 0, 1, 0.92]) + save_fig(fig, 'q20_batch_vs_streaming.png') + + +# ============================================================ +# 2. All 4 window types (TSSG) +# ============================================================ +def gen_window_types(): + fig, axes = plt.subplots(4, 1, figsize=(9, 10)) + fig.suptitle('4 typy okien — TSSG', fontsize=FS_TITLE, fontweight='bold') + + # Events on a timeline (shared concept) + events = list(range(1, 13)) + + # --- Tumbling --- + ax = axes[0] + ax.set_xlim(0, 14) + ax.set_ylim(0, 4) + ax.set_aspect('auto') + ax.axis('off') + ax.set_title('Tumbling Window (okno przerzutne) — rozłączne, stały rozmiar', + fontsize=FS_LABEL, fontweight='bold') + + # Time axis + ax.annotate('', xy=(13.5, 1.0), xytext=(0.3, 1.0), + arrowprops=dict(arrowstyle='->', lw=1.5, color=LN)) + ax.text(13.5, 0.6, 'czas', fontsize=FS_SMALL, ha='center') + + # Events + for i, e in enumerate(events): + x = 1.0 + i * 1.0 + ax.plot(x, 1.0, 'ko', markersize=5) + ax.text(x, 0.5, f'e{e}', fontsize=FS_SMALL, ha='center') + + # Windows + colors_w = [GRAY1, GRAY3, GRAY1, GRAY3] + for w in range(4): + x_start = 1.0 + w * 3.0 - 0.3 + rect = mpatches.FancyBboxPatch((x_start, 1.5), 3.0, 1.2, + boxstyle="round,pad=0.1", facecolor=colors_w[w], + edgecolor=LN, lw=1.5) + ax.add_patch(rect) + ax.text(x_start + 1.5, 2.1, f'Okno {w+1}', fontsize=FS, + ha='center', fontweight='bold') + # Braces down to events + for j in range(3): + ex = 1.0 + w * 3.0 + j * 1.0 + ax.plot([ex, ex], [1.0, 1.5], color=LN, lw=0.8, linestyle='--') + + ax.text(7.0, 3.2, 'Każde zdarzenie → DOKŁADNIE 1 okno. Zero nakładania.', + fontsize=FS, ha='center', style='italic', + bbox=dict(boxstyle='round,pad=0.2', facecolor=GRAY4, edgecolor=GRAY5)) + + # --- Sliding --- + ax = axes[1] + ax.set_xlim(0, 14) + ax.set_ylim(0, 5) + ax.set_aspect('auto') + ax.axis('off') + ax.set_title('Sliding Window (okno przesuwne) — nakładające, stały rozmiar + krok', + fontsize=FS_LABEL, fontweight='bold') + + ax.annotate('', xy=(13.5, 1.0), xytext=(0.3, 1.0), + arrowprops=dict(arrowstyle='->', lw=1.5, color=LN)) + ax.text(13.5, 0.6, 'czas', fontsize=FS_SMALL, ha='center') + + for i, e in enumerate(events[:8]): + x = 1.0 + i * 1.0 + ax.plot(x, 1.0, 'ko', markersize=5) + ax.text(x, 0.5, f'e{e}', fontsize=FS_SMALL, ha='center') + + # Sliding windows: size=4, slide=2 + slide_colors = [GRAY1, GRAY2, GRAY3] + for w in range(3): + x_start = 0.7 + w * 2.0 + y_base = 1.5 + w * 0.9 + rect = mpatches.FancyBboxPatch((x_start, y_base), 4.0, 0.7, + boxstyle="round,pad=0.08", facecolor=slide_colors[w], + edgecolor=LN, lw=1.5, alpha=0.7) + ax.add_patch(rect) + ax.text(x_start + 2.0, y_base + 0.35, f'Okno {w+1} (size=4)', + fontsize=FS_SMALL, ha='center', fontweight='bold') + + ax.text(10.5, 3.5, 'krok=2\nNakładanie!\ne3,e4 → w oknie 1 i 2', + fontsize=FS, ha='center', style='italic', + bbox=dict(boxstyle='round,pad=0.2', facecolor=GRAY4, edgecolor=GRAY5)) + + # --- Session --- + ax = axes[2] + ax.set_xlim(0, 14) + ax.set_ylim(0, 4) + ax.set_aspect('auto') + ax.axis('off') + ax.set_title('Session Window (okno sesji) — dynamiczny rozmiar, gap = przerwa', + fontsize=FS_LABEL, fontweight='bold') + + ax.annotate('', xy=(13.5, 1.0), xytext=(0.3, 1.0), + arrowprops=dict(arrowstyle='->', lw=1.5, color=LN)) + ax.text(13.5, 0.6, 'czas', fontsize=FS_SMALL, ha='center') + + # Cluster 1: events close together + cluster1 = [1.0, 1.8, 2.3, 3.0] + for x in cluster1: + ax.plot(x, 1.0, 'ko', markersize=5) + + # Gap + ax.annotate('', xy=(7.0, 0.7), xytext=(4.0, 0.7), + arrowprops=dict(arrowstyle='<->', lw=1, color=LN)) + ax.text(5.5, 0.3, 'GAP > timeout', fontsize=FS, ha='center', + fontweight='bold', style='italic') + + # Cluster 2 + cluster2 = [8.0, 8.8, 9.5] + for x in cluster2: + ax.plot(x, 1.0, 'ko', markersize=5) + + # Session boxes + rect1 = mpatches.FancyBboxPatch((0.7, 1.4), 2.6, 1.0, + boxstyle="round,pad=0.1", facecolor=GRAY1, edgecolor=LN, lw=1.5) + ax.add_patch(rect1) + ax.text(2.0, 1.9, 'Sesja 1\n(4 zdarzenia)', fontsize=FS, ha='center', + fontweight='bold') + + rect2 = mpatches.FancyBboxPatch((7.7, 1.4), 2.1, 1.0, + boxstyle="round,pad=0.1", facecolor=GRAY3, edgecolor=LN, lw=1.5) + ax.add_patch(rect2) + ax.text(8.75, 1.9, 'Sesja 2\n(3 zdarzenia)', fontsize=FS, ha='center', + fontweight='bold') + + ax.text(5.5, 3.0, 'Nowa sesja po przerwie > gap', + fontsize=FS, ha='center', style='italic') + + # --- Global --- + ax = axes[3] + ax.set_xlim(0, 14) + ax.set_ylim(0, 4) + ax.set_aspect('auto') + ax.axis('off') + ax.set_title('Global Window — jedno okno na cały strumień + trigger', + fontsize=FS_LABEL, fontweight='bold') + + ax.annotate('', xy=(13.5, 1.0), xytext=(0.3, 1.0), + arrowprops=dict(arrowstyle='->', lw=1.5, color=LN)) + ax.text(13.5, 0.6, 'czas', fontsize=FS_SMALL, ha='center') + + for i in range(12): + x = 1.0 + i * 1.0 + ax.plot(x, 1.0, 'ko', markersize=5) + + # One big window + rect = mpatches.FancyBboxPatch((0.5, 1.4), 12.5, 1.0, + boxstyle="round,pad=0.1", facecolor=GRAY1, edgecolor=LN, lw=2) + ax.add_patch(rect) + ax.text(6.75, 1.9, 'GLOBAL WINDOW (cały strumień)', fontsize=FS, + ha='center', fontweight='bold') + + # Trigger markers + for tx in [4.0, 8.0, 12.0]: + ax.plot([tx, tx], [1.4, 2.4], color=LN, lw=2, linestyle='--') + ax.text(tx, 2.7, 'EMIT', fontsize=FS_SMALL, ha='center', + fontweight='bold', + bbox=dict(boxstyle='round,pad=0.1', facecolor=GRAY3, edgecolor=LN)) + + ax.text(6.75, 3.3, 'Trigger decyduje kiedy emitować (np. co N zdarzeń)', + fontsize=FS, ha='center', style='italic') + + fig.tight_layout(rect=[0, 0, 1, 0.94]) + save_fig(fig, 'q20_window_types.png') + + +# ============================================================ +# 3. Event Time vs Processing Time scatter + watermark +# ============================================================ +def gen_event_vs_processing_time(): + fig, axes = plt.subplots(1, 2, figsize=(11, 5)) + fig.suptitle('Event Time vs Processing Time + Watermark', + fontsize=FS_TITLE, fontweight='bold') + + # --- Panel 1: Ideal vs Real --- + ax = axes[0] + ax.set_xlim(0, 10) + ax.set_ylim(0, 10) + ax.set_aspect('equal') + ax.set_xlabel('Event Time', fontsize=FS_LABEL) + ax.set_ylabel('Processing Time', fontsize=FS_LABEL) + ax.set_title('Idealny vs Realny świat', fontsize=FS_LABEL, fontweight='bold') + ax.set_xticks([]) + ax.set_yticks([]) + + # Ideal line + ax.plot([0, 9], [0, 9], 'k--', lw=1.5, label='ideał (brak opóźnień)') + + # Real scattered points (processing >= event, some out of order) + np.random.seed(42) + event_times = np.sort(np.random.uniform(1, 8, 15)) + proc_times = event_times + np.random.exponential(0.5, 15) + # Make some out of order + idx = [3, 7, 11] + for i in idx: + proc_times[i] += 1.5 + + ax.scatter(event_times, proc_times, c='black', s=30, zorder=5, + label='zdarzenia (realne)') + + # Highlight out-of-order + for i in idx: + ax.annotate('out-of-order', xy=(event_times[i], proc_times[i]), + xytext=(event_times[i] + 0.8, proc_times[i] + 0.5), + fontsize=FS_SMALL, ha='left', + arrowprops=dict(arrowstyle='->', lw=0.8, color='#555')) + + ax.legend(fontsize=FS_SMALL, loc='upper left') + ax.text(7, 2, 'Opóźnienie\nsieciowe ↑', fontsize=FS, ha='center', + style='italic', color='#555') + + # --- Panel 2: Watermark concept --- + ax = axes[1] + ax.set_xlim(0, 10) + ax.set_ylim(0, 10) + ax.set_aspect('equal') + ax.set_xlabel('Event Time', fontsize=FS_LABEL) + ax.set_ylabel('Processing Time', fontsize=FS_LABEL) + ax.set_title('Watermark — granica postępu', fontsize=FS_LABEL, fontweight='bold') + ax.set_xticks([]) + ax.set_yticks([]) + + # Events + ax.scatter(event_times, proc_times, c='black', s=30, zorder=5) + + # Watermark line (below most points, tracks progress) + wm_x = np.linspace(0, 9, 50) + wm_y = wm_x + 0.3 # watermark slightly above ideal + ax.plot(wm_x, wm_y, 'k-', lw=2.5, label='Watermark') + ax.fill_between(wm_x, 0, wm_y, alpha=0.15, color='gray') + + ax.text(2.0, 1.0, 'PONIŻEJ watermark:\n„na pewno dotarło"', + fontsize=FS, ha='center', fontweight='bold', + bbox=dict(boxstyle='round,pad=0.2', facecolor=GRAY4, edgecolor=GRAY5)) + + # Late event + late_x, late_y = event_times[7], proc_times[7] + ax.scatter([late_x], [late_y], c='white', s=80, zorder=6, + edgecolors='black', linewidths=2) + ax.annotate('LATE DATA!\n(po watermarku)', xy=(late_x, late_y), + xytext=(late_x + 1.2, late_y + 0.8), + fontsize=FS_SMALL, ha='left', fontweight='bold', + arrowprops=dict(arrowstyle='->', lw=1, color=LN)) + + ax.legend(fontsize=FS_SMALL, loc='upper left') + + fig.tight_layout(rect=[0, 0, 1, 0.92]) + save_fig(fig, 'q20_event_vs_processing_time.png') + + +# ============================================================ +# 4. Tumbling window example — fraud detection +# ============================================================ +def gen_tumbling_fraud(): + fig, ax = plt.subplots(figsize=(9, 4)) + ax.set_xlim(0, 12) + ax.set_ylim(0, 5.5) + ax.set_aspect('auto') + ax.axis('off') + ax.set_title('Tumbling Window — fraud detection (okno = 1 min)', + fontsize=FS_TITLE, fontweight='bold', pad=10) + + # Time axis + ax.annotate('', xy=(11.5, 1.0), xytext=(0.5, 1.0), + arrowprops=dict(arrowstyle='->', lw=1.5, color=LN)) + ax.text(6.0, 0.4, 'czas', fontsize=FS, ha='center') + + # Window 1: normal + draw_box(ax, 1.0, 1.5, 4.5, 3.0, '', fill=GRAY4, rounded=True, lw=2) + ax.text(3.25, 4.2, '[14:00 — 14:01]', fontsize=FS_LABEL, ha='center', + fontweight='bold') + # Transactions + txns1 = ['Sklep A: 50 zł', 'Sklep B: 30 zł', 'Stacja: 80 zł'] + for i, t in enumerate(txns1): + draw_box(ax, 1.3, 3.3 - i * 0.55, 4.0, 0.45, t, fill=GRAY1, + fontsize=FS_SMALL, rounded=False) + ax.text(3.25, 1.7, 'count = 3 → OK', fontsize=FS, ha='center', + fontweight='bold', color='#2E7D32', + bbox=dict(boxstyle='round,pad=0.15', facecolor='#E8F5E9', edgecolor='#2E7D32')) + + # Window 2: fraud! + draw_box(ax, 6.0, 1.5, 4.5, 3.0, '', fill=GRAY1, rounded=True, lw=2) + ax.text(8.25, 4.2, '[14:01 — 14:02]', fontsize=FS_LABEL, ha='center', + fontweight='bold') + txns2 = ['ATM Warszawa: 500 zł', 'ATM Kraków: 500 zł', '... +45 transakcji'] + for i, t in enumerate(txns2): + draw_box(ax, 6.3, 3.3 - i * 0.55, 4.0, 0.45, t, fill=GRAY3, + fontsize=FS_SMALL, rounded=False) + ax.text(8.25, 1.7, 'count = 47 → ALERT!', fontsize=FS, ha='center', + fontweight='bold', color='#C62828', + bbox=dict(boxstyle='round,pad=0.15', facecolor='#F8D7DA', edgecolor='#C62828')) + + save_fig(fig, 'q20_tumbling_fraud.png') + + +# ============================================================ +# 5. Sliding window — SLA monitoring +# ============================================================ +def gen_sliding_sla(): + fig, ax = plt.subplots(figsize=(9, 4.5)) + ax.set_xlim(0, 12) + ax.set_ylim(0, 6) + ax.set_aspect('auto') + ax.axis('off') + ax.set_title('Sliding Window — monitoring SLA (okno=5min, krok=1min)', + fontsize=FS_TITLE, fontweight='bold', pad=10) + + # Time axis + ax.annotate('', xy=(11.5, 0.5), xytext=(0.5, 0.5), + arrowprops=dict(arrowstyle='->', lw=1.5, color=LN)) + times = ['14:05', '14:06', '14:07', '14:08', '14:09'] + latencies = [120, 180, 340, 290, 150] + sla = 200 + + for i, (t, lat) in enumerate(zip(times, latencies)): + x = 1.5 + i * 2.0 + ax.text(x, 0.1, t, fontsize=FS, ha='center') + + # Bar proportional to latency + bar_h = lat / 100.0 + is_breach = lat > sla + fill = '#F8D7DA' if is_breach else GRAY1 + edge = '#C62828' if is_breach else LN + draw_box(ax, x - 0.5, 1.0, 1.0, bar_h, '', fill=fill, + rounded=False, edgecolor=edge, lw=1.5) + ax.text(x, 1.0 + bar_h + 0.15, f'{lat}ms', fontsize=FS, + ha='center', fontweight='bold', + color='#C62828' if is_breach else LN) + + # Status + status = 'ALERT!' if is_breach else 'OK' + ax.text(x, 1.0 + bar_h + 0.55, status, fontsize=FS_SMALL, + ha='center', fontweight='bold', + color='#C62828' if is_breach else '#2E7D32') + + # SLA line + sla_y = 1.0 + sla / 100.0 + ax.plot([0.8, 11.2], [sla_y, sla_y], 'k--', lw=1.5) + ax.text(11.3, sla_y, f'SLA={sla}ms', fontsize=FS, va='center', + fontweight='bold') + + # Sliding window bracket + ax.annotate('', xy=(1.0, 5.3), xytext=(5.0, 5.3), + arrowprops=dict(arrowstyle='<->', lw=1.5, color=LN)) + ax.text(3.0, 5.6, 'okno = 5 min', fontsize=FS, ha='center', + fontweight='bold') + + ax.annotate('', xy=(3.0, 4.8), xytext=(5.0, 4.8), + arrowprops=dict(arrowstyle='<->', lw=1, color='#555')) + ax.text(4.0, 4.4, 'krok = 1 min\n(nakładanie!)', fontsize=FS_SMALL, + ha='center', style='italic') + + save_fig(fig, 'q20_sliding_sla.png') + + +# ============================================================ +# 6. Session window — user sessions +# ============================================================ +def gen_session_users(): + fig, axes = plt.subplots(2, 1, figsize=(10, 5)) + fig.suptitle('Session Window — sesje użytkowników (gap = 30 min)', + fontsize=FS_TITLE, fontweight='bold') + + # Anna: 2 sessions + ax = axes[0] + ax.set_xlim(0, 14) + ax.set_ylim(0, 3.5) + ax.set_aspect('auto') + ax.axis('off') + ax.set_title('Użytkownik Anna', fontsize=FS_LABEL, fontweight='bold') + + ax.annotate('', xy=(13.5, 1.0), xytext=(0.3, 1.0), + arrowprops=dict(arrowstyle='->', lw=1.5, color=LN)) + + # Clicks cluster 1 + for x in [1.0, 1.8, 2.5, 3.2]: + ax.plot(x, 1.0, 'ko', markersize=6) + # Clicks cluster 2 + for x in [9.0, 9.8, 10.5]: + ax.plot(x, 1.0, 'ko', markersize=6) + + # Sessions + rect1 = mpatches.FancyBboxPatch((0.7, 1.5), 2.8, 1.2, + boxstyle="round,pad=0.1", facecolor=GRAY1, edgecolor=LN, lw=1.5) + ax.add_patch(rect1) + ax.text(2.1, 2.1, 'Sesja 1\n4 kliknięcia, 12 min', fontsize=FS, + ha='center', fontweight='bold') + + rect2 = mpatches.FancyBboxPatch((8.7, 1.5), 2.1, 1.2, + boxstyle="round,pad=0.1", facecolor=GRAY3, edgecolor=LN, lw=1.5) + ax.add_patch(rect2) + ax.text(9.75, 2.1, 'Sesja 2\n3 kliknięcia, 8 min', fontsize=FS, + ha='center', fontweight='bold') + + # Gap + ax.annotate('', xy=(8.5, 0.5), xytext=(3.8, 0.5), + arrowprops=dict(arrowstyle='<->', lw=1.5, color=LN)) + ax.text(6.15, 0.1, 'cisza 45 min > gap(30)', fontsize=FS, ha='center', + fontweight='bold', style='italic') + + # Bob: 1 session + ax = axes[1] + ax.set_xlim(0, 14) + ax.set_ylim(0, 3.5) + ax.set_aspect('auto') + ax.axis('off') + ax.set_title('Użytkownik Bob', fontsize=FS_LABEL, fontweight='bold') + + ax.annotate('', xy=(13.5, 1.0), xytext=(0.3, 1.0), + arrowprops=dict(arrowstyle='->', lw=1.5, color=LN)) + + # Clicks spread evenly + bobs = [1.0, 2.5, 4.0, 5.5, 7.0, 8.5, 10.0] + for x in bobs: + ax.plot(x, 1.0, 'ko', markersize=6) + + rect = mpatches.FancyBboxPatch((0.7, 1.5), 9.6, 1.2, + boxstyle="round,pad=0.1", facecolor=GRAY1, edgecolor=LN, lw=2) + ax.add_patch(rect) + ax.text(5.5, 2.1, 'Sesja 1 (ciągła) — 7 kliknięć, każde < 30 min od poprzedniego', + fontsize=FS, ha='center', fontweight='bold') + + fig.tight_layout(rect=[0, 0, 1, 0.92]) + save_fig(fig, 'q20_session_users.png') + + +# ============================================================ +# 7. Streaming ecosystem overview +# ============================================================ +def gen_streaming_ecosystem(): + fig, ax = plt.subplots(figsize=(10, 5.5)) + ax.set_xlim(0, 12) + ax.set_ylim(0, 7) + ax.set_aspect('auto') + ax.axis('off') + ax.set_title('Ekosystem przetwarzania strumieniowego', + fontsize=FS_TITLE, fontweight='bold', pad=10) + + # Source + draw_box(ax, 0.3, 2.5, 2.0, 3.0, 'Kafka\nTopics\n(źródło)', fill=GRAY2, + fontsize=FS, fontweight='bold') + + # Engines + engines = [ + ('Kafka Streams\n(library w JVM)', GRAY1, 4.7), + ('Apache Flink\n(klaster)', GRAY3, 3.2), + ('Spark Streaming\n(klaster)', GRAY5, 1.7), + ] + for label, color, y in engines: + draw_box(ax, 4.0, y, 3.0, 1.2, label, fill=color, + fontsize=FS, fontweight='bold') + draw_arrow(ax, 2.3, 4.0, 4.0, y + 0.6, lw=1.5) + + # Sinks + sinks = [ + ('Kafka topic\n/ baza danych', GRAY4, 4.7), + ('DB / Kafka\n/ S3', GRAY4, 3.2), + ('HDFS / DB\n/ dashboard', GRAY4, 1.7), + ] + for label, color, y in sinks: + draw_box(ax, 8.5, y, 2.5, 1.2, label, fill=color, + fontsize=FS) + draw_arrow(ax, 7.0, y + 0.6, 8.5, y + 0.6, lw=1.5) + + # Labels + ax.text(1.3, 6.0, 'ŹRÓDŁO', fontsize=FS_LABEL, ha='center', fontweight='bold') + ax.text(5.5, 6.2, 'SILNIK', fontsize=FS_LABEL, ha='center', fontweight='bold') + ax.text(9.75, 6.2, 'WYNIK', fontsize=FS_LABEL, ha='center', fontweight='bold') + + # Latency annotations + ax.text(5.5, 5.95, '~1-10 ms', fontsize=FS_SMALL, ha='center', style='italic') + ax.text(5.5, 4.5, '<10 ms', fontsize=FS_SMALL, ha='center', style='italic') + ax.text(5.5, 3.0, '~100 ms', fontsize=FS_SMALL, ha='center', style='italic') + + save_fig(fig, 'q20_streaming_ecosystem.png') + + +# ============================================================ +# 8. True streaming vs Micro-batch +# ============================================================ +def gen_true_vs_microbatch(): + fig, axes = plt.subplots(2, 1, figsize=(10, 5.5)) + fig.suptitle('True Streaming vs Micro-Batch', + fontsize=FS_TITLE, fontweight='bold') + + # True streaming + ax = axes[0] + ax.set_xlim(0, 12) + ax.set_ylim(0, 3.5) + ax.set_aspect('auto') + ax.axis('off') + ax.set_title('TRUE STREAMING (Flink, Kafka Streams) — event-by-event', + fontsize=FS_LABEL, fontweight='bold') + + for i in range(6): + x = 1.0 + i * 1.8 + # Event + draw_box(ax, x, 2.0, 0.8, 0.7, f'e{i+1}', fill=GRAY1, + fontsize=FS, fontweight='bold', rounded=False) + # Arrow down + draw_arrow(ax, x + 0.4, 2.0, x + 0.4, 1.4, lw=1) + # Result + draw_box(ax, x, 0.5, 0.8, 0.7, f'r{i+1}', fill=GRAY3, + fontsize=FS, fontweight='bold', rounded=False) + # Latency label + ax.text(x + 0.4, 1.6, '~ms', fontsize=5, ha='center', color='#555') + + ax.text(11.5, 1.3, 'Latencja:\n< 10 ms', fontsize=FS, ha='center', + fontweight='bold', + bbox=dict(boxstyle='round,pad=0.2', facecolor=GRAY4, edgecolor=GRAY5)) + + # Micro-batch + ax = axes[1] + ax.set_xlim(0, 12) + ax.set_ylim(0, 3.5) + ax.set_aspect('auto') + ax.axis('off') + ax.set_title('MICRO-BATCH (Spark Streaming) — grupami co ~100ms', + fontsize=FS_LABEL, fontweight='bold') + + batch_colors = [GRAY1, GRAY2, GRAY3] + for b in range(3): + bx = 0.8 + b * 3.5 + # Batch boundary + draw_box(ax, bx, 1.8, 3.0, 1.0, '', fill=batch_colors[b], + rounded=True, lw=1.5) + ax.text(bx + 1.5, 2.6, f'Batch {b+1}', fontsize=FS, + ha='center', fontweight='bold') + for j in range(3): + ex = bx + 0.3 + j * 0.9 + draw_box(ax, ex, 2.0, 0.7, 0.5, f'e{b*3+j+1}', fill='white', + fontsize=FS_SMALL, rounded=False) + + # Arrow down + draw_arrow(ax, bx + 1.5, 1.8, bx + 1.5, 1.2, lw=1.5) + # Result + draw_box(ax, bx + 0.5, 0.4, 2.0, 0.7, f'result {b+1}', fill=GRAY4, + fontsize=FS, fontweight='bold') + + ax.text(11.5, 1.3, 'Latencja:\n~100ms–s', fontsize=FS, ha='center', + fontweight='bold', + bbox=dict(boxstyle='round,pad=0.2', facecolor=GRAY4, edgecolor=GRAY5)) + + fig.tight_layout(rect=[0, 0, 1, 0.92]) + save_fig(fig, 'q20_true_vs_microbatch.png') + + +# ============================================================ +# 9. Platform comparison table +# ============================================================ +def gen_platform_comparison(): + fig, ax = plt.subplots(figsize=(9, 5)) + ax.set_xlim(0, 11.5) + ax.set_ylim(-6, 1) + ax.set_aspect('auto') + ax.axis('off') + ax.set_title('Porównanie platform strumieniowych', + fontsize=FS_TITLE, fontweight='bold', pad=10) + + headers = ['Cecha', 'Kafka Streams', 'Apache Flink', 'Spark Streaming'] + col_w = [2.5, 2.8, 2.8, 2.8] + rows = [ + ['Model', 'event-by-event', 'event-by-event', 'micro-batch (~100ms)'], + ['Deployment', 'library (w JVM)', 'klaster', 'klaster'], + ['Latencja', '~1–10 ms', '< 10 ms', '100 ms – sekundy'], + ['Exactly-once', 'Kafka TXN', 'checkpointing', 'WAL'], + ['State', 'RocksDB local', 'RocksDB + ckpt', 'in-memory / ext'], + ['Okna', 'T, S, Session', 'wszystkie + custom', 'T, S'], + ['Use case', 'Kafka → Kafka', 'złożona analityka', 'ETL + ML / SQL'], + ] + draw_table(ax, headers, rows, x0=0.25, y0=0.5, col_widths=col_w, + row_h=0.6, fontsize=7, header_fontsize=8) + + save_fig(fig, 'q20_platform_comparison.png') + + +# ============================================================ +# 10. Kafka Streams architecture +# ============================================================ +def gen_kafka_streams_arch(): + fig, ax = plt.subplots(figsize=(9, 5)) + ax.set_xlim(0, 12) + ax.set_ylim(0, 7) + ax.set_aspect('auto') + ax.axis('off') + ax.set_title('Kafka Streams — architektura (library w JVM)', + fontsize=FS_TITLE, fontweight='bold', pad=10) + + # Outer box: Your Java application + draw_box(ax, 0.5, 0.5, 11.0, 5.5, '', fill=GRAY4, rounded=True, lw=2.5) + ax.text(6.0, 5.7, 'Twoja aplikacja Java (JVM)', fontsize=FS_LABEL, + ha='center', fontweight='bold') + + # Kafka Consumer + draw_box(ax, 1.0, 3.0, 2.5, 1.5, 'Kafka\nConsumer\n(input topic)', + fill=GRAY1, fontsize=FS, fontweight='bold') + + # Processing + draw_box(ax, 4.5, 3.0, 2.5, 1.5, 'Kafka Streams\n(logika\nbiznesowa)', + fill=GRAY2, fontsize=FS, fontweight='bold') + + # Kafka Producer + draw_box(ax, 8.0, 3.0, 2.5, 1.5, 'Kafka\nProducer\n(output topic)', + fill=GRAY1, fontsize=FS, fontweight='bold') + + # Arrows + draw_arrow(ax, 3.5, 3.75, 4.5, 3.75, lw=2) + draw_arrow(ax, 7.0, 3.75, 8.0, 3.75, lw=2) + + # RocksDB state store + draw_box(ax, 4.5, 1.0, 2.5, 1.3, 'RocksDB\n(stan lokalny)', + fill=GRAY3, fontsize=FS, fontweight='bold') + ax.plot([5.75, 5.75], [3.0, 2.3], color=LN, lw=1.5) + ax.text(7.3, 1.6, 'okna, joiny,\nagregacje', fontsize=FS_SMALL, + style='italic', va='center') + + # Key message + ax.text(6.0, 0.2, 'NIE potrzebujesz osobnego klastra! ' + 'Skalujesz = więcej instancji JVM.', fontsize=FS, ha='center', + fontweight='bold', + bbox=dict(boxstyle='round,pad=0.2', facecolor='white', edgecolor=LN)) + + save_fig(fig, 'q20_kafka_streams_arch.png') + + +# ============================================================ +# 11. Flink architecture + checkpointing +# ============================================================ +def gen_flink_arch(): + fig, ax = plt.subplots(figsize=(9, 6)) + ax.set_xlim(0, 12) + ax.set_ylim(0, 8) + ax.set_aspect('auto') + ax.axis('off') + ax.set_title('Apache Flink — architektura klastra + checkpointing', + fontsize=FS_TITLE, fontweight='bold', pad=10) + + # Cluster border + draw_box(ax, 0.3, 1.0, 11.4, 6.2, '', fill=GRAY4, rounded=True, lw=2.5) + ax.text(6.0, 6.95, 'FLINK CLUSTER', fontsize=FS_LABEL, + ha='center', fontweight='bold') + + # Job Manager + draw_box(ax, 1.0, 5.5, 3.0, 1.2, 'Job Manager\n(koordynacja,\ncheckpointy)', + fill=GRAY2, fontsize=FS, fontweight='bold') + + # Task Managers + draw_box(ax, 1.0, 3.0, 10.0, 2.0, '', fill='white', rounded=True, lw=1.5) + ax.text(6.0, 4.7, 'Task Managers (workery)', fontsize=FS, + ha='center', fontweight='bold') + + slots = ['source\n& map()', 'map()', 'window()\n& reduce', 'sink()'] + for i, s in enumerate(slots): + x = 1.5 + i * 2.4 + draw_box(ax, x, 3.3, 2.0, 1.2, f'Slot {i+1}\n{s}', fill=GRAY1, + fontsize=FS_SMALL, fontweight='bold') + + draw_arrow(ax, 2.5, 5.5, 6.0, 5.0, lw=1.5, style='->') + ax.text(5.0, 5.5, 'przydziela\npodzadania', fontsize=FS_SMALL, + style='italic') + + # Checkpoint storage + draw_box(ax, 5.5, 1.2, 3.5, 1.2, 'Checkpoint Storage\n(HDFS / S3)', + fill=GRAY3, fontsize=FS, fontweight='bold') + ax.plot([7.25, 7.25], [2.4, 3.3], color=LN, lw=1.5, linestyle='--') + ax.text(8.0, 2.7, 'snapshoty\nstanu', fontsize=FS_SMALL, style='italic') + + # Barrier concept at bottom + ax.text(3.0, 1.6, 'Barrier:', fontsize=FS, fontweight='bold') + barrier_boxes = ['source', '|B|', 'map', '|B|', 'sink'] + bx = 0.8 + for i, b in enumerate(barrier_boxes): + if b == '|B|': + ax.text(bx + 0.3, 1.5, b, fontsize=FS, ha='center', + fontweight='bold', + bbox=dict(boxstyle='round,pad=0.1', facecolor=GRAY5, + edgecolor=LN)) + draw_arrow(ax, bx, 1.5, bx + 0.1, 1.5, lw=1) + bx += 0.7 + else: + draw_box(ax, bx, 1.3, 1.0, 0.45, b, fill=GRAY1, + fontsize=FS_SMALL, fontweight='bold') + bx += 1.2 + + save_fig(fig, 'q20_flink_arch.png') + + +# ============================================================ +# 12. Spark Streaming architecture +# ============================================================ +def gen_spark_streaming_arch(): + fig, ax = plt.subplots(figsize=(9, 5)) + ax.set_xlim(0, 12) + ax.set_ylim(0, 7) + ax.set_aspect('auto') + ax.axis('off') + ax.set_title('Spark Streaming — architektura (micro-batch)', + fontsize=FS_TITLE, fontweight='bold', pad=10) + + # Cluster border + draw_box(ax, 0.3, 0.5, 11.4, 5.8, '', fill=GRAY4, rounded=True, lw=2.5) + ax.text(6.0, 6.0, 'SPARK CLUSTER', fontsize=FS_LABEL, + ha='center', fontweight='bold') + + # Driver + draw_box(ax, 1.0, 4.5, 3.0, 1.2, 'Driver\n(planuje mini-batche)', + fill=GRAY2, fontsize=FS, fontweight='bold') + + draw_arrow(ax, 2.5, 4.5, 6.0, 4.0, lw=1.5) + + # Batches + batches = ['batch 1\n(e1,e2,e3)', 'batch 2\n(e4,e5,e6)', 'batch 3\n(e7,e8,e9)'] + for i, b in enumerate(batches): + y = 2.8 - i * 1.0 + draw_box(ax, 4.5, y, 2.5, 0.8, b, fill=GRAY1, + fontsize=FS_SMALL, fontweight='bold') + # map → reduce + draw_arrow(ax, 7.0, y + 0.4, 7.5, y + 0.4, lw=1) + draw_box(ax, 7.5, y, 1.3, 0.8, 'map→\nreduce', fill=GRAY3, + fontsize=5.5) + draw_arrow(ax, 8.8, y + 0.4, 9.3, y + 0.4, lw=1) + draw_box(ax, 9.3, y, 1.5, 0.8, f'result {i+1}', fill='white', + fontsize=FS_SMALL) + + # Spark ecosystem + draw_box(ax, 1.0, 1.0, 3.0, 1.0, 'Spark SQL / MLlib\n(ten sam ekosystem!)', + fill=GRAY5, fontsize=FS, fontweight='bold') + + ax.text(6.0, 0.3, 'ZALETA: batch API | WADA: latencja ≥ batch interval (~100ms)', + fontsize=FS, ha='center', fontweight='bold', + bbox=dict(boxstyle='round,pad=0.2', facecolor='white', edgecolor=LN)) + + save_fig(fig, 'q20_spark_streaming_arch.png') + + +# ============================================================ +# 13. Lambda vs Kappa architecture +# ============================================================ +def gen_lambda_vs_kappa(): + fig, axes = plt.subplots(2, 1, figsize=(10, 7)) + fig.suptitle('Architektura Lambda vs Kappa', + fontsize=FS_TITLE, fontweight='bold') + + # --- Lambda --- + ax = axes[0] + ax.set_xlim(0, 12) + ax.set_ylim(0, 5) + ax.set_aspect('auto') + ax.axis('off') + ax.set_title('LAMBDA — 2 ścieżki (batch + speed)', + fontsize=FS_LABEL, fontweight='bold') + + # Source + draw_box(ax, 0.3, 1.8, 2.0, 1.5, 'Źródło\ndanych', fill=GRAY2, + fontsize=FS, fontweight='bold') + + # Batch layer (top) + draw_box(ax, 3.5, 3.3, 3.0, 1.2, 'Batch Layer\n(Spark)\nprzelicza co godzinę', + fill=GRAY1, fontsize=FS_SMALL, fontweight='bold') + draw_arrow(ax, 2.3, 3.0, 3.5, 3.9, lw=1.5) + + # Speed layer (bottom) + draw_box(ax, 3.5, 0.8, 3.0, 1.2, 'Speed Layer\n(Flink)\nreal-time', + fill=GRAY3, fontsize=FS_SMALL, fontweight='bold') + draw_arrow(ax, 2.3, 2.2, 3.5, 1.4, lw=1.5) + + # Results + draw_box(ax, 7.5, 3.3, 2.0, 1.2, 'Dokładne\nwyniki\n(wolne)', + fill=GRAY4, fontsize=FS_SMALL) + draw_arrow(ax, 6.5, 3.9, 7.5, 3.9, lw=1.5) + + draw_box(ax, 7.5, 0.8, 2.0, 1.2, 'Przybliżone\nwyniki\n(szybkie)', + fill=GRAY4, fontsize=FS_SMALL) + draw_arrow(ax, 6.5, 1.4, 7.5, 1.4, lw=1.5) + + # Merge + draw_box(ax, 10.0, 2.0, 1.5, 1.5, 'MERGE\n→ UI', fill=GRAY5, + fontsize=FS, fontweight='bold') + draw_arrow(ax, 9.5, 3.5, 10.0, 3.0, lw=1.5) + draw_arrow(ax, 9.5, 1.8, 10.0, 2.5, lw=1.5) + + ax.text(6.0, 0.1, '2 systemy, 2 kody — złożone ale pewne', + fontsize=FS, ha='center', style='italic', + bbox=dict(boxstyle='round,pad=0.2', facecolor=GRAY4, edgecolor=GRAY5)) + + # --- Kappa --- + ax = axes[1] + ax.set_xlim(0, 12) + ax.set_ylim(0, 4) + ax.set_aspect('auto') + ax.axis('off') + ax.set_title('KAPPA — 1 ścieżka (streaming only)', + fontsize=FS_LABEL, fontweight='bold') + + # Source + draw_box(ax, 0.3, 1.3, 2.0, 1.5, 'Źródło\ndanych', fill=GRAY2, + fontsize=FS, fontweight='bold') + + # Single streaming layer + draw_box(ax, 3.5, 1.3, 3.5, 1.5, 'Streaming Layer\n(Flink)\n+ replay z Kafka log', + fill=GRAY1, fontsize=FS, fontweight='bold') + draw_arrow(ax, 2.3, 2.05, 3.5, 2.05, lw=2) + + # Output + draw_box(ax, 8.0, 1.3, 2.5, 1.5, 'Wyniki\n→ UI', fill=GRAY4, + fontsize=FS, fontweight='bold') + draw_arrow(ax, 7.0, 2.05, 8.0, 2.05, lw=2) + + # Replay arrow + ax.annotate('', xy=(3.5, 1.0), xytext=(7.0, 1.0), + arrowprops=dict(arrowstyle='<-', lw=1.5, color=LN, + connectionstyle='arc3,rad=0.3', linestyle='--')) + ax.text(5.25, 0.3, 'Replay z Kafka\n(przetwórz historię od nowa)', + fontsize=FS_SMALL, ha='center', style='italic') + + ax.text(6.0, 3.3, '1 system, 1 kod — prostsze, ale replay = dużo I/O', + fontsize=FS, ha='center', style='italic', + bbox=dict(boxstyle='round,pad=0.2', facecolor=GRAY4, edgecolor=GRAY5)) + + fig.tight_layout(rect=[0, 0, 1, 0.92]) + save_fig(fig, 'q20_lambda_vs_kappa.png') + + +# ============================================================ +# 14. Lambda vs Kappa comparison table +# ============================================================ +def gen_lambda_kappa_table(): + fig, ax = plt.subplots(figsize=(8, 3.5)) + ax.set_xlim(0, 10) + ax.set_ylim(-4.5, 1) + ax.set_aspect('auto') + ax.axis('off') + ax.set_title('Lambda vs Kappa — porównanie', + fontsize=FS_TITLE, fontweight='bold', pad=10) + + headers = ['Cecha', 'Lambda', 'Kappa'] + col_w = [2.5, 3.5, 3.5] + rows = [ + ['Ścieżki', '2 (batch + speed)', '1 (streaming)'], + ['Kod', '2 implementacje', '1 implementacja'], + ['Złożoność', 'wysoka', 'niska'], + ['Replay', 'batch przelicza', 'Kafka replay'], + ['Spójność', 'merge wymagany', 'natywna'], + ['Przykład', 'Netflix, LinkedIn', 'Uber, Confluent'], + ] + draw_table(ax, headers, rows, x0=0.25, y0=0.5, col_widths=col_w, + row_h=0.55, fontsize=7.5) + + save_fig(fig, 'q20_lambda_kappa_table.png') + + +# ============================================================ +# 15. Exactly-once comparison +# ============================================================ +def gen_exactly_once(): + fig, ax = plt.subplots(figsize=(9, 5)) + ax.set_xlim(0, 12) + ax.set_ylim(0, 7) + ax.set_aspect('auto') + ax.axis('off') + ax.set_title('Exactly-Once — mechanizmy na 3 platformach', + fontsize=FS_TITLE, fontweight='bold', pad=10) + + # Flink + draw_box(ax, 0.3, 4.3, 11.0, 2.0, '', fill=GRAY4, rounded=True, lw=1.5) + ax.text(1.0, 5.9, 'Flink — Distributed Snapshots (Chandy-Lamport)', + fontsize=FS, fontweight='bold') + + flink_steps = ['source', '|B|', 'map()', '|B|', 'sink()'] + bx = 1.0 + for s in flink_steps: + if s == '|B|': + ax.text(bx + 0.25, 4.85, s, fontsize=FS, ha='center', + fontweight='bold', + bbox=dict(boxstyle='round,pad=0.1', facecolor=GRAY5, + edgecolor=LN)) + draw_arrow(ax, bx - 0.1, 4.85, bx + 0.05, 4.85, lw=1) + bx += 0.7 + else: + draw_box(ax, bx, 4.6, 1.5, 0.55, s, fill=GRAY1, + fontsize=FS_SMALL, fontweight='bold') + bx += 1.8 + ax.text(8.5, 5.0, 'barrier → save state\n→ checkpoint (HDFS/S3)', + fontsize=FS_SMALL, style='italic') + + # Kafka Streams + draw_box(ax, 0.3, 2.3, 11.0, 1.5, '', fill=GRAY1, rounded=True, lw=1.5) + ax.text(1.0, 3.5, 'Kafka Streams — Transakcje Kafka', + fontsize=FS, fontweight='bold') + ax.text(1.5, 2.85, 'idempotent producer + begin TX → produce → commit TX → ' + 'consumer offsets w TX', fontsize=FS_SMALL) + + # Spark + draw_box(ax, 0.3, 0.5, 11.0, 1.5, '', fill=GRAY3, rounded=True, lw=1.5) + ax.text(1.0, 1.7, 'Spark Streaming — Write-Ahead Log (WAL)', + fontsize=FS, fontweight='bold') + ax.text(1.5, 1.05, 'WAL + checkpointing micro-batchów + idempotent sinks ' + '(np. upsert do DB)', fontsize=FS_SMALL) + + save_fig(fig, 'q20_exactly_once.png') + + +# ============================================================ +# 16. Late data strategies (DRAS) +# ============================================================ +def gen_late_data_strategies(): + fig, ax = plt.subplots(figsize=(9, 5.5)) + ax.set_xlim(0, 12) + ax.set_ylim(0, 7) + ax.set_aspect('auto') + ax.axis('off') + ax.set_title('Late Data — 4 strategie (mnemonik DRAS)', + fontsize=FS_TITLE, fontweight='bold', pad=10) + + # Setup: window closed, late event arrives + draw_box(ax, 0.5, 5.5, 4.5, 1.0, 'Okno [14:00–14:05]\nZAMKNIĘTE o 14:05', + fill=GRAY2, fontsize=FS, fontweight='bold') + draw_box(ax, 6.0, 5.5, 4.5, 1.0, 'Spóźnione zdarzenie\nevent_time=14:00:03\narrives=14:05:30', + fill='#F8D7DA', fontsize=FS_SMALL, fontweight='bold') + draw_arrow(ax, 10.5, 6.0, 5.0, 6.0, lw=2, color='#C62828', style='->') + ax.text(7.5, 5.2, 'LATE!', fontsize=FS_LABEL, ha='center', + fontweight='bold', color='#C62828') + + # 4 strategies + strategies = [ + ('D — Drop', 'Odrzuć spóźnione', '/dev/null', GRAY4), + ('R — Recompute', 'Przelicz okno ponownie', 'poprawne ale kosztowne', GRAY1), + ('A — Allowed lateness', 'Czekaj dodatkowy czas\n(np. +2 min)', 'kompromis pamięci', GRAY2), + ('S — Side output', 'Przekieruj do osobnej\nkolejki', 'elastyczne, ręczna analiza', GRAY3), + ] + for i, (name, desc, tradeoff, color) in enumerate(strategies): + y = 3.8 - i * 1.1 + draw_box(ax, 0.5, y, 2.5, 0.9, name, fill=color, fontsize=FS, + fontweight='bold') + ax.text(3.3, y + 0.45, desc, fontsize=FS_SMALL, va='center') + ax.text(8.5, y + 0.45, tradeoff, fontsize=FS_SMALL, va='center', + style='italic', color='#555') + + save_fig(fig, 'q20_late_data_strategies.png') + + +# ============================================================ +# 17. Decision tree — which platform +# ============================================================ +def gen_decision_tree(): + fig, ax = plt.subplots(figsize=(10, 5.5)) + ax.set_xlim(0, 12) + ax.set_ylim(0, 7) + ax.set_aspect('auto') + ax.axis('off') + ax.set_title('Drzewo decyzyjne — wybór platformy', + fontsize=FS_TITLE, fontweight='bold', pad=10) + + # Root question + draw_box(ax, 3.5, 5.5, 4.5, 1.0, 'Latencja < 10ms\nwymagana?', + fill=GRAY2, fontsize=FS, fontweight='bold') + + # TAK branch + draw_arrow(ax, 3.5, 5.7, 2.0, 5.0, lw=1.5) + ax.text(2.3, 5.3, 'TAK', fontsize=FS, fontweight='bold') + + draw_box(ax, 0.3, 3.5, 3.5, 1.0, 'Dane już w Kafce?\nProste transformacje?', + fill=GRAY1, fontsize=FS, fontweight='bold') + + # TAK → Kafka Streams + draw_arrow(ax, 0.3, 3.7, -0.1, 3.0, lw=1.5) + ax.text(0.0, 3.3, 'TAK', fontsize=FS_SMALL, fontweight='bold') + draw_box(ax, -0.3, 1.8, 2.5, 1.0, 'Kafka\nStreams', fill=GRAY5, + fontsize=FS_LABEL, fontweight='bold') + + # NIE → Flink + draw_arrow(ax, 3.8, 3.7, 4.5, 3.0, lw=1.5) + ax.text(4.0, 3.3, 'NIE\n(złożona logika)', fontsize=FS_SMALL) + draw_box(ax, 3.0, 1.8, 2.5, 1.0, 'Apache\nFlink', fill=GRAY5, + fontsize=FS_LABEL, fontweight='bold') + + # NIE branch + draw_arrow(ax, 8.0, 5.7, 9.5, 5.0, lw=1.5) + ax.text(8.7, 5.3, 'NIE', fontsize=FS, fontweight='bold') + + draw_box(ax, 7.5, 3.5, 4.2, 1.0, '~100ms–1s OK?\nPotrzeba ML / SQL?', + fill=GRAY1, fontsize=FS, fontweight='bold') + + # TAK + ML → Spark + draw_arrow(ax, 9.5, 3.5, 9.5, 3.0, lw=1.5) + ax.text(10.0, 3.3, 'TAK + ML/SQL', fontsize=FS_SMALL) + draw_box(ax, 8.0, 1.8, 2.5, 1.0, 'Spark\nStreaming', fill=GRAY5, + fontsize=FS_LABEL, fontweight='bold') + + # TAK + proste → Kafka Streams too + draw_arrow(ax, 7.5, 3.7, 6.5, 3.0, lw=1.5) + ax.text(6.3, 3.3, 'proste + TAK', fontsize=FS_SMALL) + draw_box(ax, 5.8, 1.8, 2.0, 1.0, 'Kafka\nStreams', fill=GRAY5, + fontsize=FS, fontweight='bold') + + # Legend + ax.text(6.0, 0.7, 'Reguła: Kafka Streams = najprostsze (library) | ' + 'Flink = najpotężniejszy (true streaming) | Spark = ekosystem ML', + fontsize=FS, ha='center', + bbox=dict(boxstyle='round,pad=0.2', facecolor=GRAY4, edgecolor=GRAY5)) + + save_fig(fig, 'q20_decision_tree.png') + + +# ============================================================ +# MAIN +# ============================================================ +if __name__ == '__main__': + print("Generating ALL PYTANIE 20 diagrams...") + gen_batch_vs_streaming() + gen_window_types() + gen_event_vs_processing_time() + gen_tumbling_fraud() + gen_sliding_sla() + gen_session_users() + gen_streaming_ecosystem() + gen_true_vs_microbatch() + gen_platform_comparison() + gen_kafka_streams_arch() + gen_flink_arch() + gen_spark_streaming_arch() + gen_lambda_vs_kappa() + gen_lambda_kappa_table() + gen_exactly_once() + gen_late_data_strategies() + gen_decision_tree() + print("\nAll 17 PYTANIE 20 diagrams generated successfully!") diff --git a/pytania/generate_q9_all_diagrams.py b/pytania/generate_q9_all_diagrams.py new file mode 100644 index 0000000..e1b617b --- /dev/null +++ b/pytania/generate_q9_all_diagrams.py @@ -0,0 +1,1009 @@ +#!/usr/bin/env python3 +""" +Generate ALL diagrams for PYTANIE 9: Procesy i wątki (SOI). +Replaces every ASCII diagram with a monochrome A4-printable PNG (300 DPI). +""" + +import matplotlib +matplotlib.use('Agg') +import matplotlib.pyplot as plt +import matplotlib.patches as mpatches +from matplotlib.patches import FancyBboxPatch +import numpy as np +import os + +DPI = 300 +BG = 'white' +LN = 'black' +FS = 8 +FS_TITLE = 11 +FS_SMALL = 6.5 +FS_LABEL = 9 +OUTPUT_DIR = os.path.join(os.path.dirname(os.path.abspath(__file__)), 'img') +os.makedirs(OUTPUT_DIR, exist_ok=True) + +GRAY1 = '#E8E8E8' +GRAY2 = '#D0D0D0' +GRAY3 = '#B8B8B8' +GRAY4 = '#F5F5F5' +GRAY5 = '#C0C0C0' + + +def draw_box(ax, x, y, w, h, text, fill='white', lw=1.2, fontsize=FS, + fontweight='normal', ha='center', va='center', rounded=True, + edgecolor=LN, linestyle='-'): + if rounded: + rect = FancyBboxPatch((x, y), w, h, boxstyle="round,pad=0.05", + lw=lw, edgecolor=edgecolor, facecolor=fill, + linestyle=linestyle) + else: + rect = mpatches.Rectangle((x, y), w, h, lw=lw, edgecolor=edgecolor, + facecolor=fill, linestyle=linestyle) + ax.add_patch(rect) + ax.text(x + w/2, y + h/2, text, ha=ha, va=va, fontsize=fontsize, + fontweight=fontweight, wrap=True) + + +def draw_arrow(ax, x1, y1, x2, y2, lw=1.2, style='->', color=LN): + ax.annotate("", xy=(x2, y2), xytext=(x1, y1), + arrowprops=dict(arrowstyle=style, color=color, lw=lw)) + + +def draw_double_arrow(ax, x1, y1, x2, y2, lw=1.2, color=LN): + ax.annotate("", xy=(x2, y2), xytext=(x1, y1), + arrowprops=dict(arrowstyle='<->', color=color, lw=lw)) + + +def save_fig(fig, name): + path = os.path.join(OUTPUT_DIR, name) + fig.savefig(path, dpi=DPI, bbox_inches='tight', facecolor=BG, pad_inches=0.15) + plt.close(fig) + print(f" Saved: {path}") + + +def draw_table(ax, headers, rows, x0, y0, col_widths, row_h=0.4, + header_fill=GRAY2, row_fills=None, fontsize=FS, header_fontsize=None): + """Draw a clean table on axes.""" + if header_fontsize is None: + header_fontsize = fontsize + n_cols = len(headers) + n_rows = len(rows) + total_w = sum(col_widths) + + # Header + cx = x0 + for j, hdr in enumerate(headers): + draw_box(ax, cx, y0, col_widths[j], row_h, hdr, fill=header_fill, + fontsize=header_fontsize, fontweight='bold', rounded=False) + cx += col_widths[j] + + # Rows + for i, row in enumerate(rows): + cy = y0 - (i + 1) * row_h + cx = x0 + fill = GRAY4 if (i % 2 == 0) else 'white' + if row_fills and i < len(row_fills): + fill = row_fills[i] + for j, cell in enumerate(row): + fw = 'bold' if j == 0 else 'normal' + draw_box(ax, cx, cy, col_widths[j], row_h, cell, fill=fill, + fontsize=fontsize, fontweight=fw, rounded=False) + cx += col_widths[j] + + +# ============================================================ +# 1. Process vs Thread comparison table +# ============================================================ +def gen_process_vs_thread(): + fig, ax = plt.subplots(figsize=(7.5, 4.5)) + ax.set_xlim(0, 10) + ax.set_ylim(-4.5, 1.5) + ax.set_aspect('auto') + ax.axis('off') + ax.set_title('Proces vs Wątek — porównanie', fontsize=FS_TITLE, fontweight='bold', pad=10) + + headers = ['Cecha', 'Proces', 'Wątek'] + col_w = [2.5, 3.5, 3.5] + rows = [ + ['Pamięć', 'Własna, izolowana', 'Współdzielona (heap)'], + ['Tworzenie', '~1–10 ms', '~10–100 μs (100× szybciej)'], + ['Przełączanie', '~1–5 μs (TLB flush)', '~0.1–0.5 μs (10×)'], + ['Komunikacja', 'IPC (pipe, socket, shm)', 'Bezpośrednia (wspólna pam.)'], + ['Izolacja', 'Pełna — awaria izolowana', 'Brak — może zabić proces'], + ['Zastosowanie', 'Bezpieczeństwo, izolacja', 'Wydajność, współdzielenie'], + ] + draw_table(ax, headers, rows, x0=0.25, y0=0.8, col_widths=col_w, row_h=0.55, + fontsize=7.5, header_fontsize=FS_LABEL) + + # Analogy at bottom + ax.text(5.0, -4.2, 'Analogia: Proces = mieszkanie (własny adres) ' + 'Wątek = pokój w mieszkaniu (wspólna kuchnia = heap)', + ha='center', fontsize=FS, style='italic', + bbox=dict(boxstyle='round,pad=0.3', facecolor=GRAY4, edgecolor=GRAY3)) + + save_fig(fig, 'q9_process_vs_thread.png') + + +# ============================================================ +# 2. Memory segments layout +# ============================================================ +def gen_memory_layout(): + fig, ax = plt.subplots(figsize=(6, 5)) + ax.set_xlim(0, 10) + ax.set_ylim(0, 8) + ax.set_aspect('auto') + ax.axis('off') + ax.set_title('Segmenty pamięci procesu', fontsize=FS_TITLE, fontweight='bold', pad=10) + + segments = [ + ('STACK ↓', 'zmienne lokalne, adresy\npowrotu (każdy wątek WŁASNY)', GRAY1), + ('...', '(wolna przestrzeń)', 'white'), + ('HEAP ↑', 'malloc/new — dynamiczna\nalokacja (współdzielony)', GRAY4), + ('BSS', 'zmienne globalne\nniezainicjalizowane (zerowane)', GRAY2), + ('DATA', 'zmienne globalne\nzainicjalizowane', GRAY3), + ('TEXT', 'kod maszynowy\n(read-only, współdzielony)', GRAY5), + ] + bx, bw = 2.0, 2.5 + seg_h = 0.9 + gap = 0.05 + top_y = 7.0 + + for i, (name, desc, color) in enumerate(segments): + y = top_y - i * (seg_h + gap) + draw_box(ax, bx, y, bw, seg_h, name, fill=color, fontsize=FS_LABEL, + fontweight='bold', rounded=False) + ax.text(bx + bw + 0.3, y + seg_h / 2, desc, fontsize=7.5, va='center') + + # Address labels + ax.text(bx - 0.2, top_y + seg_h / 2, 'wysoki\nadres', fontsize=FS_SMALL, + va='center', ha='right', style='italic') + bottom_y = top_y - 5 * (seg_h + gap) + ax.text(bx - 0.2, bottom_y + seg_h / 2, 'niski\nadres', fontsize=FS_SMALL, + va='center', ha='right', style='italic') + + # Arrows for growth + ax.annotate('', xy=(bx - 0.5, top_y - 0.1), xytext=(bx - 0.5, top_y + seg_h + 0.1), + arrowprops=dict(arrowstyle='->', lw=1.5, color=LN)) + ax.text(bx - 0.9, top_y + 0.4, 'rośnie\nw dół', fontsize=FS_SMALL, ha='center') + + heap_y = top_y - 2 * (seg_h + gap) + ax.annotate('', xy=(bx - 0.5, heap_y + seg_h + 0.1), xytext=(bx - 0.5, heap_y - 0.1), + arrowprops=dict(arrowstyle='->', lw=1.5, color=LN)) + ax.text(bx - 0.9, heap_y + 0.5, 'rośnie\nw górę', fontsize=FS_SMALL, ha='center') + + save_fig(fig, 'q9_memory_layout.png') + + +# ============================================================ +# 3. Process states diagram +# ============================================================ +def gen_process_states(): + fig, ax = plt.subplots(figsize=(7, 3.5)) + ax.set_xlim(0, 12) + ax.set_ylim(0, 5) + ax.set_aspect('auto') + ax.axis('off') + ax.set_title('Stany procesu — diagram przejść', fontsize=FS_TITLE, + fontweight='bold', pad=10) + + states = { + 'NEW': (1.0, 2.5), + 'READY': (3.5, 2.5), + 'RUNNING': (6.5, 2.5), + 'BLOCKED': (6.5, 0.5), + 'TERMINATED': (10.0, 2.5), + } + fills = { + 'NEW': GRAY4, 'READY': GRAY1, 'RUNNING': GRAY3, + 'BLOCKED': GRAY2, 'TERMINATED': GRAY5, + } + bw, bh = 1.8, 0.9 + for name, (x, y) in states.items(): + draw_box(ax, x, y, bw, bh, name, fill=fills[name], fontsize=FS_LABEL, + fontweight='bold') + + # Transitions + transitions = [ + ('NEW', 'READY', 'admit'), + ('READY', 'RUNNING', 'dispatch\n(scheduler)'), + ('RUNNING', 'TERMINATED', 'exit'), + ('RUNNING', 'BLOCKED', 'I/O wait'), + ] + for src, dst, label in transitions: + sx, sy = states[src] + dx, dy = states[dst] + if sy == dy: # horizontal + draw_arrow(ax, sx + bw, sy + bh/2, dx, dy + bh/2, lw=1.5) + mx = (sx + bw + dx) / 2 + ax.text(mx, sy + bh/2 + 0.25, label, fontsize=FS_SMALL, + ha='center', va='bottom') + else: # vertical + draw_arrow(ax, sx + bw/2, sy, dx + bw/2, dy + bh, lw=1.5) + ax.text(sx + bw + 0.2, (sy + dy + bh) / 2, label, + fontsize=FS_SMALL, ha='left', va='center') + + # BLOCKED → READY + bx, by = states['BLOCKED'] + rx, ry = states['READY'] + ax.annotate("", xy=(rx + bw/2, ry), xytext=(bx - 0.3, by + bh/2), + arrowprops=dict(arrowstyle='->', lw=1.5, color=LN, + connectionstyle='arc3,rad=0.3')) + ax.text(3.5, 0.7, 'I/O done', fontsize=FS_SMALL, ha='center') + + # RUNNING → READY (preemption) + rux, ruy = states['RUNNING'] + draw_arrow(ax, rux, ruy + bh, rx + bw, ry + bh, lw=1.2) + ax.text(5.0, 3.7, 'preempt /\ntimeout', fontsize=FS_SMALL, ha='center') + + save_fig(fig, 'q9_process_states.png') + + +# ============================================================ +# 4. Thread structure within process +# ============================================================ +def gen_thread_structure(): + fig, ax = plt.subplots(figsize=(8, 4.5)) + ax.set_xlim(0, 10) + ax.set_ylim(0, 6) + ax.set_aspect('auto') + ax.axis('off') + ax.set_title('Wątki wewnątrz procesu (PID=42)', fontsize=FS_TITLE, + fontweight='bold', pad=10) + + # Shared memory region + draw_box(ax, 0.5, 3.5, 9.0, 1.8, '', fill=GRAY1, rounded=False, lw=2) + ax.text(5.0, 5.0, 'WSPÓŁDZIELONE', fontsize=FS, fontweight='bold', ha='center') + + labels_shared = ['TEXT', 'DATA', 'BSS', 'HEAP', 'pliki', 'PID'] + for i, lab in enumerate(labels_shared): + x = 1.0 + i * 1.4 + draw_box(ax, x, 3.8, 1.1, 0.6, lab, fill=GRAY3, fontsize=FS, + fontweight='bold', rounded=False) + ax.text(x + 0.55, 4.6, lab, fontsize=FS_SMALL, ha='center', color='#555555') + + # Per-thread regions + draw_box(ax, 0.5, 0.5, 9.0, 2.7, '', fill='white', rounded=False, lw=2, + linestyle='--') + ax.text(5.0, 2.95, 'PRYWATNE (każdy wątek)', fontsize=FS, fontweight='bold', + ha='center') + + for i in range(3): + x = 1.0 + i * 3.0 + tid = i + 1 + draw_box(ax, x, 0.7, 2.3, 2.0, '', fill=GRAY4, rounded=False) + ax.text(x + 1.15, 2.4, f'Wątek {tid}', fontsize=FS_LABEL, + fontweight='bold', ha='center') + items = [f'stos_{tid}', f'rejestry_{tid}', f'PC_{tid}', f'TID={40+tid}'] + for j, item in enumerate(items): + ax.text(x + 1.15, 2.0 - j * 0.35, item, fontsize=FS_SMALL, + ha='center', family='monospace') + + save_fig(fig, 'q9_thread_structure.png') + + +# ============================================================ +# 5. PCB structure +# ============================================================ +def gen_pcb_structure(): + fig, ax = plt.subplots(figsize=(5, 3.5)) + ax.set_xlim(0, 8) + ax.set_ylim(0, 5.5) + ax.set_aspect('auto') + ax.axis('off') + ax.set_title('PCB (Process Control Block)', fontsize=FS_TITLE, + fontweight='bold', pad=10) + + fields = [ + ('PID', '42'), + ('Stan', 'READY / RUNNING / BLOCKED'), + ('Rejestry CPU', 'EAX, EBX, ESP, EIP ...'), + ('Tablice stron', 'mapowanie wirtualne → fizyczne'), + ('Otwarte pliki', 'fd[0], fd[1], fd[2] ...'), + ('Priorytety', 'nice value, scheduling class'), + ('Statystyki', 'CPU time, I/O count'), + ] + + top_y = 4.8 + for i, (field, value) in enumerate(fields): + y = top_y - i * 0.55 + draw_box(ax, 0.5, y, 2.2, 0.45, field, fill=GRAY2, fontsize=FS, + fontweight='bold', rounded=False) + draw_box(ax, 2.7, y, 4.5, 0.45, value, fill=GRAY4, fontsize=FS, + rounded=False) + + ax.text(4.0, 0.3, 'Context switch = zapisz PCB starego → wczytaj PCB nowego', + fontsize=FS_SMALL, ha='center', style='italic') + + save_fig(fig, 'q9_pcb_structure.png') + + +# ============================================================ +# 6. Speed comparison +# ============================================================ +def gen_speed_comparison(): + fig, axes = plt.subplots(1, 2, figsize=(9, 3.5)) + fig.suptitle('Szybkość — procesy vs wątki (benchmarki Linux)', + fontsize=FS_TITLE, fontweight='bold') + + # Left: creation + ax = axes[0] + ops = ['fork()\n(nowy proces)', 'pthread_create()\n(nowy wątek)'] + times = [3.0, 0.05] # ms + colors = [GRAY3, GRAY1] + bars = ax.barh(ops, times, color=colors, edgecolor=LN, height=0.5, linewidth=1.2) + ax.set_xlabel('Czas [ms]', fontsize=FS) + ax.set_title('Tworzenie', fontsize=FS_LABEL, fontweight='bold') + ax.set_xlim(0, 4.5) + for bar, t in zip(bars, times): + ax.text(bar.get_width() + 0.1, bar.get_y() + bar.get_height()/2, + f'{t} ms', va='center', fontsize=FS) + ax.text(2.5, -0.6, '~100× szybciej', fontsize=FS, ha='center', + fontweight='bold', transform=ax.get_xaxis_transform()) + ax.tick_params(labelsize=FS) + + # Right: context switch + ax = axes[1] + ops2 = ['Proces→Proces\n(TLB flush)', 'Wątek→Wątek\n(TLB warm)'] + times2 = [3000, 300] # ns + bars2 = ax.barh(ops2, times2, color=colors, edgecolor=LN, height=0.5, linewidth=1.2) + ax.set_xlabel('Czas [ns]', fontsize=FS) + ax.set_title('Przełączanie kontekstu', fontsize=FS_LABEL, fontweight='bold') + ax.set_xlim(0, 4500) + for bar, t in zip(bars2, times2): + ax.text(bar.get_width() + 50, bar.get_y() + bar.get_height()/2, + f'{t} ns', va='center', fontsize=FS) + ax.text(2500, -0.6, '~10× szybciej', fontsize=FS, ha='center', + fontweight='bold', transform=ax.get_xaxis_transform()) + ax.tick_params(labelsize=FS) + + fig.tight_layout(rect=[0, 0.05, 1, 0.92]) + save_fig(fig, 'q9_speed_comparison.png') + + +# ============================================================ +# 7. Scenario table (when to use process vs thread) +# ============================================================ +def gen_scenario_table(): + fig, ax = plt.subplots(figsize=(8.5, 4.5)) + ax.set_xlim(0, 11) + ax.set_ylim(-5.5, 1) + ax.set_aspect('auto') + ax.axis('off') + ax.set_title('Kiedy proces, kiedy wątek? — typowe scenariusze', + fontsize=FS_TITLE, fontweight='bold', pad=10) + + headers = ['Scenariusz', 'Wybór', 'Dlaczego?'] + col_w = [3.5, 2.5, 4.5] + rows = [ + ['Serwer WWW (Apache)', 'Proces', 'izolacja klientów'], + ['Serwer WWW (nginx)', 'Wątek / async', 'szybkość, cooperacja'], + ['Przeglądarka (karty)', 'Proces', 'crash isolation'], + ['Przeglądarka (JS+render)', 'Wątek', 'współdzielony DOM'], + ['Gra (fizyka+rendering)', 'Wątek', 'współdzielony świat gry'], + ['Kompilacja (make -j8)', 'Proces', 'izolacja, prostota'], + ['Baza danych (zapytania)', 'Wątek', 'współdzielony cache'], + ['Microservices', 'Proces (kontener)', 'izolacja, deployment'], + ] + draw_table(ax, headers, rows, x0=0.25, y0=0.5, col_widths=col_w, + row_h=0.5, fontsize=7) + + save_fig(fig, 'q9_scenario_table.png') + + +# ============================================================ +# 8. IPC details: pipe, shared memory, socket (3-panel) +# ============================================================ +def gen_ipc_details(): + fig, axes = plt.subplots(1, 3, figsize=(11, 3.5)) + fig.suptitle('Mechanizmy IPC — szczegóły', fontsize=FS_TITLE, fontweight='bold') + + # Panel 1: Pipe + ax = axes[0] + ax.set_xlim(0, 8) + ax.set_ylim(0, 5) + ax.set_aspect('auto') + ax.axis('off') + ax.set_title('Pipe (potok)', fontsize=FS_LABEL, fontweight='bold') + + draw_box(ax, 0.2, 2.0, 1.8, 1.2, 'Proces A\n(ls)\nstdout', fill=GRAY1, + fontsize=FS) + draw_box(ax, 3.0, 2.0, 1.8, 1.2, 'Bufor\njądra\n(4 KB)', fill=GRAY2, + fontsize=FS, fontweight='bold') + draw_box(ax, 5.8, 2.0, 1.8, 1.2, 'Proces B\n(grep)\nstdin', fill=GRAY1, + fontsize=FS) + draw_arrow(ax, 2.0, 2.6, 3.0, 2.6, lw=1.5) + ax.text(2.5, 3.0, 'write()\nfd[1]', fontsize=FS_SMALL, ha='center') + draw_arrow(ax, 4.8, 2.6, 5.8, 2.6, lw=1.5) + ax.text(5.3, 3.0, 'read()\nfd[0]', fontsize=FS_SMALL, ha='center') + ax.text(4.0, 0.8, 'Jednokierunkowy\nBufor pełny → write() blokuje', + fontsize=FS_SMALL, ha='center', style='italic', + bbox=dict(boxstyle='round,pad=0.2', facecolor=GRAY4, edgecolor=GRAY3)) + + # Panel 2: Shared Memory + ax = axes[1] + ax.set_xlim(0, 8) + ax.set_ylim(0, 5) + ax.set_aspect('auto') + ax.axis('off') + ax.set_title('Shared Memory', fontsize=FS_LABEL, fontweight='bold') + + draw_box(ax, 0.3, 3.0, 2.2, 1.2, 'Proces A\nstrona 7', fill=GRAY1, fontsize=FS) + draw_box(ax, 5.5, 3.0, 2.2, 1.2, 'Proces B\nstrona 3', fill=GRAY1, fontsize=FS) + draw_box(ax, 2.8, 1.0, 2.4, 1.2, 'RAM\nramka 42', fill=GRAY3, fontsize=FS, + fontweight='bold') + draw_arrow(ax, 2.0, 3.0, 3.5, 2.2, lw=1.5) + draw_arrow(ax, 6.0, 3.0, 4.5, 2.2, lw=1.5) + ax.text(4.0, 0.3, 'Zero kopiowania!\nA pisze → B widzi od razu\n' + 'Wymaga synchronizacji (semafor)', + fontsize=FS_SMALL, ha='center', style='italic', + bbox=dict(boxstyle='round,pad=0.2', facecolor=GRAY4, edgecolor=GRAY3)) + + # Panel 3: Socket + ax = axes[2] + ax.set_xlim(0, 8) + ax.set_ylim(0, 5) + ax.set_aspect('auto') + ax.axis('off') + ax.set_title('Socket', fontsize=FS_LABEL, fontweight='bold') + + # Network socket + draw_box(ax, 0.3, 3.2, 1.8, 0.9, 'Klient', fill=GRAY1, fontsize=FS, fontweight='bold') + draw_box(ax, 5.5, 3.2, 1.8, 0.9, 'Serwer', fill=GRAY1, fontsize=FS, fontweight='bold') + draw_double_arrow(ax, 2.1, 3.65, 5.5, 3.65, lw=1.5) + ax.text(3.8, 4.3, 'TCP/IP (sieciowy)', fontsize=FS, ha='center', fontweight='bold') + + # Unix socket + draw_box(ax, 0.3, 1.3, 1.8, 0.9, 'Proces A', fill=GRAY4, fontsize=FS, fontweight='bold') + draw_box(ax, 5.5, 1.3, 1.8, 0.9, 'Proces B', fill=GRAY4, fontsize=FS, fontweight='bold') + draw_double_arrow(ax, 2.1, 1.75, 5.5, 1.75, lw=1.5) + ax.text(3.8, 2.4, 'Unix domain socket\n(/tmp/app.sock)', fontsize=FS, + ha='center', fontweight='bold') + + ax.text(3.8, 0.5, 'Dwukierunkowy\nNajbardziej uniwersalny IPC', + fontsize=FS_SMALL, ha='center', style='italic', + bbox=dict(boxstyle='round,pad=0.2', facecolor=GRAY4, edgecolor=GRAY3)) + + fig.tight_layout(rect=[0, 0, 1, 0.9]) + save_fig(fig, 'q9_ipc_details.png') + + +# ============================================================ +# 9. IPC comparison table +# ============================================================ +def gen_ipc_table(): + fig, ax = plt.subplots(figsize=(8.5, 3.5)) + ax.set_xlim(0, 11) + ax.set_ylim(-4.5, 1) + ax.set_aspect('auto') + ax.axis('off') + ax.set_title('Porównanie mechanizmów IPC', fontsize=FS_TITLE, fontweight='bold', pad=10) + + headers = ['Mechanizm', 'Kierunek', 'Szybkość', 'Zastosowanie'] + col_w = [2.5, 2.0, 2.5, 3.5] + rows = [ + ['Pipe', 'jednokierunkowy', 'średnia', 'ls | grep'], + ['Named Pipe', 'jednokierunkowy', 'średnia', 'demon → klient'], + ['Shared Memory', 'dwukierunkowy', 'NAJSZYBSZA', 'video, bazy danych'], + ['Message Queue', 'dwukierunkowy', 'średnia', 'wieloproducentowe'], + ['Socket', 'dwukierunkowy', 'wolna (sieć)', 'klient-serwer'], + ['Signal', 'jednokierunkowy', 'natychmiastowa', 'powiadomienia (nr)'], + ] + draw_table(ax, headers, rows, x0=0.25, y0=0.5, col_widths=col_w, + row_h=0.5, fontsize=7.5) + + save_fig(fig, 'q9_ipc_table.png') + + +# ============================================================ +# 10. Race condition (simple x + bank timeline) +# ============================================================ +def gen_race_condition(): + fig, axes = plt.subplots(1, 2, figsize=(11, 5)) + fig.suptitle('Wyścig (Race Condition) — przykłady', fontsize=FS_TITLE, + fontweight='bold') + + # Panel 1: simple x increment + ax = axes[0] + ax.set_xlim(0, 8) + ax.set_ylim(0, 7) + ax.set_aspect('auto') + ax.axis('off') + ax.set_title('Prosty wyścig: x = x + 1', fontsize=FS_LABEL, fontweight='bold') + + # Timeline + steps_a = ['czytaj x (=0)', 'dodaj 1', 'zapisz x (=1)'] + steps_b = ['czytaj x (=0)', 'dodaj 1', 'zapisz x (=1)'] + ax.text(2.0, 6.3, 'Wątek A', fontsize=FS_LABEL, ha='center', fontweight='bold') + ax.text(6.0, 6.3, 'Wątek B', fontsize=FS_LABEL, ha='center', fontweight='bold') + ax.plot([2, 2], [0.8, 6.0], color=LN, lw=1) + ax.plot([6, 6], [0.8, 6.0], color=LN, lw=1) + + for i, (sa, sb) in enumerate(zip(steps_a, steps_b)): + y = 5.3 - i * 1.2 + draw_box(ax, 0.5, y, 3.0, 0.6, sa, fill=GRAY4, fontsize=FS) + draw_box(ax, 4.5, y - 0.3, 3.0, 0.6, sb, fill=GRAY1, fontsize=FS) + + ax.text(4.0, 0.4, 'Wynik: x = 1 (powinno 2!)', + fontsize=FS, ha='center', fontweight='bold', color='#C62828', + bbox=dict(boxstyle='round', facecolor='#F8D7DA', edgecolor='#C62828')) + + # Panel 2: bank account + ax = axes[1] + ax.set_xlim(0, 10) + ax.set_ylim(0, 7) + ax.set_aspect('auto') + ax.axis('off') + ax.set_title('Konto bankowe: saldo = 1000 zł', fontsize=FS_LABEL, fontweight='bold') + + ax.text(2.5, 6.3, 'Wątek A (+500)', fontsize=FS, ha='center', fontweight='bold') + ax.text(7.5, 6.3, 'Wątek B (−200)', fontsize=FS, ha='center', fontweight='bold') + ax.plot([2.5, 2.5], [0.8, 6.0], color=LN, lw=1) + ax.plot([7.5, 7.5], [0.8, 6.0], color=LN, lw=1) + + events = [ + ('t1', 'czytaj → 1000', '', 5.3), + ('t2', '', 'czytaj → 1000', 4.6), + ('t3', '1000+500=1500', '', 3.9), + ('t4', '', '1000−200=800', 3.2), + ('t5', 'zapisz 1500', '', 2.5), + ('t6', '', 'zapisz 800 ✗', 1.8), + ] + for t, a, b, y in events: + ax.text(0.3, y + 0.15, t, fontsize=FS_SMALL, fontweight='bold', va='center') + if a: + draw_box(ax, 1.0, y, 3.0, 0.45, a, fill=GRAY4, fontsize=FS_SMALL) + if b: + fill = '#F8D7DA' if '✗' in b else GRAY1 + draw_box(ax, 6.0, y, 3.0, 0.45, b, fill=fill, fontsize=FS_SMALL) + + ax.text(5.0, 0.4, 'Wynik: 800 zł (powinno 1300!)', + fontsize=FS, ha='center', fontweight='bold', color='#C62828', + bbox=dict(boxstyle='round', facecolor='#F8D7DA', edgecolor='#C62828')) + + fig.tight_layout(rect=[0, 0, 1, 0.9]) + save_fig(fig, 'q9_race_condition.png') + + +# ============================================================ +# 11. Deadlock scenario + cycle +# ============================================================ +def gen_deadlock_scenario(): + fig, axes = plt.subplots(1, 2, figsize=(11, 4.5)) + fig.suptitle('Zakleszczenie (Deadlock)', fontsize=FS_TITLE, fontweight='bold') + + # Panel 1: timeline + ax = axes[0] + ax.set_xlim(0, 8) + ax.set_ylim(0, 6) + ax.set_aspect('auto') + ax.axis('off') + ax.set_title('Scenariusz z 2 mutexami', fontsize=FS_LABEL, fontweight='bold') + + ax.text(2.5, 5.3, 'Wątek A', fontsize=FS_LABEL, ha='center', fontweight='bold') + ax.text(6.0, 5.3, 'Wątek B', fontsize=FS_LABEL, ha='center', fontweight='bold') + + steps = [ + ('lock(mutex1) OK', '', 'trzyma', False, 4.5), + ('', 'lock(mutex2) OK', 'trzyma', False, 3.7), + ('lock(mutex2) ...WAIT', '', 'CZEKA!', True, 2.9), + ('', 'lock(mutex1) ...WAIT', 'CZEKA!', True, 2.1), + ] + for a_text, b_text, note, is_wait, y in steps: + if a_text: + fill = '#F8D7DA' if is_wait else GRAY4 + draw_box(ax, 0.5, y, 3.3, 0.55, a_text, fill=fill, fontsize=FS_SMALL) + if b_text: + fill = '#F8D7DA' if is_wait else GRAY4 + draw_box(ax, 4.3, y, 3.3, 0.55, b_text, fill=fill, fontsize=FS_SMALL) + + ax.text(4.0, 1.2, 'DEADLOCK!\nŻaden nie odpuści', + fontsize=FS, ha='center', fontweight='bold', color='#C62828', + bbox=dict(boxstyle='round', facecolor='#F8D7DA', edgecolor='#C62828')) + + # Panel 2: cycle diagram + ax = axes[1] + ax.set_xlim(0, 8) + ax.set_ylim(0, 6) + ax.set_aspect('auto') + ax.axis('off') + ax.set_title('Cykl oczekiwania', fontsize=FS_LABEL, fontweight='bold') + + # Thread boxes + draw_box(ax, 0.5, 3.5, 2.2, 1.2, 'Wątek A\ntrzyma Mutex 1', fill=GRAY1, + fontsize=FS, fontweight='bold') + draw_box(ax, 5.3, 3.5, 2.2, 1.2, 'Wątek B\ntrzyma Mutex 2', fill=GRAY1, + fontsize=FS, fontweight='bold') + + # Mutex boxes + draw_box(ax, 0.5, 1.0, 2.2, 1.0, 'Mutex 1', fill=GRAY3, fontsize=FS, + fontweight='bold') + draw_box(ax, 5.3, 1.0, 2.2, 1.0, 'Mutex 2', fill=GRAY3, fontsize=FS, + fontweight='bold') + + # holds arrows (down) + draw_arrow(ax, 1.6, 3.5, 1.6, 2.0, lw=2) + ax.text(0.9, 2.7, 'trzyma', fontsize=FS_SMALL, rotation=90, va='center') + draw_arrow(ax, 6.4, 3.5, 6.4, 2.0, lw=2) + ax.text(7.0, 2.7, 'trzyma', fontsize=FS_SMALL, rotation=90, va='center') + + # waits-for arrows (across, red) + draw_arrow(ax, 2.7, 4.3, 5.3, 4.3, lw=2.5, color='#C62828') + ax.text(4.0, 4.7, 'czeka na Mutex 2', fontsize=FS_SMALL, ha='center', + fontweight='bold', color='#C62828') + draw_arrow(ax, 5.3, 3.7, 2.7, 3.7, lw=2.5, color='#C62828') + ax.text(4.0, 3.1, 'czeka na Mutex 1', fontsize=FS_SMALL, ha='center', + fontweight='bold', color='#C62828') + + fig.tight_layout(rect=[0, 0, 1, 0.9]) + save_fig(fig, 'q9_deadlock_scenario.png') + + +# ============================================================ +# 12. Coffman conditions + prevention strategies +# ============================================================ +def gen_coffman_strategies(): + fig, ax = plt.subplots(figsize=(9, 4)) + ax.set_xlim(0, 11.5) + ax.set_ylim(-3.5, 1) + ax.set_aspect('auto') + ax.axis('off') + ax.set_title('Warunki Coffmana — zapobieganie deadlockowi', + fontsize=FS_TITLE, fontweight='bold', pad=10) + + headers = ['Warunek', 'Opis', 'Jak złamać', 'Przykład'] + col_w = [2.5, 2.5, 3.0, 3.0] + rows = [ + ['1. Mutual Exclusion', 'zasób wyłączny', 'współdzielony zasób', 'Read-write lock'], + ['2. Hold and Wait', 'trzymaj + czekaj', 'bierz WSZYSTKIE naraz', 'lock(m1,m2) atomowo'], + ['3. No Preemption', 'nie zabierzesz siłą', 'timeout / trylock', 'pthread_mutex_trylock()'], + ['4. Circular Wait', 'cykliczne oczekiw.', 'porządek liniowy', 'zawsze m1 przed m2'], + ] + draw_table(ax, headers, rows, x0=0.25, y0=0.5, col_widths=col_w, + row_h=0.6, fontsize=7) + + ax.text(5.75, -3.1, '▸ Najczęstsza strategia: PORZĄDEK LINIOWY — ' + 'numeruj mutexy, zawsze blokuj rosnąco', + fontsize=FS, ha='center', fontweight='bold', + bbox=dict(boxstyle='round,pad=0.2', facecolor=GRAY4, edgecolor=GRAY3)) + + save_fig(fig, 'q9_coffman_strategies.png') + + +# ============================================================ +# 13. Starvation + Priority Inversion (2-panel) +# ============================================================ +def gen_starvation_priority(): + fig, axes = plt.subplots(1, 2, figsize=(11, 4.5)) + fig.suptitle('Zagłodzenie i Inwersja priorytetów', fontsize=FS_TITLE, + fontweight='bold') + + # Panel 1: Starvation + aging + ax = axes[0] + ax.set_xlim(0, 8) + ax.set_ylim(0, 6) + ax.set_aspect('auto') + ax.axis('off') + ax.set_title('Zagłodzenie (Starvation)', fontsize=FS_LABEL, fontweight='bold') + + threads = [ + ('Wątek HIGH', 'prio=10', GRAY5, 3.0), + ('Wątek HIGH', 'prio=9', GRAY3, 2.2), + ('Wątek MED', 'prio=5', GRAY2, 1.4), + ('Wątek LOW', 'prio=1 → głoduje!', '#F8D7DA', 0.6), + ] + for name, prio, color, y in threads: + draw_box(ax, 0.5, y, 2.0, 0.6, name, fill=color, fontsize=FS_SMALL, + fontweight='bold') + ax.text(2.8, y + 0.3, prio, fontsize=FS_SMALL, va='center') + + ax.text(1.5, 4.2, 'CPU zawsze\ndostaje HIGH!', fontsize=FS, + ha='center', fontweight='bold') + draw_arrow(ax, 1.5, 3.9, 1.5, 3.65, lw=1.5) + + # Aging solution + draw_box(ax, 4.5, 1.5, 3.2, 2.5, '', fill=GRAY4, rounded=True) + ax.text(6.1, 3.7, 'Rozwiązanie: AGING', fontsize=FS, fontweight='bold', ha='center') + aging = ['t=0: prio=1', 't=100ms: prio=2', 't=200ms: prio=3', '...', 'w końcu → CPU!'] + for i, line in enumerate(aging): + ax.text(6.1, 3.2 - i * 0.4, line, fontsize=FS_SMALL, ha='center', + family='monospace') + + # Panel 2: Priority Inversion + ax = axes[1] + ax.set_xlim(0, 10) + ax.set_ylim(0, 6) + ax.set_aspect('auto') + ax.axis('off') + ax.set_title('Inwersja priorytetów', fontsize=FS_LABEL, fontweight='bold') + + # Timeline + labels = ['H (wysoki)', 'M (średni)', 'L (niski)'] + ys = [4.2, 2.8, 1.4] + for label, y in zip(labels, ys): + ax.text(0.3, y + 0.2, label, fontsize=FS, fontweight='bold', va='center') + + # L runs and locks mutex + draw_box(ax, 2.0, ys[2], 1.2, 0.5, 'lock(m)', fill=GRAY1, fontsize=FS_SMALL) + + # M preempts L + draw_box(ax, 3.5, ys[1], 3.0, 0.5, 'M pracuje...', fill=GRAY3, fontsize=FS_SMALL) + + # H waits for mutex + draw_box(ax, 3.5, ys[0], 3.0, 0.5, 'CZEKA na mutex!', fill='#F8D7DA', + fontsize=FS_SMALL, fontweight='bold') + + # M finishes, L continues, unlocks + draw_box(ax, 6.8, ys[2], 1.5, 0.5, 'unlock(m)', fill=GRAY1, fontsize=FS_SMALL) + draw_box(ax, 8.5, ys[0], 1.2, 0.5, 'H runs', fill=GRAY4, fontsize=FS_SMALL) + + # Explanation + ax.text(5.0, 0.5, 'H czeka na M (mimo H > M)!\n' + 'Rozwiązanie: Priority Inheritance\n' + 'L dziedziczy priorytet H → M nie wypycha L', + fontsize=FS_SMALL, ha='center', style='italic', + bbox=dict(boxstyle='round,pad=0.3', facecolor=GRAY4, edgecolor=GRAY3)) + + ax.text(5.0, 0.0, 'Mars Pathfinder (1997) — klasyczny bug!', + fontsize=FS_SMALL, ha='center', fontweight='bold') + + fig.tight_layout(rect=[0, 0, 1, 0.9]) + save_fig(fig, 'q9_starvation_priority.png') + + +# ============================================================ +# 14. Bounded buffer + readers-writers + philosophers +# ============================================================ +def gen_classic_problems(): + fig, axes = plt.subplots(1, 3, figsize=(12, 5)) + fig.suptitle('Klasyczne problemy synchronizacji', fontsize=FS_TITLE, + fontweight='bold') + + # Panel 1: Bounded Buffer with semaphores + ax = axes[0] + ax.set_xlim(0, 8) + ax.set_ylim(0, 7) + ax.set_aspect('auto') + ax.axis('off') + ax.set_title('Producent-Konsument\n(Bounded Buffer, N=4)', fontsize=FS, + fontweight='bold') + + draw_box(ax, 0.2, 4.0, 2.0, 1.2, 'Producent\nP(empty)\nP(mutex)\nwstaw()\nV(mutex)\nV(full)', + fill=GRAY1, fontsize=5.5) + # Buffer + items = ['A', 'B', '', ''] + for i, item in enumerate(items): + x = 2.8 + i * 0.9 + fill = GRAY3 if item else 'white' + draw_box(ax, x, 4.3, 0.9, 0.7, item, fill=fill, fontsize=FS, + fontweight='bold', rounded=False) + ax.text(4.6, 5.2, 'Bufor (N=4)', fontsize=FS_SMALL, ha='center', + fontweight='bold') + draw_box(ax, 6.0, 4.0, 2.0, 1.2, 'Konsument\nP(full)\nP(mutex)\npobierz()\nV(mutex)\nV(empty)', + fill=GRAY4, fontsize=5.5) + draw_arrow(ax, 2.2, 4.6, 2.8, 4.65, lw=1.2) + draw_arrow(ax, 6.4, 4.65, 6.0, 4.6, lw=1.2) + + # Semaphores + sems = [('mutex = 1', GRAY2), ('empty = N', GRAY1), ('full = 0', GRAY3)] + for i, (s, c) in enumerate(sems): + draw_box(ax, 2.0, 2.5 - i * 0.6, 4.0, 0.45, s, fill=c, fontsize=FS_SMALL, + fontweight='bold') + + ax.text(4.0, 0.5, 'KOLEJNOŚĆ: P(empty/full)\nPRZED P(mutex)!\nOdwrotnie = DEADLOCK', + fontsize=5.5, ha='center', fontweight='bold', color='#C62828', + bbox=dict(boxstyle='round', facecolor='#F8D7DA', edgecolor='#C62828')) + + # Panel 2: Readers-Writers + ax = axes[1] + ax.set_xlim(0, 8) + ax.set_ylim(0, 7) + ax.set_aspect('auto') + ax.axis('off') + ax.set_title('Czytelnicy-Pisarze\n(Readers-Writers)', fontsize=FS, + fontweight='bold') + + # Resource + draw_box(ax, 2.5, 3.5, 3.0, 1.5, 'Dane\n(współdzielone)', fill=GRAY2, + fontsize=FS, fontweight='bold') + + # Readers + for i in range(3): + x = 0.3 + i * 1.0 + draw_box(ax, x, 5.5, 0.8, 0.7, f'R{i+1}', fill=GRAY4, fontsize=FS, + fontweight='bold') + draw_arrow(ax, x + 0.4, 5.5, 3.0 + i * 0.5, 5.0, lw=1) + + ax.text(1.5, 6.5, 'Czytelnicy (wielu naraz)', fontsize=FS_SMALL, + ha='center', fontweight='bold') + + # Writer + draw_box(ax, 5.5, 5.5, 1.5, 0.7, 'Pisarz', fill=GRAY5, fontsize=FS, + fontweight='bold') + draw_arrow(ax, 6.25, 5.5, 5.0, 5.0, lw=1.5) + ax.text(6.25, 6.5, 'WYŁĄCZNY', fontsize=FS_SMALL, ha='center', + fontweight='bold', color='#C62828') + + # Rules + rules = ['Wielu czytelników = OK', 'Jeden pisarz = wyłączny', + 'Czytelnik + Pisarz = ✗', 'Problem: pisarze głodują'] + for i, r in enumerate(rules): + ax.text(4.0, 2.5 - i * 0.45, r, fontsize=FS_SMALL, ha='center') + + ax.text(4.0, 0.5, 'Rozwiązanie:\nrw_mutex + count_mutex\n+ zmienna readers', + fontsize=5.5, ha='center', + bbox=dict(boxstyle='round,pad=0.2', facecolor=GRAY4, edgecolor=GRAY3)) + + # Panel 3: Dining Philosophers + ax = axes[2] + ax.set_xlim(0, 8) + ax.set_ylim(0, 7) + ax.set_aspect('auto') + ax.axis('off') + ax.set_title('Ucztujący filozofowie\n(Dining Philosophers)', fontsize=FS, + fontweight='bold') + + # Draw circular table + cx, cy, r = 4.0, 3.8, 1.8 + table = plt.Circle((cx, cy), 0.8, fill=True, facecolor=GRAY2, + edgecolor=LN, lw=1.5) + ax.add_patch(table) + ax.text(cx, cy, 'Stół', fontsize=FS, ha='center', fontweight='bold') + + # 5 philosophers around table + for i in range(5): + angle = np.pi/2 + i * 2 * np.pi / 5 + px = cx + r * np.cos(angle) + py = cy + r * np.sin(angle) + circle = plt.Circle((px, py), 0.35, fill=True, facecolor=GRAY1, + edgecolor=LN, lw=1.2) + ax.add_patch(circle) + ax.text(px, py, f'F{i}', ha='center', va='center', fontsize=FS, + fontweight='bold') + + # Fork between philosophers + fork_angle = np.pi/2 + (i + 0.5) * 2 * np.pi / 5 + fx = cx + (r * 0.6) * np.cos(fork_angle) + fy = cy + (r * 0.6) * np.sin(fork_angle) + ax.plot([fx - 0.1, fx + 0.1], [fy - 0.15, fy + 0.15], color=LN, + lw=2.5, solid_capstyle='round') + ax.text(fx + 0.2, fy + 0.15, f'w{i}', fontsize=5, color='#555555') + + # Rules + rules = ['Jedzenie = 2 widelce', 'Naiwne → DEADLOCK', + 'Fix: F4 bierze odwrotnie', + 'Alt: semafor(4)'] + for i, r in enumerate(rules): + ax.text(4.0, 1.2 - i * 0.35, r, fontsize=FS_SMALL, ha='center') + + fig.tight_layout(rect=[0, 0, 1, 0.88]) + save_fig(fig, 'q9_classic_problems.png') + + +# ============================================================ +# 15. Sync mechanisms comparison + mutex/sem/spinlock +# ============================================================ +def gen_sync_comparison(): + fig, axes = plt.subplots(2, 1, figsize=(9, 7)) + + # Top: comparison table + ax = axes[0] + ax.set_xlim(0, 11.5) + ax.set_ylim(-5, 1) + ax.set_aspect('auto') + ax.axis('off') + ax.set_title('Mechanizmy synchronizacji — porównanie', fontsize=FS_TITLE, + fontweight='bold', pad=10) + + headers = ['Mechanizm', 'Opis', 'Kiedy używać'] + col_w = [2.5, 4.5, 4.0] + rows = [ + ['Mutex', 'Zamek: 1 wątek w sekcji', 'Sekcja krytyczna'], + ['Semafor(n)', 'Licznik: max n wątków', 'Ograniczone zasoby (n miejsc)'], + ['Monitor', 'Obiekt z wbudowanym mutex', 'Java synchronized'], + ['Cond. Variable', 'wait()/signal() na warunek', 'Producent-konsument'], + ['Spinlock', 'Aktywne czekanie (busy-wait)', 'Bardzo krótkie sekcje (<1 μs)'], + ['RW Lock', 'Wielu czytelników LUB 1 pisarz', 'Bazy danych, cache'], + ['Barrier', 'Czekaj aż wszyscy dotrą', 'Obliczenia równoległe'], + ] + draw_table(ax, headers, rows, x0=0.25, y0=0.5, col_widths=col_w, + row_h=0.5, fontsize=7) + + # Bottom: mutex vs semafor vs spinlock + ax = axes[1] + ax.set_xlim(0, 12) + ax.set_ylim(0, 5) + ax.set_aspect('auto') + ax.axis('off') + ax.set_title('Mutex vs Semafor vs Spinlock', fontsize=FS_TITLE, + fontweight='bold', pad=5) + + # Mutex + draw_box(ax, 0.3, 2.5, 3.5, 2.0, '', fill=GRAY4) + ax.text(2.05, 4.2, 'MUTEX', fontsize=FS_LABEL, fontweight='bold', ha='center') + ax.text(2.05, 3.6, '= klucz do łazienki\n(1 osoba)', fontsize=FS, ha='center') + ax.text(2.05, 2.8, 'Wątek ZASYPIA gdy czeka\nOS go obudzi (~μs)', fontsize=FS_SMALL, + ha='center', style='italic') + + # Semafor + draw_box(ax, 4.3, 2.5, 3.5, 2.0, '', fill=GRAY1) + ax.text(6.05, 4.2, 'SEMAFOR(n)', fontsize=FS_LABEL, fontweight='bold', ha='center') + ax.text(6.05, 3.6, '= parking na n miejsc\n(n wątków naraz)', fontsize=FS, ha='center') + ax.text(6.05, 2.8, 'Semafor(1) = mutex\nP() = zmniejsz, V() = zwiększ', + fontsize=FS_SMALL, ha='center', style='italic') + + # Spinlock + draw_box(ax, 8.3, 2.5, 3.5, 2.0, '', fill=GRAY2) + ax.text(10.05, 4.2, 'SPINLOCK', fontsize=FS_LABEL, fontweight='bold', ha='center') + ax.text(10.05, 3.6, '= obrotowe drzwi\n(kręcisz się w kółko)', fontsize=FS, ha='center') + ax.text(10.05, 2.8, 'Wątek KRĘCI się w pętli\nLepszy gdy sekcja < 1 μs', + fontsize=FS_SMALL, ha='center', style='italic') + + # Dividing rule + ax.text(6.0, 1.5, 'Reguła kciuka: sekcja > 1 μs → MUTEX | ' + 'sekcja < 1 μs → SPINLOCK | n jednocześnie → SEMAFOR(n)', + fontsize=FS, ha='center', fontweight='bold', + bbox=dict(boxstyle='round,pad=0.3', facecolor=GRAY4, edgecolor=GRAY3)) + + fig.tight_layout() + save_fig(fig, 'q9_sync_comparison.png') + + +# ============================================================ +# 16. Semaphore concept diagram +# ============================================================ +def gen_semaphore_concept(): + fig, ax = plt.subplots(figsize=(6, 3)) + ax.set_xlim(0, 10) + ax.set_ylim(0, 5) + ax.set_aspect('auto') + ax.axis('off') + ax.set_title('Semafor — koncepcja (parking na 3 miejsca)', fontsize=FS_TITLE, + fontweight='bold', pad=10) + + # Parking slots + for i in range(3): + x = 2.0 + i * 2.0 + occupied = i < 2 # 2 occupied, 1 free + fill = GRAY3 if occupied else 'white' + label = f'Wątek {i+1}' if occupied else '(wolne)' + draw_box(ax, x, 2.5, 1.5, 1.2, label, fill=fill, fontsize=FS, + fontweight='bold' if occupied else 'normal', rounded=False) + + ax.text(5.0, 4.2, 'semafor(3): counter = 1 (jedno wolne miejsce)', + fontsize=FS, ha='center', fontweight='bold') + + # Waiting thread + draw_box(ax, 0.2, 0.5, 1.5, 0.8, 'Wątek 4\nP() → czeka', fill='#F8D7DA', + fontsize=FS_SMALL) + draw_arrow(ax, 1.7, 0.9, 2.0, 2.5, lw=1.2, color='#C62828') + + ax.text(5.0, 0.6, 'P() = counter−− (jeśli 0 → czekaj)\n' + 'V() = counter++ (obudź czekającego)', + fontsize=FS, ha='center', family='monospace', + bbox=dict(boxstyle='round,pad=0.2', facecolor=GRAY4, edgecolor=GRAY3)) + + save_fig(fig, 'q9_semaphore_concept.png') + + +# ============================================================ +# MAIN — generate all +# ============================================================ +if __name__ == '__main__': + print("Generating ALL PYTANIE 9 diagrams...") + gen_process_vs_thread() + gen_memory_layout() + gen_process_states() + gen_thread_structure() + gen_pcb_structure() + gen_speed_comparison() + gen_scenario_table() + gen_ipc_details() + gen_ipc_table() + gen_race_condition() + gen_deadlock_scenario() + gen_coffman_strategies() + gen_starvation_priority() + gen_classic_problems() + gen_sync_comparison() + gen_semaphore_concept() + print("\nAll 16 PYTANIE 9 diagrams generated successfully!") diff --git a/pytania/img/q20_batch_vs_streaming.png b/pytania/img/q20_batch_vs_streaming.png new file mode 100644 index 0000000..ed1f98f Binary files /dev/null and b/pytania/img/q20_batch_vs_streaming.png differ diff --git a/pytania/img/q20_decision_tree.png b/pytania/img/q20_decision_tree.png new file mode 100644 index 0000000..8c0e0ed Binary files /dev/null and b/pytania/img/q20_decision_tree.png differ diff --git a/pytania/img/q20_event_vs_processing_time.png b/pytania/img/q20_event_vs_processing_time.png new file mode 100644 index 0000000..20f4f39 Binary files /dev/null and b/pytania/img/q20_event_vs_processing_time.png differ diff --git a/pytania/img/q20_exactly_once.png b/pytania/img/q20_exactly_once.png new file mode 100644 index 0000000..14f64e1 Binary files /dev/null and b/pytania/img/q20_exactly_once.png differ diff --git a/pytania/img/q20_flink_arch.png b/pytania/img/q20_flink_arch.png new file mode 100644 index 0000000..ecf5a0b Binary files /dev/null and b/pytania/img/q20_flink_arch.png differ diff --git a/pytania/img/q20_kafka_streams_arch.png b/pytania/img/q20_kafka_streams_arch.png new file mode 100644 index 0000000..c2c6544 Binary files /dev/null and b/pytania/img/q20_kafka_streams_arch.png differ diff --git a/pytania/img/q20_lambda_kappa_table.png b/pytania/img/q20_lambda_kappa_table.png new file mode 100644 index 0000000..011cdc5 Binary files /dev/null and b/pytania/img/q20_lambda_kappa_table.png differ diff --git a/pytania/img/q20_lambda_vs_kappa.png b/pytania/img/q20_lambda_vs_kappa.png new file mode 100644 index 0000000..0ce68db Binary files /dev/null and b/pytania/img/q20_lambda_vs_kappa.png differ diff --git a/pytania/img/q20_late_data_strategies.png b/pytania/img/q20_late_data_strategies.png new file mode 100644 index 0000000..7c60548 Binary files /dev/null and b/pytania/img/q20_late_data_strategies.png differ diff --git a/pytania/img/q20_platform_comparison.png b/pytania/img/q20_platform_comparison.png new file mode 100644 index 0000000..f9b29b4 Binary files /dev/null and b/pytania/img/q20_platform_comparison.png differ diff --git a/pytania/img/q20_session_users.png b/pytania/img/q20_session_users.png new file mode 100644 index 0000000..cf33e91 Binary files /dev/null and b/pytania/img/q20_session_users.png differ diff --git a/pytania/img/q20_sliding_sla.png b/pytania/img/q20_sliding_sla.png new file mode 100644 index 0000000..2d882d9 Binary files /dev/null and b/pytania/img/q20_sliding_sla.png differ diff --git a/pytania/img/q20_spark_streaming_arch.png b/pytania/img/q20_spark_streaming_arch.png new file mode 100644 index 0000000..eb67d36 Binary files /dev/null and b/pytania/img/q20_spark_streaming_arch.png differ diff --git a/pytania/img/q20_streaming_ecosystem.png b/pytania/img/q20_streaming_ecosystem.png new file mode 100644 index 0000000..6c364d4 Binary files /dev/null and b/pytania/img/q20_streaming_ecosystem.png differ diff --git a/pytania/img/q20_true_vs_microbatch.png b/pytania/img/q20_true_vs_microbatch.png new file mode 100644 index 0000000..eed6744 Binary files /dev/null and b/pytania/img/q20_true_vs_microbatch.png differ diff --git a/pytania/img/q20_tumbling_fraud.png b/pytania/img/q20_tumbling_fraud.png new file mode 100644 index 0000000..164a89f Binary files /dev/null and b/pytania/img/q20_tumbling_fraud.png differ diff --git a/pytania/img/q20_window_types.png b/pytania/img/q20_window_types.png new file mode 100644 index 0000000..1d78b29 Binary files /dev/null and b/pytania/img/q20_window_types.png differ diff --git a/pytania/img/q9_classic_problems.png b/pytania/img/q9_classic_problems.png new file mode 100644 index 0000000..e1aeaaa Binary files /dev/null and b/pytania/img/q9_classic_problems.png differ diff --git a/pytania/img/q9_coffman_strategies.png b/pytania/img/q9_coffman_strategies.png new file mode 100644 index 0000000..ef55e3e Binary files /dev/null and b/pytania/img/q9_coffman_strategies.png differ diff --git a/pytania/img/q9_deadlock_scenario.png b/pytania/img/q9_deadlock_scenario.png new file mode 100644 index 0000000..1c69613 Binary files /dev/null and b/pytania/img/q9_deadlock_scenario.png differ diff --git a/pytania/img/q9_ipc_details.png b/pytania/img/q9_ipc_details.png new file mode 100644 index 0000000..a1d0b44 Binary files /dev/null and b/pytania/img/q9_ipc_details.png differ diff --git a/pytania/img/q9_ipc_table.png b/pytania/img/q9_ipc_table.png new file mode 100644 index 0000000..54a85d3 Binary files /dev/null and b/pytania/img/q9_ipc_table.png differ diff --git a/pytania/img/q9_memory_layout.png b/pytania/img/q9_memory_layout.png new file mode 100644 index 0000000..752a033 Binary files /dev/null and b/pytania/img/q9_memory_layout.png differ diff --git a/pytania/img/q9_pcb_structure.png b/pytania/img/q9_pcb_structure.png new file mode 100644 index 0000000..311c6e4 Binary files /dev/null and b/pytania/img/q9_pcb_structure.png differ diff --git a/pytania/img/q9_process_states.png b/pytania/img/q9_process_states.png new file mode 100644 index 0000000..fd187a7 Binary files /dev/null and b/pytania/img/q9_process_states.png differ diff --git a/pytania/img/q9_process_vs_thread.png b/pytania/img/q9_process_vs_thread.png new file mode 100644 index 0000000..2d5425d Binary files /dev/null and b/pytania/img/q9_process_vs_thread.png differ diff --git a/pytania/img/q9_race_condition.png b/pytania/img/q9_race_condition.png new file mode 100644 index 0000000..f041dde Binary files /dev/null and b/pytania/img/q9_race_condition.png differ diff --git a/pytania/img/q9_scenario_table.png b/pytania/img/q9_scenario_table.png new file mode 100644 index 0000000..229176b Binary files /dev/null and b/pytania/img/q9_scenario_table.png differ diff --git a/pytania/img/q9_semaphore_concept.png b/pytania/img/q9_semaphore_concept.png new file mode 100644 index 0000000..bdb2d38 Binary files /dev/null and b/pytania/img/q9_semaphore_concept.png differ diff --git a/pytania/img/q9_speed_comparison.png b/pytania/img/q9_speed_comparison.png new file mode 100644 index 0000000..fd846f8 Binary files /dev/null and b/pytania/img/q9_speed_comparison.png differ diff --git a/pytania/img/q9_starvation_priority.png b/pytania/img/q9_starvation_priority.png new file mode 100644 index 0000000..8bf4a60 Binary files /dev/null and b/pytania/img/q9_starvation_priority.png differ diff --git a/pytania/img/q9_sync_comparison.png b/pytania/img/q9_sync_comparison.png new file mode 100644 index 0000000..6cfaabd Binary files /dev/null and b/pytania/img/q9_sync_comparison.png differ diff --git a/pytania/img/q9_thread_structure.png b/pytania/img/q9_thread_structure.png new file mode 100644 index 0000000..d00cddd Binary files /dev/null and b/pytania/img/q9_thread_structure.png differ diff --git a/pytania/questions/pytanie_02.md b/pytania/questions/pytanie_02.md index c57df80..37b454e 100644 --- a/pytania/questions/pytanie_02.md +++ b/pytania/questions/pytanie_02.md @@ -128,197 +128,95 @@ Przykład — kluczowa różnica: decrease-key: ### Pseudokod (Python) -**Dijkstra:** +**Dijkstra** (graph = słownik sąsiedztwa, np. `{'A': [('B',2), ('C',4)]}`): - def dijkstra(graph, start): # graph = słownik, klucz = wierzchołek, - # wartość = lista par (sąsiad, waga) - # np. graph = {'A': [('B',2), ('C',4)], - # 'B': [('D',3)], ...} - # start = wierzchołek startowy, np. 'A' - - d = {v: float('inf') for v in graph} # d = słownik odległości (distance) - # Klucz: wierzchołek v - # Wartość: najkrótsza DOTYCHCZAS ZNANA - # odległość od start do v - # Na początku: ∞ (nieskończoność) dla - # wszystkich — bo jeszcze niczego - # nie odkryliśmy - # float('inf') = Python-owa nieskończoność, - # każda liczba jest od niej mniejsza - - d[start] = 0 # odległość od startu do samego siebie = 0 - - visited = set() # visited = zbiór ZAMKNIĘTYCH wierzchołków - # (już przetworzonych, nie wracamy do nich) - # set() = pusty zbiór Pythona — O(1) lookup - - for _ in range(len(graph)): # powtórz V razy (raz na każdy wierzchołek) - # W każdej iteracji wybieramy JEDEN - # wierzchołek o min d[v] i go przetwarzamy - - # --- Szukanie minimum w tablicy d --- → O(V) na każde szukanie - u = None # u = wierzchołek o najmniejszej odległości - # (jeszcze nie odwiedzony) - for v in graph: # przejrzyj WSZYSTKIE wierzchołki - if v not in visited: # pomiń już odwiedzone - if u is None or d[v] < d[u]: # jeśli v ma mniejszą odległość - u = v # zapamiętaj go jako kandydata - # Po tej pętli: u = wierzchołek z min d, spośród nieodwiedzonych - # To jest O(V) — przeszukujemy całą tablicę! - - if d[u] == float('inf'): # jeśli minimum to ∞, reszta jest - break # nieosiągalna — koniec - - visited.add(u) # oznacz u jako odwiedzony (zamknięty) - # NIE WRACAMY do u — to jest ZACHŁANNOŚĆ - # Dijkstry (i dlatego ujemne wagi psują!) - - for v, w in graph[u]: # iteruj po sąsiadach wierzchołka u - # v = sąsiad (vertex), w = waga krawędzi u→v - # np. graph['A'] = [('B',2), ('C',4)] - # → v='B', w=2, potem v='C', w=4 - - if d[u] + w < d[v]: # RELAKSACJA: czy droga do v PRZEZ u - # jest krótsza niż dotychczas znana? - # d[u] = koszt dotarcia do u - # w = koszt krawędzi u→v - # d[u]+w = koszt drogi start→u→v - # d[v] = dotychczasowy najlepszy koszt do v - - d[v] = d[u] + w # TAK, jest krótsza → zaktualizuj! - # (tablica d pełni tu rolę kolejki - # priorytetowej — po prostu szukamy - # minimum w niej w każdej iteracji) - - return d # zwróć słownik najkrótszych odległości - # np. {'A': 0, 'B': 2, 'C': 4, 'D': 5} - # Złożoność: O(V²) — V szukań min × O(V) każde + def dijkstra(graph, source): + dist = {v: float('inf') for v in graph} + dist[source] = 0 + visited = set() + for _ in range(len(graph)): + current = None # szukaj nieodwiedzonego wierzchołka o min dist — O(V) + for v in graph: + if v not in visited and (current is None or dist[v] < dist[current]): + current = v + if dist[current] == float('inf'): + break # reszta nieosiągalna + visited.add(current) # zamknij — NIE wracamy (zachłanność) + for neighbor, weight in graph[current]: # relaksacja sąsiadów + if dist[current] + weight < dist[neighbor]: + dist[neighbor] = dist[current] + weight + return dist # O(V²) z tablicą ![Przejście grafu algorytmem Dijkstry — krok po kroku](img/dijkstra_traversal.png) -**Bellman-Ford:** +**Bellman-Ford** (vertices = lista wierzchołków, edges = lista krotek (src, dst, weight)): - def bellman_ford(vertices, edges, start): # vertices = lista wierzchołków, np. ['A','B','C','D'] - # edges = lista krawędzi, każda to (skąd, dokąd, waga) - # np. [('A','B',2), ('A','C',4), ('B','D',3), ...] - # start = wierzchołek startowy - # UWAGA: format inny niż w Dijkstrze! - # Dijkstra: graf jako słownik sąsiedztwa - # B-F: explicite lista krawędzi + def bellman_ford(vertices, edges, source): + dist = {v: float('inf') for v in vertices} + dist[source] = 0 + for _ in range(len(vertices) - 1): # V−1 iteracji (najdłuższa ścieżka = V−1 krawędzi) + for src, dst, weight in edges: # relaksuj WSZYSTKIE krawędzie + if dist[src] + weight < dist[dst]: + dist[dst] = dist[src] + weight + for src, dst, weight in edges: # V-ta iteracja: wykrywanie cyklu ujemnego + if dist[src] + weight < dist[dst]: + return None # cykl ujemny! + return dist # O(V·E) - d = {v: float('inf') for v in vertices} # d = słownik odległości — identycznie - # jak w Dijkstrze. Klucz = wierzchołek, - # wartość = najkrótsza znana odległość. - # Na starcie: ∞ dla wszystkich. +Przykład — graf z ujemnymi wagami (Dijkstra daje ZŁY wynik, B-F poprawny): - d[start] = 0 # odległość do siebie = 0 + Graf: S→A(2), A→C(3), S→B(5), B→A(−4) - for _ in range(len(vertices) - 1): # powtórz V−1 razy (V = liczba wierzchołków) - # DLACZEGO V−1? Bo najdłuższa najkrótsza - # ścieżka (bez cykli) ma co najwyżej V−1 - # krawędzi. Po k iteracjach mamy poprawne - # odległości dla ścieżek o ≤ k krawędziach. - # _ = zmienna, której nie używamy (konwencja) + Dijkstra: + 1. S(0): dist[A]=2, dist[B]=5 + 2. A(2) zamknięty: dist[C]=5 + 3. B(5): B→A = 5−4 = 1 < 2, ALE A już zamknięty → POMIJA! + Wynik: A=2, C=5 ← BŁĄD (prawidłowe: A=1, C=4) - for u, v, w in edges: # w KAŻDEJ iteracji przejrzyj WSZYSTKIE krawędzie - # u = początek krawędzi, v = koniec, w = waga - # To jest brute-force — stąd O(V·E) + Bellman-Ford — relaksuje WSZYSTKIE krawędzie, V−1 = 3 razy: + Start: dist = [S:0, A:∞, B:∞, C:∞] - if d[u] + w < d[v]: # RELAKSACJA — identyczna jak w Dijkstrze: - # czy droga start→u→v jest krótsza niż d[v]? + Iteracja 1: + S→A: 0+2=2 < ∞ → A=2 + A→C: 2+3=5 < ∞ → C=5 + S→B: 0+5=5 < ∞ → B=5 + B→A: 5−4=1 < 2 → A=1 ← ujemna waga poprawia! - d[v] = d[u] + w # TAK → zaktualizuj + Iteracja 2: + A→C: 1+3=4 < 5 → C=4 ← propagacja poprawionego A - # --- Wykrywanie cyklu ujemnego --- - for u, v, w in edges: # dodatkowe (V-te) przejście po krawędziach - # Jeśli NADAL da się poprawić odległość, - # to znaczy, że istnieje cykl ujemny! - # (po V−1 iteracjach powinno być stabilne) + Iteracja 3: brak zmian → stabilne. + Wynik: [S:0, A:1, B:5, C:4] ← POPRAWNE - if d[u] + w < d[v]: # nadal można polepszyć? → cykl ujemny! - return None # zwróć None = sygnał "cykl ujemny wykryty" - - return d # zwróć słownik odległości (jak Dijkstra) + Wykrywanie cyklu ujemnego — dodaj krawędź C→B(−3): + Cykl B→A→C→B = −4 + 3 + (−3) = −4 < 0. + Po V−1 iteracjach dist nadal maleje → V-ta iteracja: + dist[src] + weight < dist[dst] → return None ![Przejście grafu algorytmem Bellmana-Forda — krok po kroku](img/bellman_ford_traversal.png) -**A*:** +**A\*** (graph jak Dijkstra; heuristic = h(v) → oszacowanie odl. do celu): - def a_star(graph, start, goal, h): # graph = słownik sąsiedztwa (jak Dijkstra) - # start = wierzchołek startowy - # goal = wierzchołek DOCELOWY (cel) - # → to jedyna różnica od Dijkstry: - # szukamy ścieżki do JEDNEGO celu - # h = FUNKCJA heurystyczna: h(v) zwraca - # oszacowanie odległości od v do goal - # np. h = lambda v: odl_euklidesowa(v, goal) - - d = {start: 0} # d = słownik g(n) = faktyczny koszt - # dotarcia od start do n - # Tu trzymamy TYLKO odkryte wierzchołki - # (nie inicjalizujemy ∞ dla reszty) - - f = {start: h(start)} # f = słownik f(n) = g(n) + h(n) - # f to szacunkowy ŁĄCZNY koszt ścieżki: - # dotychczasowy koszt g + heurystyka h - # Sortujemy po f (nie po g!) — to kieruje - # przeszukiwanie W STRONĘ CELU - # Na starcie: f(start) = 0 + h(start) - - came_from = {} # came_from = słownik "skąd przyszliśmy" - # Klucz: wierzchołek v - # Wartość: wierzchołek, z którego dotarliśmy do v - # Służy do ODTWORZENIA ścieżki po znalezieniu celu - # np. came_from = {'B':'A', 'D':'B'} - # → ścieżka: A → B → D - - visited = set() # visited = zbiór zamkniętych wierzchołków - # (już przetworzonych) - - while f: # dopóki są odkryte, nieprzetworzone wierzchołki - # (f zawiera tylko te, do których dotarliśmy) - - # --- Szukanie minimum f w tablicy --- → O(V) - u = min(f, key=f.get) # u = wierzchołek o najniższym f(n) - # min() przeszukuje WSZYSTKIE klucze w f - # key=f.get → porównuj po wartościach f[v] - # Równoważne: for v in f: if f[v] < f[best]... - del f[u] # usuń u z open set (przetwarzamy go teraz) - - if u == goal: break # ZNALEZIONO CEL! → przerwij - # Kluczowa optymalizacja A*: - # Dijkstra przetwarza WSZYSTKIE wierzchołki, - # A* KOŃCZY gdy dotrze do celu - - visited.add(u) # oznacz u jako przetworzony - - for v, w in graph[u]: # iteruj po sąsiadach u - # v = sąsiad, w = waga krawędzi u→v - - if v in visited: # jeśli v już przetworzony → pomiń + def a_star(graph, source, goal, heuristic): + cost_so_far = {source: 0} # g(n) — faktyczny koszt dotarcia + priority = {source: heuristic(source)} # f(n) = g(n) + h(n) + came_from = {} # do odtworzenia ścieżki + visited = set() + while priority: + current = min(priority, key=priority.get) # wierzchołek o min f(n) + del priority[current] + if current == goal: + break # dotarliśmy — A* kończy (Dijkstra przetworzyłby wszystko) + visited.add(current) + for neighbor, weight in graph[current]: + if neighbor in visited: continue - - g_new = d[u] + w # g_new = potencjalny nowy koszt dotarcia do v - # (koszt do u + krawędź u→v) - - if v not in d or g_new < d[v]: # jeśli v jeszcze nie odkryty - # LUB znaleźliśmy krótszą drogę - - d[v] = g_new # zaktualizuj g(v) = faktyczny koszt do v - - f[v] = g_new + h(v) # zaktualizuj f(v) = g(v) + h(v) - # f kieruje przeszukiwanie: - # niskie f = „obiecujący" wierzchołek - # (blisko celu wg heurystyki) - - came_from[v] = u # zapamiętaj: do v dotarliśmy z u - # (do odtworzenia ścieżki) - - return came_from, d.get(goal) # came_from = mapa do odtworzenia ścieżki - # d.get(goal) = koszt najkrótszej ścieżki - # do celu (None jeśli nieosiągalny) - # Złożoność: O(V²) z tablicą, ale w praktyce - # dużo szybciej dzięki heurystyce + new_cost = cost_so_far[current] + weight + if neighbor not in cost_so_far or new_cost < cost_so_far[neighbor]: + cost_so_far[neighbor] = new_cost + priority[neighbor] = new_cost + heuristic(neighbor) + came_from[neighbor] = current + return came_from, cost_so_far.get(goal) # ścieżka + koszt ![Przejście grafu algorytmem A* — krok po kroku](img/astar_traversal.png) diff --git a/pytania/questions/pytanie_09.md b/pytania/questions/pytanie_09.md index 58f223f..5429097 100644 --- a/pytania/questions/pytanie_09.md +++ b/pytania/questions/pytanie_09.md @@ -8,18 +8,9 @@ **Proces (process)** — program w trakcie wykonania. Każdy proces ma własną, izolowaną przestrzeń adresową. System operacyjny zarządza procesami — tworzy, planuje (scheduling), kończy. Np. przeglądarka i edytor to osobne procesy. -**Wątek (thread)** — lekka jednostka wykonania wewnątrz procesu. Wątki jednego procesu współdzielą pamięć (kod, dane, heap), ale mają własny stos i rejestry CPU. Tworzenie wątku jest ~100x szybsze niż procesu. +**Wątek (thread)** — lekka jednostka wykonania wewnątrz procesu. Wątki jednego procesu współdzielą pamięć (kod, dane, heap), ale mają własny stos i rejestry CPU. Tworzenie wątku jest ~100× szybsze niż procesu. - Proces = mieszkanie (własny adres, izolacja) - Wątek = pokój w mieszkaniu (współdzielona kuchnia = heap) - - Cecha Proces Wątek - ───────────────────────────────────────── - Pamięć własna współdzielona - Tworzenie ~1-10 ms ~10-100 μs - Przełączanie wolne (TLB) szybkie (rejestry) - Komunikacja IPC/pipe bezpośrednia - Awaria izolowana może zabić proces +![Proces vs Wątek — porównanie](img/q9_process_vs_thread.png) --- @@ -32,23 +23,20 @@ - **HEAP** — pamięć alokowana dynamicznie (malloc/new), rośnie w górę - **STACK** — zmienne lokalne, adresy powrotu, rośnie w dół - ┌──────────┐ wysoki adres - │ STACK ↓ │ - │ ... │ - │ HEAP ↑ │ - │ BSS │ - │ DATA │ - │ TEXT │ - └──────────┘ niski adres +![Segmenty pamięci procesu](img/q9_memory_layout.png) **PCB (Process Control Block)** — struktura danych w jądrze OS opisująca proces: PID, stan, rejestry CPU, tablice stron, otwarte pliki, priorytety. Przełączenie kontekstu = zapisanie PCB starego procesu i wczytanie nowego. +![PCB — Process Control Block](img/q9_pcb_structure.png) + **PID (Process ID)** — unikalny identyfikator procesu w systemie. Np. `PID 1` = init/systemd w Linux. **TID (Thread ID)** — unikalny identyfikator wątku. **Stany procesu:** NEW (tworzony) → READY (gotowy, czeka na CPU) ↔ RUNNING (wykonywany) → BLOCKED (czeka na I/O), TERMINATED (zakończony). Scheduler decyduje, który READY staje się RUNNING. +![Stany procesu — diagram przejść](img/q9_process_states.png) + --- **Przełączanie kontekstu (context switch)** — zapisanie stanu aktualnego procesu/wątku i wczytanie stanu następnego. Dla procesów kosztowne (wymaga TLB flush = unieważnienie cache translacji adresów). Dla wątków tańsze (ta sama przestrzeń adresowa = brak TLB flush). @@ -70,17 +58,13 @@ **Wyścig (race condition)** — sytuacja, gdy wynik programu zależy od kolejności wykonania operacji przez wątki. Przykład: dwa wątki zwiększają x=0 o 1 → wynik może być 1 zamiast 2, bo oba czytają 0 zanim zapiszą. - Wątek A: czytaj x(=0) → dodaj 1 → zapisz x(=1) - Wątek B: czytaj x(=0) → dodaj 1 → zapisz x(=1) - Wynik: x = 1 zamiast oczekiwanego 2! +![Wyścig — race condition](img/q9_race_condition.png) **Sekcja krytyczna (critical section)** — fragment kodu, który może być wykonywany przez najwyżej jeden wątek naraz. Chroni współdzielone zasoby przed race condition. **Zakleszczenie (deadlock)** — sytuacja, w której dwa lub więcej wątków czekają na siebie nawzajem i żaden nie może kontynuować. Jak dwa samochody na skrzyżowaniu — oba czekają, nikt nie jedzie. - Wątek A: trzyma mutex1, czeka na mutex2 - Wątek B: trzyma mutex2, czeka na mutex1 - → Zakleszczenie! Żaden nie puści swojego. +![Zakleszczenie — deadlock](img/q9_deadlock_scenario.png) **Warunki Coffmana** — 4 warunki konieczne deadlocka (wszystkie muszą zachodzić jednocześnie): 1. **Mutual exclusion** — zasób jest wyłączny (tylko jeden wątek) @@ -97,9 +81,7 @@ Złam jeden = brak deadlocka. **Semafor (semaphore)** — uogólniony mutex z licznikiem. Semafor binarny (0/1) = mutex. Semafor zliczający (n) — pozwala n wątkom jednocześnie. P() = probeer (zmniejsz), V() = verhoog (zwiększ). - semafor(3): 3 wątki mogą wejść naraz - P() → counter-- (jeśli 0 → czekaj) - V() → counter++ (obudź czekającego) +![Semafor — koncepcja](img/q9_semaphore_concept.png) **Monitor** — wysokopoziomowy mechanizm synchronizacji. Obiekt z mutexem wbudowanym — tylko jeden wątek może wykonywać metody monitora. Java: `synchronized`. @@ -119,25 +101,21 @@ Proces = program w trakcie wykonania + cały jego kontekst. Składa się z: **Pamięć (oddzielna przestrzeń adresowa):** - ┌──────────┐ wysoki adres - │ STACK ↓ │ zmienne lokalne, adresy powrotu (każdy wątek ma WŁASNY) - │ ... │ - │ HEAP ↑ │ malloc/new — dynamiczna alokacja (współdzielony między wątkami) - │ BSS │ zmienne globalne niezainicjalizowane (zerowane) - │ DATA │ zmienne globalne zainicjalizowane - │ TEXT │ kod maszynowy (read-only, współdzielony) - └──────────┘ niski adres +![Segmenty pamięci procesu — szczegóły](img/q9_memory_layout.png) **PCB (Process Control Block)** — struktura w jądrze OS opisująca proces: - PCB = { PID, stan (READY/RUNNING/BLOCKED), rejestry CPU, - tablice stron, otwarte pliki, priorytety, statystyki } +![PCB — struktura](img/q9_pcb_structure.png) Przełączenie kontekstu = zapisanie PCB starego procesu → wczytanie PCB nowego. **Stany procesu:** NEW → READY ↔ RUNNING → BLOCKED → TERMINATED. Scheduler decyduje, który READY staje się RUNNING. +> **Mnemonik BUDOWY PROCESU — „TDBHS" (segmenty od dołu):** +> **T**ata **D**aje **B**abci **H**erbatę ze **S**mietanką = TEXT → DATA → BSS → HEAP → STACK. +> Proces = mieszkanie (własny adres, izolacja), PCB = dowód osobisty procesu (PID, stan, rejestry). + ### Budowa wątku Wątek = lekka jednostka wykonania WEWNĄTRZ procesu. @@ -145,36 +123,30 @@ Wątek = lekka jednostka wykonania WEWNĄTRZ procesu. **Współdzielone** z innymi wątkami procesu: TEXT, DATA, BSS, HEAP, otwarte pliki, PID. **Prywatne** (każdy wątek ma własne): stos (stack), rejestry CPU, program counter (PC), TID. - Proces P (PID=42) - ┌────────────────────────────────────────┐ - │ TEXT │ DATA │ BSS │ HEAP │ ← współdzielone - ├────────┴────────┴───────┴──────────────┤ - │ Wątek 1: [stos₁] [rejestry₁] [PC₁] │ ← prywatne - │ Wątek 2: [stos₂] [rejestry₂] [PC₂] │ - │ Wątek 3: [stos₃] [rejestry₃] [PC₃] │ - └────────────────────────────────────────┘ +![Wątki wewnątrz procesu](img/q9_thread_structure.png) Kluczowa różnica: proces ma CAŁĄ przestrzeń adresową, wątek to tylko kontekst wykonania (stos + rejestry) w ramach tej przestrzeni. +> **Mnemonik BUDOWY WĄTKU — „Wspólna kuchnia, własny pokój":** +> Współdzielone = KOD + DANE + HEAP + PLIKI (kuchnia, salon, łazienka). +> Prywatne = STOS + REJESTRY + PC (twój pokój, twój telefon, twoja pozycja w książce). +> Skrót: **„SRP"** — **S**tos, **R**ejestry, **P**C = prywatne. + ### Szybkość — porównanie ilościowe | Operacja | Proces | Wątek | Dlaczego różnica? | |-------------------|------------------|---------------------|--------------------------------------| -| Tworzenie | ~1–10 ms | ~10–100 μs (100x) | Proces: nowa przestrzeń adresowa, tablice stron, kopiowanie struktur jądra. Wątek: tylko nowy stos + wpis w schedulerze. | -| Przełączanie | ~1000–5000 ns | ~100–500 ns (10x) | Proces: TLB flush (unieważnienie cache adresów). Wątek: te same tablice stron, TLB zostaje. | +| Tworzenie | ~1–10 ms | ~10–100 μs (100×) | Proces: nowa przestrzeń adresowa, tablice stron, kopiowanie struktur jądra. Wątek: tylko nowy stos + wpis w schedulerze. | +| Przełączanie | ~1000–5000 ns | ~100–500 ns (10×) | Proces: TLB flush (unieważnienie cache adresów). Wątek: te same tablice stron, TLB zostaje. | | Komunikacja | ~μs (IPC) | ~ns (pamięć) | Proces: kopiowanie przez jądro (pipe/socket). Wątek: bezpośredni zapis/odczyt heapu. | | Zakończenie | wolne | szybkie | Proces: zwolnienie przestrzeni adresowej, zamknięcie plików. Wątek: zwolnienie stosu. | -Konkretny przykład tworzenia (Linux): +![Szybkość — benchmarki Linux](img/q9_speed_comparison.png) - fork() (nowy proces): ~1-5 ms → kopiowanie tablic stron (copy-on-write) - pthread_create() (wątek): ~50 μs → alokacja stosu (~8 MB) + wpis w schedulerze - -Przełączanie kontekstu (benchmarki): - - Proces → Proces: ~3000 ns (TLB flush + cache cold) - Wątek → Wątek: ~300 ns (TLB ciepły, ta sama pamięć) - Zysk: ~10x szybciej +> **Mnemonik SZYBKOŚCI — „100, 10, 1000":** +> Tworzenie wątku **100×** szybsze, przełączanie **10×** szybsze, komunikacja **1000×** szybsza. +> Dlaczego? **„TLB zostaje"** — wątek nie zmienia mieszkania (przestrzeni adresowej), więc cache adresów (TLB) nie trzeba czyścić. +> Zapamiętaj: **fork() = przeprowadzka**, **pthread_create() = nowy pokój w tym samym mieszkaniu**. ### Zastosowanie — kiedy proces, kiedy wątek? @@ -198,18 +170,13 @@ Przełączanie kontekstu (benchmarki): - Rendering: każdy wątek renderuje część klatki - **Responsywność UI** — wątek główny obsługuje interfejs, wątek tła liczy/pobiera dane -**Typowe scenariusze w praktyce:** +![Kiedy proces, kiedy wątek? — scenariusze](img/q9_scenario_table.png) - Scenariusz Proces czy wątek? Dlaczego? - ────────────────────────────────────────────────────────────── - Serwer WWW (Apache pre-fork) Proces izolacja klientów - Serwer WWW (nginx) Wątek/async szybkość, cooperacja - Przeglądarka (karty) Proces crash isolation - Przeglądarka (JS + rendering) Wątek współdzielony DOM - Gra (fizyka + rendering) Wątek współdzielony świat - Kompilacja wieloplikowa Proces (make -j8) izolacja, prostota - Baza danych (zapytania) Wątek współdzielony cache - Microservices Proces (kontener) izolacja, deployment +> **Mnemonik ZASTOSOWANIA — „MURY vs OKNA":** +> **Proces jak MURY** = izolacja, bezpieczeństwo, crash isolation (Chrome karty, Apache, sandboxing). +> **Wątek jak OKNA** = szybki dostęp do wspólnych danych, lekki, wydajny (gry, thread pool, UI). +> Pytanie-test: „Czy awaria jednego ma zabić resztę?" → TAK = wątek OK, NIE = proces. +> Regułka: **„IBW → P, WSO → W"** = **I**zolacja/**B**ezpieczeństwo/**W**ieloprogramowość → **P**roces. **W**spółdzielenie/**S**zybkość/**O**bliczenia → **W**ątek. ### Porównanie zbiorcze @@ -237,18 +204,12 @@ Procesy mają **izolowane** przestrzenie adresowe — nie mogą bezpośrednio cz #### Mechanizmy IPC z przykładami +![Mechanizmy IPC — szczegóły: Pipe, Shared Memory, Socket](img/q9_ipc_details.png) + **Pipe (potok anonimowy)** — jednokierunkowy strumień bajtów w pamięci jądra. Tylko między procesem-rodzicem a potomkiem (fork). Klasyczny przykład Unix: $ ls -la | grep ".txt" | wc -l - Jak to działa wewnętrznie: - ┌────────┐ write() ┌─────────────┐ read() ┌────────┐ - │ ls │──────────→│ bufor jądra │──────────→│ grep │ - │ stdout │ fd[1] │ (4 KB) │ fd[0] │ stdin │ - └────────┘ └─────────────┘ └────────┘ - Proces A pisze do fd[1], Proces B czyta z fd[0]. - Jądro buforuje dane. Gdy bufor pełny → write() blokuje. - Kod C: int fd[2]; pipe(fd); // tworzy potok: fd[0]=read, fd[1]=write @@ -268,14 +229,6 @@ Procesy mają **izolowane** przestrzenie adresowe — nie mogą bezpośrednio cz **Shared Memory (pamięć współdzielona)** — najszybszy IPC. OS mapuje ten sam region pamięci fizycznej do obu procesów. Zero kopiowania — oba procesy czytają/piszą bezpośrednio. ALE: wymaga synchronizacji (semafor/mutex). - ┌───────────┐ ┌───────────┐ - │ Proces A │ │ Proces B │ - │ │ │ │ - │ strona 7 ─┼──→ RAM ←─┼─ strona 3 │ ← ta sama ramka fizyczna! - │ │ ramka 42 │ │ - └───────────┘ └───────────┘ - Bez kopiowania — A pisze, B widzi od razu. - Kod C (POSIX): int fd = shm_open("/my_shm", O_CREAT|O_RDWR, 0666); ftruncate(fd, 4096); @@ -285,22 +238,14 @@ Procesy mają **izolowane** przestrzenie adresowe — nie mogą bezpośrednio cz **Message Queue (kolejka wiadomości)** — strukturalne wiadomości w jądrze. Asynchroniczna: nadawca wrzuca, odbiorca pobiera z kolejki kiedy chce. Typ wiadomości pozwala filtrować. + Kod C: Proces A: msgsnd(qid, &msg, size, 0) // wyślij wiadomość Proces B: msgrcv(qid, &msg, size, typ, 0) // odbierz (filtruj typ) - Przewaga nad pipe: wiele nadawców/odbiorców, filtrowanie typów, wiadomość ma granice (pipe to surowy strumień bajtów). **Socket** — dwukierunkowa komunikacja, działa lokalnie (Unix domain) i przez sieć (TCP/UDP). Najbardziej uniwersalny mechanizm IPC. - ┌──────────┐ TCP/IP ┌──────────┐ - │ Klient │←────────→│ Serwer │ sieciowy (różne maszyny) - └──────────┘ └──────────┘ - - ┌──────────┐ Unix ┌──────────┐ - │ Proces A │←────────→│ Proces B │ lokalny (ten sam host) - └──────────┘ socket └──────────┘ /tmp/app.sock - **Signal (sygnał)** — asynchroniczne powiadomienie od jądra/procesu. Przesyła TYLKO numer (nie dane). Użycie: obsługa Ctrl+C (SIGINT), zabijanie procesów (SIGKILL), powiadomienie o zdarzeniu. kill(pid, SIGUSR1); // wyślij sygnał SIGUSR1 do procesu @@ -309,14 +254,13 @@ Procesy mają **izolowane** przestrzenie adresowe — nie mogą bezpośrednio cz #### Porównanie mechanizmów IPC - Mechanizm Kierunek Szybkość Zastosowanie - ────────────────────────────────────────────────────────── - Pipe jednokier. średnia ls | grep - Named Pipe jednokier. średnia demon → klient - Shared Memory dwukier. najszybsza video, bazy danych - Message Queue dwukier. średnia wieloproducentowe - Socket dwukier. wolna (sieć) klient-serwer - Signal jednokier. natychmiast. powiadomienia +![Porównanie mechanizmów IPC — tabela](img/q9_ipc_table.png) + +> **Mnemonik KOMUNIKACJI — „PNMSSS" (6 mechanizmów IPC):** +> **P**iotrek **N**ie **M**a **S**iedmiu **S**tarych **S**karpet = **P**ipe, **N**amed pipe, **M**essage queue, **S**hared memory, **S**ocket, **S**ignal. +> Szybkość: **Shared Memory > Pipe ≈ MsgQueue > Socket** (sieciowy najwolniejszy). +> Zapamiętaj: **„Shared = zero kopii"** — najszybszy bo oba procesy piszą do tej samej ramki RAM. +> **Pipe = rura z wodą** (jednokierunkowa), **Socket = telefon** (dwukierunkowy, też przez sieć). --- @@ -330,142 +274,71 @@ Gdy wątki (lub procesy z shared memory) współdzielą dane, pojawiają się 4 Wynik programu zależy od losowej kolejności operacji wątków. Źródło: operacja „czytaj-modyfikuj-zapisz" nie jest atomowa. - Przykład: konto bankowe, saldo = 1000 zł - Wątek A: wpłata 500 zł Wątek B: wypłata 200 zł - - BEZ synchronizacji (błąd!): - ───────────────────────────────────────────────────────── - Czas Wątek A Wątek B - ───────────────────────────────────────────────────────── - t1 czytaj saldo → 1000 - t2 czytaj saldo → 1000 - t3 saldo = 1000 + 500 = 1500 - t4 saldo = 1000 - 200 = 800 - t5 zapisz saldo ← 1500 - t6 zapisz saldo ← 800 - ───────────────────────────────────────────────────────── - Wynik: 800 zł (powinno być 1300!) ← wypłata nadpisała wpłatę! +![Wyścig — dwa przykłady](img/q9_race_condition.png) Poprawka — mutex: lock(mutex); saldo = saldo + kwota; // sekcja krytyczna unlock(mutex); - Z mutex: A blokuje → czyta 1000 → pisze 1500 → B blokuje → czyta 1500 → pisze 1300. ✓ - #### Problem 2 — Zakleszczenie (Deadlock) Dwa lub więcej wątków czekają na siebie nawzajem — żaden nie może kontynuować. System „zamiera". - Klasyczny scenariusz: 2 wątki, 2 mutexy - ───────────────────────────────────────────────────────── - Wątek A: Wątek B: - lock(mutex1) ✓ ←trzyma lock(mutex2) ✓ ←trzyma - lock(mutex2) ⏳ ←czeka! lock(mutex1) ⏳ ←czeka! - ───────────────────────────────────────────────────────── - A czeka na mutex2 (B go trzyma), B czeka na mutex1 (A go trzyma). - → DEADLOCK — żaden nie odpuści! - - Diagram cyklu: - ┌──────────┐ czeka na ┌──────────┐ - │ Wątek A │───────────→│ Mutex 2 │ - │ trzyma │ │ trzyma │ - │ Mutex 1 │←───────────│ Wątek B │ - └──────────┘ czeka na └──────────┘ +![Zakleszczenie — scenariusz i cykl oczekiwania](img/q9_deadlock_scenario.png) **Warunki Coffmana** — 4 warunki konieczne deadlocka (WSZYSTKIE muszą zachodzić): - 1. Mutual Exclusion — zasób wyłączny (tylko 1 wątek) - 2. Hold and Wait — trzymaj zasób, czekaj na kolejny - 3. No Preemption — nie można zabrać zasobu siłą - 4. Circular Wait — cykliczne oczekiwanie (A→B→...→A) - - Strategie zapobiegania (złam jeden warunek): - ──────────────────────────────────────────────────── - Warunek Jak złamać Przykład - ──────────────────────────────────────────────────── - Mutual Exclusion Zrób zasób współdzielony Read-write lock - Hold and Wait Bierz WSZYSTKIE naraz lock(m1, m2) atomowo - No Preemption Pozwól na timeout/trylock pthread_mutex_trylock() - Circular Wait Porządek liniowy zamków Zawsze m1 przed m2 - ──────────────────────────────────────────────────── - - Najczęstsza strategia: PORZĄDEK LINIOWY (Circular Wait). - Zasada: numeruj mutexy, zawsze blokuj w rosnącej kolejności. - Jeśli mutex1 < mutex2 → ZAWSZE lock(mutex1) przed lock(mutex2). +![Warunki Coffmana — strategie zapobiegania](img/q9_coffman_strategies.png) #### Problem 3 — Zagłodzenie (Starvation) Wątek nigdy nie dostaje zasobu, bo inni ciągle go wyprzedzają. Nie jest deadlockiem (inni się wykonują). Przykład: 10 wątków, wątek niskopriorytetowy nigdy nie dostaje CPU bo wysoko priorytetowe ciągle dominują. - Rozwiązanie: aging (starzenie) — priorytet rośnie z czasem oczekiwania. - Po 100 ms bez CPU: priorytet +1, po 200 ms: +2, itd. - W końcu nawet najniższy wątek dostanie CPU. - #### Problem 4 — Inwersja priorytetów (Priority Inversion) Wątek wysokopriorytetowy (H) czeka na mutex trzymany przez niskopriorytetowy (L), a średniopriorytetowy (M) blokuje L. Efekt: H czeka na M (mimo wyższego priorytetu!). - Priorytet: H > M > L - ───────────────────────────────────────────────── - Czas L M H - ───────────────────────────────────────────────── - t1 lock(mutex) - t2 (gotowy, wypycha L!) - t3 pracuje... (czeka na mutex!) - t4 pracuje... (CZEKA — bo M blokuje L) - t5 gotowy - t6 unlock(mutex) (wreszcie!) - ───────────────────────────────────────────────── - H czekał, dopóki M nie skończył, mimo że H > M! +![Zagłodzenie i inwersja priorytetów](img/q9_starvation_priority.png) - Rozwiązanie: Priority Inheritance Protocol. - L dziedziczy priorytet H (tymczasowo L=H), więc M nie może wypchać L. - Mars Pathfinder (1997) — klasyczny bug priority inversion w kosmosie! +> **Mnemonik SYNCHRONIZACJI — „WZZI" (4 problemy):** +> **W**szystkie **Z**egarki **Z**atrzymały się **I**naczej = **W**yścig, **Z**akleszczenie, **Z**agłodzenie, **I**nwersja priorytetów. +> Coffman — **„MHNC"**: **M**uszę **H**amować, **N**ie **C**ofam = **M**utual exclusion, **H**old-and-wait, **N**o preemption, **C**ircular wait. Złam JEDEN = brak deadlocka. +> Najłatwiej złamać **Circular Wait** → numeruj mutexy, blokuj ROSNĄCO. +> Wyścig → **mutex**. Zakleszczenie → **porządek zamków**. Zagłodzenie → **aging**. Inwersja → **priority inheritance**. --- ### Klasyczne problemy synchronizacji -![Producent-konsument z buforem cyklicznym](img/producer_consumer.png) +![Klasyczne problemy synchronizacji — 3 panele](img/q9_classic_problems.png) #### Producent-Konsument (Bounded Buffer) n producentów wrzuca elementy do bufora o ograniczonej pojemności, m konsumentów pobiera. Bufor pełny → producent czeka. Bufor pusty → konsument czeka. +![Producent-konsument z buforem cyklicznym](img/producer_consumer.png) + Rozwiązanie z semaforami: - ───────────────────────────────────────────────── semaphore mutex = 1; // wyłączny dostęp do bufora semaphore empty = N; // ile wolnych slotów (początkowo N) semaphore full = 0; // ile pełnych slotów (początkowo 0) Producent: Konsument: - P(empty) // czekaj na P(full) // czekaj na - // wolny slot // pełny slot - P(mutex) // wejdź do P(mutex) // wejdź do - // sek. kryt. // sek. kryt. - wstaw(elem) elem = pobierz() - V(mutex) // wyjdź V(mutex) // wyjdź - V(full) // +1 pełny V(empty) // +1 wolny - ───────────────────────────────────────────────── + P(empty) P(full) + P(mutex) P(mutex) + wstaw(elem) elem = pobierz() + V(mutex) V(mutex) + V(full) V(empty) - Bufor (N=4): - ┌────┬────┬────┬────┐ - │ A │ B │ │ │ ← full=2, empty=2 - └────┴────┴────┴────┘ - ↑ ↑ - konsument producent - - BŁĄD jeśli zamienimy kolejność P(empty) i P(mutex) w producencie: - Producent: P(mutex) → P(empty) ← bufor pełny → czeka z mutexem! - Konsument: P(full) → P(mutex) ← mutex zajęty → DEADLOCK! + UWAGA: kolejność P(empty/full) PRZED P(mutex)! + Odwrotnie (P(mutex) → P(empty)) = DEADLOCK! #### Czytelnicy-Pisarze (Readers-Writers) Wielu czytelników może czytać jednocześnie. Pisarz wymaga wyłącznego dostępu (ani czytelnicy, ani inni pisarze). Rozwiązanie (first readers-writers): - ───────────────────────────────────────────────── int readers = 0; mutex rw_mutex; // pisarz LUB pierwszy/ostatni czytelnik mutex count_mutex; // ochrona zmiennej readers @@ -474,15 +347,14 @@ Wielu czytelników może czytać jednocześnie. Pisarz wymaga wyłącznego dost lock(count_mutex) lock(rw_mutex) readers++ // PISZ (wyłączny) if (readers == 1) unlock(rw_mutex) - lock(rw_mutex) // 1. czytelnik blokuje pisarzy + lock(rw_mutex) unlock(count_mutex) // CZYTAJ (wielu jednocześnie!) lock(count_mutex) readers-- if (readers == 0) - unlock(rw_mutex) // ostatni odblokowuje pisarzy + unlock(rw_mutex) unlock(count_mutex) - ───────────────────────────────────────────────── Problem: pisarze mogą głodować (readers=0 nigdy nie zachodzi). Rozwiązanie: fairness — kolejka FIFO czytelników i pisarzy. @@ -498,34 +370,22 @@ Wielu czytelników może czytać jednocześnie. Pisarz wymaga wyłącznego dost Alternatywa: semafor(4) — max 4 filozofów próbuje jeść naraz → jeden widelec zawsze wolny → brak deadlocka. +> **Mnemonik KLASYCZNYCH PROBLEMÓW — „PCF":** +> **P**yszne **C**iastka **F**ilozofów = **P**roducent-konsument, **C**zytelnicy-pisarze, **F**ilozofowie. +> Producent-konsument: „P(empty) PRZED P(mutex)" — inaczej deadlock! +> Czytelnicy-pisarze: „wielu czyta, jeden pisze" — pisarze mogą głodować. +> Filozofowie: „jeden bierze odwrotnie" — łamie circular wait. + --- ### Mechanizmy synchronizacji — porównanie - Mechanizm Opis Kiedy używać - ────────────────────────────────────────────────────────────────── - Mutex Zamek: 1 wątek w sekcji Sekcja krytyczna - Semafor(n) Licznik: max n wątków Ograniczone zasoby - Monitor Obiekt z wbudowanym mutex Java synchronized - Cond. Variable wait()/signal() na warunek Producent-konsument - Spinlock Aktywne czekanie (busy-wait) Bardzo krótkie sekcje - RW Lock Wielu czytelników LUB 1 pisarz Bazy danych, cache - Barrier Czekaj aż wszyscy dotrą Obliczenia równoległe +![Mechanizmy synchronizacji — porównanie i mutex vs semafor vs spinlock](img/q9_sync_comparison.png) - Mutex vs Semafor: - ┌────────────────────────────────────────────────────────────┐ - │ Mutex = klucz do łazienki (1 osoba) │ - │ Semafor(3) = parking na 3 miejsca (3 samochody naraz) │ - │ Semafor(1) = mutex (szczególny przypadek) │ - └────────────────────────────────────────────────────────────┘ - - Mutex vs Spinlock: - ┌────────────────────────────────────────────────────────────┐ - │ Mutex: wątek ZASYPIA gdy czeka → OS go obudzi (koszt ~μs)│ - │ Spinlock: wątek KRĘCI się w pętli → marnuje CPU │ - │ Spinlock lepszy gdy sekcja < 1 μs (koszt uśpienia > spin)│ - │ Mutex lepszy gdy sekcja > 1 μs (nie marnuje CPU) │ - └────────────────────────────────────────────────────────────┘ +> **Mnemonik MECHANIZMÓW — „MSMCSBR":** +> **M**ała **S**owa **M**oże **C**zasem **S**pinać na **B**ardzo **R**ówno = **M**utex, **S**emafor, **M**onitor, **C**ond.Variable, **S**pinlock, **B**arrier, **R**W Lock. +> Reguła kciuka: sekcja **> 1 μs → MUTEX** (wątek zasypia). Sekcja **< 1 μs → SPINLOCK** (kręci się). **n wątków naraz → SEMAFOR(n)**. +> Mutex = klucz do łazienki (1 osoba). Semafor(3) = parking na 3 miejsca. Spinlock = obrotowe drzwi. ### Etymologia @@ -536,4 +396,12 @@ Wielu czytelników może czytać jednocześnie. Pisarz wymaga wyłącznego dost - **„Proces = mieszkanie, Wątek = pokój"** — każde mieszkanie ma adres (przestrzeń), pokoje dzielą kuchnię (heap) - Wątki szybsze bo nie trzeba zmieniać „mieszkania" (TLB flush) - **4 warunki Coffmana** zakleszczenia: złam jeden → brak deadlocka +- **„100, 10, 1000"** — tworzenie 100× szybsze, przełączanie 10×, komunikacja 1000× +- **PNMSSS** — 6 mechanizmów IPC (Pipe, Named pipe, Msg queue, Shared mem, Socket, Signal) +- **WZZI** — 4 problemy synchronizacji (Wyścig, Zakleszczenie, Zagłodzenie, Inwersja) +- **MHNC** — 4 warunki Coffmana (Mutual excl., Hold&wait, No preemption, Circular wait) +- **PCF** — 3 klasyczne problemy (Producent-konsument, Czytelnicy-pisarze, Filozofowie) +- **SRP** — prywatne części wątku (Stos, Rejestry, PC) +- **IBW → Proces, WSO → Wątek** — kiedy co stosować +\newpage diff --git a/pytania/questions/pytanie_09_backup.md b/pytania/questions/pytanie_09_backup.md new file mode 100644 index 0000000..58f223f --- /dev/null +++ b/pytania/questions/pytanie_09_backup.md @@ -0,0 +1,539 @@ +## PYTANIE 9: Procesy i wątki (SOI) + +**Budowa, szybkość, zastosowanie. Problemy komunikacji i synchronizacji.** + +--- + +### Tło pojęciowe — słowniczek + +**Proces (process)** — program w trakcie wykonania. Każdy proces ma własną, izolowaną przestrzeń adresową. System operacyjny zarządza procesami — tworzy, planuje (scheduling), kończy. Np. przeglądarka i edytor to osobne procesy. + +**Wątek (thread)** — lekka jednostka wykonania wewnątrz procesu. Wątki jednego procesu współdzielą pamięć (kod, dane, heap), ale mają własny stos i rejestry CPU. Tworzenie wątku jest ~100x szybsze niż procesu. + + Proces = mieszkanie (własny adres, izolacja) + Wątek = pokój w mieszkaniu (współdzielona kuchnia = heap) + + Cecha Proces Wątek + ───────────────────────────────────────── + Pamięć własna współdzielona + Tworzenie ~1-10 ms ~10-100 μs + Przełączanie wolne (TLB) szybkie (rejestry) + Komunikacja IPC/pipe bezpośrednia + Awaria izolowana może zabić proces + +--- + +**Przestrzeń adresowa (address space)** — zakres adresów pamięci wirtualnej dostępnych procesowi. Każdy proces widzi swoją „prywatną" pamięć, nawet jeśli fizycznie jest mapowana gdzieś indziej. + +**Segmenty pamięci procesu:** +- **TEXT** — kod maszynowy (read-only) +- **DATA** — zainicjalizowane zmienne globalne/statyczne +- **BSS** — niezainicjalizowane zmienne globalne (zerowane) +- **HEAP** — pamięć alokowana dynamicznie (malloc/new), rośnie w górę +- **STACK** — zmienne lokalne, adresy powrotu, rośnie w dół + + ┌──────────┐ wysoki adres + │ STACK ↓ │ + │ ... │ + │ HEAP ↑ │ + │ BSS │ + │ DATA │ + │ TEXT │ + └──────────┘ niski adres + +**PCB (Process Control Block)** — struktura danych w jądrze OS opisująca proces: PID, stan, rejestry CPU, tablice stron, otwarte pliki, priorytety. Przełączenie kontekstu = zapisanie PCB starego procesu i wczytanie nowego. + +**PID (Process ID)** — unikalny identyfikator procesu w systemie. Np. `PID 1` = init/systemd w Linux. + +**TID (Thread ID)** — unikalny identyfikator wątku. + +**Stany procesu:** NEW (tworzony) → READY (gotowy, czeka na CPU) ↔ RUNNING (wykonywany) → BLOCKED (czeka na I/O), TERMINATED (zakończony). Scheduler decyduje, który READY staje się RUNNING. + +--- + +**Przełączanie kontekstu (context switch)** — zapisanie stanu aktualnego procesu/wątku i wczytanie stanu następnego. Dla procesów kosztowne (wymaga TLB flush = unieważnienie cache translacji adresów). Dla wątków tańsze (ta sama przestrzeń adresowa = brak TLB flush). + +**TLB (Translation Lookaside Buffer)** — sprzętowy cache translacji adres wirtualny → fizyczny. Przy zmianie procesu TLB trzeba wyczyścić (flush), bo nowy proces ma inne mapowania. Koszt: ~1000 ns. Przy zmianie wątku — TLB zostaje (ten sam proces). + +--- + +**IPC (Inter-Process Communication)** — mechanizmy komunikacji między procesami. Konieczne, bo procesy mają izolowane przestrzenie adresowe i nie mogą czytać wzajemnej pamięci bezpośrednio. + +- **Pipe** — jednokierunkowy strumień bajtów (ls | grep foo). Anonimowy, tylko między spokrewnionymi procesami. +- **Named Pipe (FIFO)** — pipe z nazwą w systemie plików, mogą go używać niespokrewnione procesy. +- **Message Queue** — kolejka wiadomości w jądrze; asynchroniczna komunikacja. +- **Shared Memory** — wspólny region pamięci; najszybszy IPC (brak kopiowania), ale wymaga synchronizacji. +- **Socket** — komunikacja sieciowa lub lokalna (Unix domain socket). Uniwersalny, działa między maszynami. +- **Signal** — asynchroniczne powiadomienie (np. SIGKILL, SIGTERM). Ograniczony — przesyła tylko numer sygnału. + +--- + +**Wyścig (race condition)** — sytuacja, gdy wynik programu zależy od kolejności wykonania operacji przez wątki. Przykład: dwa wątki zwiększają x=0 o 1 → wynik może być 1 zamiast 2, bo oba czytają 0 zanim zapiszą. + + Wątek A: czytaj x(=0) → dodaj 1 → zapisz x(=1) + Wątek B: czytaj x(=0) → dodaj 1 → zapisz x(=1) + Wynik: x = 1 zamiast oczekiwanego 2! + +**Sekcja krytyczna (critical section)** — fragment kodu, który może być wykonywany przez najwyżej jeden wątek naraz. Chroni współdzielone zasoby przed race condition. + +**Zakleszczenie (deadlock)** — sytuacja, w której dwa lub więcej wątków czekają na siebie nawzajem i żaden nie może kontynuować. Jak dwa samochody na skrzyżowaniu — oba czekają, nikt nie jedzie. + + Wątek A: trzyma mutex1, czeka na mutex2 + Wątek B: trzyma mutex2, czeka na mutex1 + → Zakleszczenie! Żaden nie puści swojego. + +**Warunki Coffmana** — 4 warunki konieczne deadlocka (wszystkie muszą zachodzić jednocześnie): +1. **Mutual exclusion** — zasób jest wyłączny (tylko jeden wątek) +2. **Hold and wait** — trzymaj zasób, czekaj na kolejny +3. **No preemption** — nie można zabrać zasobu siłą +4. **Circular wait** — cykliczne oczekiwanie (A→B→C→A) +Złam jeden = brak deadlocka. + +**Zagłodzenie (starvation)** — wątek nigdy nie dostaje zasobu, bo inni ciągle go wyprzedzają (np. nisko priorytetowy wątek przy high-priority scheduling). + +--- + +**Mutex (MUTual EXclusion)** — zamek na sekcję krytyczną. Tylko jeden wątek może go „zamknąć" (lock); reszta czeka (sleep). Tryb: lock → sekcja krytyczna → unlock. + +**Semafor (semaphore)** — uogólniony mutex z licznikiem. Semafor binarny (0/1) = mutex. Semafor zliczający (n) — pozwala n wątkom jednocześnie. P() = probeer (zmniejsz), V() = verhoog (zwiększ). + + semafor(3): 3 wątki mogą wejść naraz + P() → counter-- (jeśli 0 → czekaj) + V() → counter++ (obudź czekającego) + +**Monitor** — wysokopoziomowy mechanizm synchronizacji. Obiekt z mutexem wbudowanym — tylko jeden wątek może wykonywać metody monitora. Java: `synchronized`. + +**Condition Variable** — pozwala wątkowi czekać (wait) na spełnienie warunku i być obudzonym (signal/notify) przez inny wątek. Używane z mutexem. + +**Spinlock** — zamek, w którym wątek aktywnie czeka w pętli (busy-wait) zamiast zasypiać. Szybki dla bardzo krótkich sekcji krytycznych (~ns), marnotrawny dla dłuższych. + +**Read-Write Lock** — pozwala wielu czytelnikom jednocześnie LUB jednemu pisarzowi. Optymalizacja dla scenariuszy z dużo odczytów i rzadkimi zapisami. + +**Barrier** — punkt synchronizacji: wszystkie wątki muszą dotrzeć do bariery, zanim którykolwiek może kontynuować. Użyteczna w obliczeniach równoległych (np. po każdej iteracji). + +--- + +### Budowa procesu + +Proces = program w trakcie wykonania + cały jego kontekst. Składa się z: + +**Pamięć (oddzielna przestrzeń adresowa):** + + ┌──────────┐ wysoki adres + │ STACK ↓ │ zmienne lokalne, adresy powrotu (każdy wątek ma WŁASNY) + │ ... │ + │ HEAP ↑ │ malloc/new — dynamiczna alokacja (współdzielony między wątkami) + │ BSS │ zmienne globalne niezainicjalizowane (zerowane) + │ DATA │ zmienne globalne zainicjalizowane + │ TEXT │ kod maszynowy (read-only, współdzielony) + └──────────┘ niski adres + +**PCB (Process Control Block)** — struktura w jądrze OS opisująca proces: + + PCB = { PID, stan (READY/RUNNING/BLOCKED), rejestry CPU, + tablice stron, otwarte pliki, priorytety, statystyki } + +Przełączenie kontekstu = zapisanie PCB starego procesu → wczytanie PCB nowego. + +**Stany procesu:** NEW → READY ↔ RUNNING → BLOCKED → TERMINATED. +Scheduler decyduje, który READY staje się RUNNING. + +### Budowa wątku + +Wątek = lekka jednostka wykonania WEWNĄTRZ procesu. + +**Współdzielone** z innymi wątkami procesu: TEXT, DATA, BSS, HEAP, otwarte pliki, PID. +**Prywatne** (każdy wątek ma własne): stos (stack), rejestry CPU, program counter (PC), TID. + + Proces P (PID=42) + ┌────────────────────────────────────────┐ + │ TEXT │ DATA │ BSS │ HEAP │ ← współdzielone + ├────────┴────────┴───────┴──────────────┤ + │ Wątek 1: [stos₁] [rejestry₁] [PC₁] │ ← prywatne + │ Wątek 2: [stos₂] [rejestry₂] [PC₂] │ + │ Wątek 3: [stos₃] [rejestry₃] [PC₃] │ + └────────────────────────────────────────┘ + +Kluczowa różnica: proces ma CAŁĄ przestrzeń adresową, wątek to tylko kontekst wykonania (stos + rejestry) w ramach tej przestrzeni. + +### Szybkość — porównanie ilościowe + +| Operacja | Proces | Wątek | Dlaczego różnica? | +|-------------------|------------------|---------------------|--------------------------------------| +| Tworzenie | ~1–10 ms | ~10–100 μs (100x) | Proces: nowa przestrzeń adresowa, tablice stron, kopiowanie struktur jądra. Wątek: tylko nowy stos + wpis w schedulerze. | +| Przełączanie | ~1000–5000 ns | ~100–500 ns (10x) | Proces: TLB flush (unieważnienie cache adresów). Wątek: te same tablice stron, TLB zostaje. | +| Komunikacja | ~μs (IPC) | ~ns (pamięć) | Proces: kopiowanie przez jądro (pipe/socket). Wątek: bezpośredni zapis/odczyt heapu. | +| Zakończenie | wolne | szybkie | Proces: zwolnienie przestrzeni adresowej, zamknięcie plików. Wątek: zwolnienie stosu. | + +Konkretny przykład tworzenia (Linux): + + fork() (nowy proces): ~1-5 ms → kopiowanie tablic stron (copy-on-write) + pthread_create() (wątek): ~50 μs → alokacja stosu (~8 MB) + wpis w schedulerze + +Przełączanie kontekstu (benchmarki): + + Proces → Proces: ~3000 ns (TLB flush + cache cold) + Wątek → Wątek: ~300 ns (TLB ciepły, ta sama pamięć) + Zysk: ~10x szybciej + +### Zastosowanie — kiedy proces, kiedy wątek? + +**Procesy — stosuj gdy:** +- **Izolacja jest krytyczna** — awaria jednego nie zabija reszty + - Przeglądarka Chrome: każda karta = osobny proces. Crash Flash w jednej karcie nie zabija reszty. + - Serwer: każde połączenie = fork() → klient nie może uszkodzić serwera (Apache pre-fork MPM) +- **Bezpieczeństwo** — procesy nie widzą nawzajem pamięci + - Sandboxing: proces renderujący PDFa nie ma dostępu do pamięci procesu z hasłami +- **Wieloprogramowość** — różne programy (edytor + kompilator + przeglądarka) +- **Fork-exec** — klasyczny model Unix: fork() + exec() → nowy program + +**Wątki — stosuj gdy:** +- **Współdzielenie danych** — wątki czytają/piszą ten sam heap bez kopiowania + - Serwer WWW: wątki obsługujące requesty współdzielą cache w pamięci (nginx worker threads) + - Gra: wątek renderujący i wątek fizyki czytają ten sam świat gry +- **Szybkość tworzenia/przełączania** — potrzeba wielu lekkich zadań + - Thread pool: 8 wątków obsługuje tysiące zadań (zamiast tysiąca procesów) +- **Obliczenia równoległe** — podział pracy na rdzenie CPU + - Mnożenie macierzy: każdy wątek liczy fragment wyniku + - Rendering: każdy wątek renderuje część klatki +- **Responsywność UI** — wątek główny obsługuje interfejs, wątek tła liczy/pobiera dane + +**Typowe scenariusze w praktyce:** + + Scenariusz Proces czy wątek? Dlaczego? + ────────────────────────────────────────────────────────────── + Serwer WWW (Apache pre-fork) Proces izolacja klientów + Serwer WWW (nginx) Wątek/async szybkość, cooperacja + Przeglądarka (karty) Proces crash isolation + Przeglądarka (JS + rendering) Wątek współdzielony DOM + Gra (fizyka + rendering) Wątek współdzielony świat + Kompilacja wieloplikowa Proces (make -j8) izolacja, prostota + Baza danych (zapytania) Wątek współdzielony cache + Microservices Proces (kontener) izolacja, deployment + +### Porównanie zbiorcze + +| Cecha | Proces | Wątek | +|-----------------|------------------|----------------------| +| Przestrzeń addr | Własna, izolowana| Współdzielona | +| Tworzenie | ~1-10 ms | ~10-100 μs | +| Przełączanie | Wolne (TLB flush)| Szybkie (rejestry) | +| Komunikacja | IPC (pipe, shm) | Współdzielona pamięć | +| Izolacja | Pełna | Brak | +| Awaria | Nie zabija innych | Może zabić cały proces| +| Zastosowanie | Izolacja, bezpieczeństwo | Wydajność, współdzielenie | + +### Problemy komunikacji + +Procesy mają **izolowane** przestrzenie adresowe — nie mogą bezpośrednio czytać/pisać wzajemnej pamięci. Komunikacja wymaga pośrednictwa jądra OS (IPC). Wątki mają odwrotny problem: współdzielą pamięć, więc komunikacja jest trywialna, ale wymaga synchronizacji. + +![Mechanizmy IPC — porównanie wizualny](img/ipc_mechanisms.png) + +**Problem 1 — Overhead kopiowania (procesy).** Większość mechanizmów IPC wymaga kopiowania danych: proces A → jądro → proces B (2 kopie!). Przy dużych danych (np. klatki wideo 4K = 24 MB) to kosztowne. + +**Problem 2 — Synchronizacja dostępu (wątki).** Wątki komunikują się przez wspólny heap, ale muszą pilnować, by nie pisać jednocześnie w to samo miejsce (race condition). + +**Problem 3 — Blokowanie.** IPC może być synchroniczne (blokujące — nadawca czeka aż odbiorca przeczyta) lub asynchroniczne (nieblokujące — nadawca idzie dalej). Wybór wpływa na wydajność i złożoność kodu. + +#### Mechanizmy IPC z przykładami + +**Pipe (potok anonimowy)** — jednokierunkowy strumień bajtów w pamięci jądra. Tylko między procesem-rodzicem a potomkiem (fork). Klasyczny przykład Unix: + + $ ls -la | grep ".txt" | wc -l + + Jak to działa wewnętrznie: + ┌────────┐ write() ┌─────────────┐ read() ┌────────┐ + │ ls │──────────→│ bufor jądra │──────────→│ grep │ + │ stdout │ fd[1] │ (4 KB) │ fd[0] │ stdin │ + └────────┘ └─────────────┘ └────────┘ + Proces A pisze do fd[1], Proces B czyta z fd[0]. + Jądro buforuje dane. Gdy bufor pełny → write() blokuje. + + Kod C: + int fd[2]; + pipe(fd); // tworzy potok: fd[0]=read, fd[1]=write + if (fork() == 0) { // potomek + close(fd[1]); // zamknij pisanie + read(fd[0], buf, n); // czytaj od rodzica + } else { // rodzic + close(fd[0]); // zamknij czytanie + write(fd[1], "hello", 5); // pisz do potomka + } + +**Named Pipe (FIFO)** — jak pipe, ale ma nazwę w systemie plików. Niespokrewnione procesy mogą go używać: + + $ mkfifo /tmp/moj_potok # stwórz FIFO + $ echo "dane" > /tmp/moj_potok & # proces A pisze + $ cat /tmp/moj_potok # proces B czyta → "dane" + +**Shared Memory (pamięć współdzielona)** — najszybszy IPC. OS mapuje ten sam region pamięci fizycznej do obu procesów. Zero kopiowania — oba procesy czytają/piszą bezpośrednio. ALE: wymaga synchronizacji (semafor/mutex). + + ┌───────────┐ ┌───────────┐ + │ Proces A │ │ Proces B │ + │ │ │ │ + │ strona 7 ─┼──→ RAM ←─┼─ strona 3 │ ← ta sama ramka fizyczna! + │ │ ramka 42 │ │ + └───────────┘ └───────────┘ + Bez kopiowania — A pisze, B widzi od razu. + + Kod C (POSIX): + int fd = shm_open("/my_shm", O_CREAT|O_RDWR, 0666); + ftruncate(fd, 4096); + char *ptr = mmap(NULL, 4096, PROT_READ|PROT_WRITE, MAP_SHARED, fd, 0); + sprintf(ptr, "dane z procesu A"); // Proces A pisze + // Proces B: shm_open + mmap → czyta ptr → "dane z procesu A" + +**Message Queue (kolejka wiadomości)** — strukturalne wiadomości w jądrze. Asynchroniczna: nadawca wrzuca, odbiorca pobiera z kolejki kiedy chce. Typ wiadomości pozwala filtrować. + + Proces A: msgsnd(qid, &msg, size, 0) // wyślij wiadomość + Proces B: msgrcv(qid, &msg, size, typ, 0) // odbierz (filtruj typ) + + Przewaga nad pipe: wiele nadawców/odbiorców, filtrowanie typów, + wiadomość ma granice (pipe to surowy strumień bajtów). + +**Socket** — dwukierunkowa komunikacja, działa lokalnie (Unix domain) i przez sieć (TCP/UDP). Najbardziej uniwersalny mechanizm IPC. + + ┌──────────┐ TCP/IP ┌──────────┐ + │ Klient │←────────→│ Serwer │ sieciowy (różne maszyny) + └──────────┘ └──────────┘ + + ┌──────────┐ Unix ┌──────────┐ + │ Proces A │←────────→│ Proces B │ lokalny (ten sam host) + └──────────┘ socket └──────────┘ /tmp/app.sock + +**Signal (sygnał)** — asynchroniczne powiadomienie od jądra/procesu. Przesyła TYLKO numer (nie dane). Użycie: obsługa Ctrl+C (SIGINT), zabijanie procesów (SIGKILL), powiadomienie o zdarzeniu. + + kill(pid, SIGUSR1); // wyślij sygnał SIGUSR1 do procesu + $ kill -9 1234 // wyślij SIGKILL (nie do przechwycenia) + signal(SIGINT, handler); // zarejestruj handler dla Ctrl+C + +#### Porównanie mechanizmów IPC + + Mechanizm Kierunek Szybkość Zastosowanie + ────────────────────────────────────────────────────────── + Pipe jednokier. średnia ls | grep + Named Pipe jednokier. średnia demon → klient + Shared Memory dwukier. najszybsza video, bazy danych + Message Queue dwukier. średnia wieloproducentowe + Socket dwukier. wolna (sieć) klient-serwer + Signal jednokier. natychmiast. powiadomienia + +--- + +### Problemy synchronizacji + +Gdy wątki (lub procesy z shared memory) współdzielą dane, pojawiają się 4 fundamentalne problemy: + +![Ilustracja zakleszczenia — cykl oczekiwania](img/deadlock_illustration.png) + +#### Problem 1 — Wyścig (Race Condition) + +Wynik programu zależy od losowej kolejności operacji wątków. Źródło: operacja „czytaj-modyfikuj-zapisz" nie jest atomowa. + + Przykład: konto bankowe, saldo = 1000 zł + Wątek A: wpłata 500 zł Wątek B: wypłata 200 zł + + BEZ synchronizacji (błąd!): + ───────────────────────────────────────────────────────── + Czas Wątek A Wątek B + ───────────────────────────────────────────────────────── + t1 czytaj saldo → 1000 + t2 czytaj saldo → 1000 + t3 saldo = 1000 + 500 = 1500 + t4 saldo = 1000 - 200 = 800 + t5 zapisz saldo ← 1500 + t6 zapisz saldo ← 800 + ───────────────────────────────────────────────────────── + Wynik: 800 zł (powinno być 1300!) ← wypłata nadpisała wpłatę! + + Poprawka — mutex: + lock(mutex); + saldo = saldo + kwota; // sekcja krytyczna + unlock(mutex); + + Z mutex: A blokuje → czyta 1000 → pisze 1500 → B blokuje → czyta 1500 → pisze 1300. ✓ + +#### Problem 2 — Zakleszczenie (Deadlock) + +Dwa lub więcej wątków czekają na siebie nawzajem — żaden nie może kontynuować. System „zamiera". + + Klasyczny scenariusz: 2 wątki, 2 mutexy + ───────────────────────────────────────────────────────── + Wątek A: Wątek B: + lock(mutex1) ✓ ←trzyma lock(mutex2) ✓ ←trzyma + lock(mutex2) ⏳ ←czeka! lock(mutex1) ⏳ ←czeka! + ───────────────────────────────────────────────────────── + A czeka na mutex2 (B go trzyma), B czeka na mutex1 (A go trzyma). + → DEADLOCK — żaden nie odpuści! + + Diagram cyklu: + ┌──────────┐ czeka na ┌──────────┐ + │ Wątek A │───────────→│ Mutex 2 │ + │ trzyma │ │ trzyma │ + │ Mutex 1 │←───────────│ Wątek B │ + └──────────┘ czeka na └──────────┘ + +**Warunki Coffmana** — 4 warunki konieczne deadlocka (WSZYSTKIE muszą zachodzić): + + 1. Mutual Exclusion — zasób wyłączny (tylko 1 wątek) + 2. Hold and Wait — trzymaj zasób, czekaj na kolejny + 3. No Preemption — nie można zabrać zasobu siłą + 4. Circular Wait — cykliczne oczekiwanie (A→B→...→A) + + Strategie zapobiegania (złam jeden warunek): + ──────────────────────────────────────────────────── + Warunek Jak złamać Przykład + ──────────────────────────────────────────────────── + Mutual Exclusion Zrób zasób współdzielony Read-write lock + Hold and Wait Bierz WSZYSTKIE naraz lock(m1, m2) atomowo + No Preemption Pozwól na timeout/trylock pthread_mutex_trylock() + Circular Wait Porządek liniowy zamków Zawsze m1 przed m2 + ──────────────────────────────────────────────────── + + Najczęstsza strategia: PORZĄDEK LINIOWY (Circular Wait). + Zasada: numeruj mutexy, zawsze blokuj w rosnącej kolejności. + Jeśli mutex1 < mutex2 → ZAWSZE lock(mutex1) przed lock(mutex2). + +#### Problem 3 — Zagłodzenie (Starvation) + +Wątek nigdy nie dostaje zasobu, bo inni ciągle go wyprzedzają. Nie jest deadlockiem (inni się wykonują). Przykład: 10 wątków, wątek niskopriorytetowy nigdy nie dostaje CPU bo wysoko priorytetowe ciągle dominują. + + Rozwiązanie: aging (starzenie) — priorytet rośnie z czasem oczekiwania. + Po 100 ms bez CPU: priorytet +1, po 200 ms: +2, itd. + W końcu nawet najniższy wątek dostanie CPU. + +#### Problem 4 — Inwersja priorytetów (Priority Inversion) + +Wątek wysokopriorytetowy (H) czeka na mutex trzymany przez niskopriorytetowy (L), a średniopriorytetowy (M) blokuje L. Efekt: H czeka na M (mimo wyższego priorytetu!). + + Priorytet: H > M > L + ───────────────────────────────────────────────── + Czas L M H + ───────────────────────────────────────────────── + t1 lock(mutex) + t2 (gotowy, wypycha L!) + t3 pracuje... (czeka na mutex!) + t4 pracuje... (CZEKA — bo M blokuje L) + t5 gotowy + t6 unlock(mutex) (wreszcie!) + ───────────────────────────────────────────────── + H czekał, dopóki M nie skończył, mimo że H > M! + + Rozwiązanie: Priority Inheritance Protocol. + L dziedziczy priorytet H (tymczasowo L=H), więc M nie może wypchać L. + Mars Pathfinder (1997) — klasyczny bug priority inversion w kosmosie! + +--- + +### Klasyczne problemy synchronizacji + +![Producent-konsument z buforem cyklicznym](img/producer_consumer.png) + +#### Producent-Konsument (Bounded Buffer) + +n producentów wrzuca elementy do bufora o ograniczonej pojemności, m konsumentów pobiera. Bufor pełny → producent czeka. Bufor pusty → konsument czeka. + + Rozwiązanie z semaforami: + ───────────────────────────────────────────────── + semaphore mutex = 1; // wyłączny dostęp do bufora + semaphore empty = N; // ile wolnych slotów (początkowo N) + semaphore full = 0; // ile pełnych slotów (początkowo 0) + + Producent: Konsument: + P(empty) // czekaj na P(full) // czekaj na + // wolny slot // pełny slot + P(mutex) // wejdź do P(mutex) // wejdź do + // sek. kryt. // sek. kryt. + wstaw(elem) elem = pobierz() + V(mutex) // wyjdź V(mutex) // wyjdź + V(full) // +1 pełny V(empty) // +1 wolny + ───────────────────────────────────────────────── + + Bufor (N=4): + ┌────┬────┬────┬────┐ + │ A │ B │ │ │ ← full=2, empty=2 + └────┴────┴────┴────┘ + ↑ ↑ + konsument producent + + BŁĄD jeśli zamienimy kolejność P(empty) i P(mutex) w producencie: + Producent: P(mutex) → P(empty) ← bufor pełny → czeka z mutexem! + Konsument: P(full) → P(mutex) ← mutex zajęty → DEADLOCK! + +#### Czytelnicy-Pisarze (Readers-Writers) + +Wielu czytelników może czytać jednocześnie. Pisarz wymaga wyłącznego dostępu (ani czytelnicy, ani inni pisarze). + + Rozwiązanie (first readers-writers): + ───────────────────────────────────────────────── + int readers = 0; + mutex rw_mutex; // pisarz LUB pierwszy/ostatni czytelnik + mutex count_mutex; // ochrona zmiennej readers + + Czytelnik: Pisarz: + lock(count_mutex) lock(rw_mutex) + readers++ // PISZ (wyłączny) + if (readers == 1) unlock(rw_mutex) + lock(rw_mutex) // 1. czytelnik blokuje pisarzy + unlock(count_mutex) + // CZYTAJ (wielu jednocześnie!) + lock(count_mutex) + readers-- + if (readers == 0) + unlock(rw_mutex) // ostatni odblokowuje pisarzy + unlock(count_mutex) + ───────────────────────────────────────────────── + + Problem: pisarze mogą głodować (readers=0 nigdy nie zachodzi). + Rozwiązanie: fairness — kolejka FIFO czytelników i pisarzy. + +#### Ucztujący filozofowie (Dining Philosophers) + +5 filozofów siedzi przy okrągłym stole, między każdą parą 1 widelec. Filozofowie myślą lub jedzą. Jedzenie wymaga 2 widelców (lewego i prawego). Naiwne rozwiązanie: każdy bierze lewy → prawy → deadlock (wszyscy trzymają lewy, czekają na prawy). + + Rozwiązanie — złamanie cyklu: + Filozofowie 0-3: bierz lewy, potem prawy. + Filozof 4: bierz PRAWY, potem lewy. ← łamie circular wait! + + Alternatywa: semafor(4) — max 4 filozofów próbuje jeść naraz + → jeden widelec zawsze wolny → brak deadlocka. + +--- + +### Mechanizmy synchronizacji — porównanie + + Mechanizm Opis Kiedy używać + ────────────────────────────────────────────────────────────────── + Mutex Zamek: 1 wątek w sekcji Sekcja krytyczna + Semafor(n) Licznik: max n wątków Ograniczone zasoby + Monitor Obiekt z wbudowanym mutex Java synchronized + Cond. Variable wait()/signal() na warunek Producent-konsument + Spinlock Aktywne czekanie (busy-wait) Bardzo krótkie sekcje + RW Lock Wielu czytelników LUB 1 pisarz Bazy danych, cache + Barrier Czekaj aż wszyscy dotrą Obliczenia równoległe + + Mutex vs Semafor: + ┌────────────────────────────────────────────────────────────┐ + │ Mutex = klucz do łazienki (1 osoba) │ + │ Semafor(3) = parking na 3 miejsca (3 samochody naraz) │ + │ Semafor(1) = mutex (szczególny przypadek) │ + └────────────────────────────────────────────────────────────┘ + + Mutex vs Spinlock: + ┌────────────────────────────────────────────────────────────┐ + │ Mutex: wątek ZASYPIA gdy czeka → OS go obudzi (koszt ~μs)│ + │ Spinlock: wątek KRĘCI się w pętli → marnuje CPU │ + │ Spinlock lepszy gdy sekcja < 1 μs (koszt uśpienia > spin)│ + │ Mutex lepszy gdy sekcja > 1 μs (nie marnuje CPU) │ + └────────────────────────────────────────────────────────────┘ + +### Etymologia + +**Proces** — łac. „processus" = posuwanie się naprzód. **Wątek (Thread)** — metafora nitki wykonania (jak nić Ariadny). **Mutex** — portmanteau MUTual EXclusion. **Semafor** — Dijkstra (1965); od semaforów kolejowych; P() = hol. „proberen" (próbować), V() = hol. „verhogen" (podnosić). **Coffman** — Edward Coffman Jr. et al. (1971): 4 warunki konieczne deadlocka. **Deadlock (zakleszczenie)** — jak zablokowane koła zębate. **IPC** — Inter-Process Communication. + +### Jak zapamiętać + +- **„Proces = mieszkanie, Wątek = pokój"** — każde mieszkanie ma adres (przestrzeń), pokoje dzielą kuchnię (heap) +- Wątki szybsze bo nie trzeba zmieniać „mieszkania" (TLB flush) +- **4 warunki Coffmana** zakleszczenia: złam jeden → brak deadlocka + diff --git a/pytania/questions/pytanie_20_30.md b/pytania/questions/pytanie_20_30.md index f1a3b69..3d896f8 100644 --- a/pytania/questions/pytanie_20_30.md +++ b/pytania/questions/pytanie_20_30.md @@ -8,8 +8,7 @@ **Dane strumieniowe (streaming data)** — ciągły, potencjalnie nieskończony przepływ zdarzeń (events) przychodzących w czasie rzeczywistym. Przykłady: kliknięcia użytkowników, odczyty sensorów IoT, transakcje bankowe, logi serwerów. W odróżnieniu od danych wsadowych (batch): nie możesz „poczekać na wszystkie" — musisz analizować na bieżąco. - Batch: [cały zbiór] → analiza → wynik (minuty/godziny) - Streaming: event → event → event → ...→ analiza ciągła (ms/sekundy) +![Batch vs Streaming](img/q20_batch_vs_streaming.png) **Strumień (stream)** — abstrakcja: nieograniczona (unbounded) sekwencja zdarzeń, każde ze stemplem czasowym. Musisz przetwarzać „w locie" — nie mieścisz wszystkiego w pamięci. @@ -20,10 +19,6 @@ - **Processing Time** — moment GDY system przetwarza zdarzenie (np. o 14:00:07) - Różnica wynika z opóźnień sieciowych. Zdarzenia mogą przychodzić **out-of-order** (pozamiejscowe). - Zdarzenie A (event time 14:00:01) → dociera o 14:00:05 - Zdarzenie B (event time 14:00:03) → dociera o 14:00:04 - B dociera PRZED A, mimo że A było wcześniej! - **Watermark** — znacznik postępu: „z dużym prawdopodobieństwem nie przyjdą już zdarzenia z event time < W". Pozwala systemowi zdecydować, kiedy zamknąć okno i wyemitować wynik. Zdarzenia po watermarku = „late data" (spóźnione). --- @@ -32,19 +27,14 @@ **Tumbling window (okno przerzutne)** — stały rozmiar, rozłączne. Np. „liczba kliknięć co 5 minut". - |---5min---|---5min---|---5min---| - [events A] [events B] [events C] ← 0 nakładania - **Sliding window (okno przesuwne)** — stały rozmiar + krok przesunięcia. Nakładają się. Np. „średnia z 10 min, co 1 min". - |----10min----| - |----10min----| - |----10min----| ← nakładanie - **Session window (okno sesji)** — dynamiczny rozmiar, oparte na aktywności. Nowa sesja po przerwie (gap). Np. „sesja użytkownika: od pierwszego kliknięcia do 30 min nieaktywności". **Global window** — jedno okno na cały strumień. Trigger decyduje kiedy wyemitować wynik. +![4 typy okien](img/q20_window_types.png) + --- **True streaming vs Micro-batch:** @@ -61,15 +51,10 @@ **Algorytmy strumieniowe (probabilistyczne):** -**HyperLogLog** — estymacja liczby unikalnych elementów (cardinality). Zużywa O(1) pamięci (~1.5 KB) niezależnie od liczby elementów. Błąd ~2%. - - 100 mln unikalnych URL-i → HyperLogLog odpowiada "~100 mln ± 2%" - Pamięć: 1.5 KB zamiast ~800 MB (hash set) +**HyperLogLog** — estymacja liczby unikalnych elementów (cardinality). Zużywa O(1) pamięci (~1.5 KB) niezależnie od liczby elementów. Błąd ~2%. Np. 100 mln unikalnych URL-i → HyperLogLog: ~100 mln ± 2%, pamięć: 1.5 KB zamiast ~800 MB (hash set). **Count-Min Sketch** — estymacja częstości elementów. Macierz d×w z hashami. Gwarantuje overestimates (nigdy nie zaniży). O(1) per query/update. - "Ile razy pojawił się IP 192.168.1.1?" → CMS: ~4523 (± ε·N) - **Reservoir Sampling** — równomierne próbkowanie k elementów ze strumienia o nieznanym rozmiarze n. Każdy element ma szansę k/n. O(k) pamięci. **Late data strategies:** @@ -82,7 +67,7 @@ ### Rozwiązania analityczne — przegląd -Rozwiązanie analityczne na strumieniu = odpowiedź na pytanie biznesowe w czasie rzeczywistym, gdy dane przychodzą ciągle i nie można ich wszystkich zapamiętać. Trzy filary: **windowing** (jak grupować), **platformy** (gdzie przetwarzać), **algorytmy probabilistyczne** (jak liczyć w O(1) pamięci). +Rozwiązanie analityczne na strumieniu = odpowiedź na pytanie biznesowe w czasie rzeczywistym, gdy dane przychodzą ciągle i nie można ich wszystkich zapamiętać. Dwa filary: **windowing** (jak grupować zdarzenia w skończone porcje) i **platformy** (gdzie i jak przetwarzać strumień). --- @@ -90,7 +75,9 @@ Rozwiązanie analityczne na strumieniu = odpowiedź na pytanie biznesowe w czasi Problem: strumień jest nieskończony, a analiza wymaga skończonej porcji danych. Okno wyodrębnia fragment strumienia do obliczenia agregatu (count, sum, avg, max). -**4 typy okien:** +**Analogia:** Strumień to rzeka. Nie możesz zmierzyć „wszystkiej wody w rzece", ale możesz nabierać wiadra (okna) i mierzyć każde z nich. + +**4 typy okien — TSSG (Tumbling, Sliding, Session, Global):** | Okno | Rozmiar | Nakładanie | Kiedy użyć | |------|---------|------------|------------| @@ -99,27 +86,79 @@ Problem: strumień jest nieskończony, a analiza wymaga skończonej porcji danyc | **Session** | dynamiczny (gap) | rozłączne per klucz | sesje użytkowników: „aktywność do 30 min przerwy" | | **Global** | cały strumień | — | trigger-based: „emituj po N zdarzeniach" | -**Przykład — Tumbling window (fraud detection):** +--- - Strumień transakcji bankowych, okno = 1 minuta: - [14:00–14:01] → 3 transakcje z karty X → OK - [14:01–14:02] → 47 transakcji z karty X → ALERT! (>10 = podejrzane) +**Tumbling Window — szczegółowo:** -**Przykład — Sliding window (monitoring SLA):** +Stały rozmiar, zero nakładania. Każde zdarzenie trafia do DOKŁADNIE jednego okna. - Okno = 5 min, krok = 1 min (nakładanie): - t=14:05 → avg latency [14:00–14:05] = 120ms ✓ - t=14:06 → avg latency [14:01–14:06] = 340ms ✗ → alert +**Przykład — fraud detection (tumbling 1 min):** + +![Tumbling window — fraud detection](img/q20_tumbling_fraud.png) + +**Przykład — raporty sprzedaży (tumbling 1h):** + +Sklep internetowy, okno = 1 godzina: [10:00-11:00] zamówienia: 142, suma: 28 400 zł | [11:00-12:00] zamówienia: 89, suma: 17 800 zł | [12:00-13:00] zamówienia: 201, suma: 40 200 zł (szczyt). Dashboard aktualizuje się CO GODZINĘ. + +--- + +**Sliding Window — szczegółowo:** + +Stały rozmiar + krok przesunięcia. Okna NAKŁADAJĄ SIĘ → jedno zdarzenie trafia do WIELU okien. + +**Dlaczego nakładanie jest przydatne?** Tumbling 5 min: nagły skok dokładnie na granicę okien → podzielony na dwa okna, żaden nie pokaże pełnego obrazu. Sliding 5 min / 1 min: okna co minutę → skok ZAWSZE widoczny w co najmniej jednym pełnym oknie. + +**Przykład — monitoring SLA (sliding 5 min, krok 1 min):** + +![Sliding window — monitoring SLA](img/q20_sliding_sla.png) + +--- + +**Session Window — szczegółowo:** + +Rozmiar DYNAMICZNY, zależy od aktywności. Gap (przerwa) definiuje koniec sesji. + +![Session window — sesje użytkowników](img/q20_session_users.png) + +**Przykład — analiza zachowań użytkowników (e-commerce, gap = 30 min):** +- Sesja Anny: strona główna → kategoria → produkt → koszyk → ZAKUP (22 min, 5 stron, konwersja: TAK) +- Sesja Boba: strona główna → kategoria → wychodzi (3 min, 2 strony, konwersja: NIE) +- Metryka: avg session duration, pages/session, conversion rate + +--- + +**Global Window:** + +Jedno okno na cały strumień. Potrzebujesz **triggera** aby wyemitować wynik (np. „emituj po każdych 1000 zdarzeń" lub „emituj co 1 minutę" → de facto tumbling na processing time). + +--- + +**Event Time vs Processing Time + Watermark:** + +![Event Time vs Processing Time + Watermark](img/q20_event_vs_processing_time.png) -**Event Time vs Processing Time:** - Okna na **event time** = poprawne biznesowo (kiedy zdarzenie faktycznie nastąpiło) - Okna na **processing time** = prostsze, ale podatne na out-of-order delivery - **Watermark** rozwiązuje problem: „z prawdopodobieństwem ~100% nie przyjdą zdarzenia z event time < W" +**Watermark krok po kroku:** e1 (event=14:00:01, arrives=14:00:02) → watermark=14:00:01. e2 (event=14:00:03, arrives=14:00:03) → watermark=14:00:03. e3 (event=14:00:02, arrives=14:00:04) → LATE! (event < watermark). Okno [14:00:00-14:00:05]: watermark przeszedł 14:00:05 → okno ZAMKNIĘTE. Strategia: allowed lateness +2s → jeszcze przyjmij, albo drop/side output. + +**Mnemonik okien — „TSSG jak Termometr/Suwak/Sesja/Glob":** T = Tumbling = Termometr (stały odczyt co X minut, ZERO nakładania). S = Sliding = Suwak (przesuwasz suwak = nakładanie). S = Session = Sesja (zależy od aktywności użytkownika). G = Global = Glob (jedna wielka kula = cały strumień). Albo: „TeSt SG" — Testujesz Strumieniowe Grupowanie. + +--- + +**Late data — 4 strategie (mnemonik „DRAS"):** + +![Late data — 4 strategie DRAS](img/q20_late_data_strategies.png) + --- ### Rozwiązanie 2 — Platformy przetwarzania strumieniowego +**Analogia:** Masz fabrykę (dane strumieniowe). Potrzebujesz maszyny do ich przetwarzania. Trzy główne maszyny na rynku — każda z inną filozofią. + +![Ekosystem streamingu](img/q20_streaming_ecosystem.png) + | Cecha | Kafka Streams | Apache Flink | Spark Streaming | |-------|---------------|--------------|-----------------| | **Model** | event-by-event | event-by-event | micro-batch (~100ms) | @@ -128,106 +167,107 @@ Problem: strumień jest nieskończony, a analiza wymaga skończonej porcji danyc | **Exactly-once** | tak (Kafka TXN) | tak (checkpointing) | tak (WAL) | | **State management** | RocksDB local | RocksDB + checkpoints | in-memory/external | | **Okna** | tumbling, sliding, session | wszystkie + custom | tumbling, sliding | +| **Skalowalność** | liczba partycji Kafka | auto-scaling klaster | klaster Spark | | **Use case** | transformacja Kafka → Kafka | złożona analityka real-time | ETL z ekosystemem Spark | -**True streaming vs Micro-batch — co wybrać?** +--- - True streaming (Flink, Kafka Streams): - Latencja: < 10 ms ← trade fraud, click tracking - Semantyka: event-by-event - Złożoność: wyższa (watermarks, state, exactly-once) +**True streaming vs Micro-batch — wizualnie:** - Micro-batch (Spark Streaming): - Latencja: ~100 ms – sekundy - Semantyka: mini-batch (prostsza, batch-like API) - Ekosystem: Spark SQL, MLlib → łatwa integracja z ML +![True Streaming vs Micro-batch](img/q20_true_vs_microbatch.png) -**Architektura Lambda vs Kappa:** +**Kiedy co wybrać? (drzewo decyzyjne)** - Lambda: [batch layer (Spark)] + [speed layer (Flink)] → merge - Dwa systemy, dwa kody — skomplikowane ale pewne - - Kappa: [streaming only (Flink/Kafka)] → replay z Kafka - Jeden system — prostsze, ale replay = I/O koszt +![Drzewo decyzyjne wyboru platformy](img/q20_decision_tree.png) --- -### Rozwiązanie 3 — Algorytmy probabilistyczne (Sketches) +**Kafka Streams — architektura (library, nie klaster!):** -Problem: na strumieniu nie zmieścisz WSZYSTKICH danych w pamięci. Algorytmy probabilistyczne dają przybliżone odpowiedzi w **O(1) pamięci** z gwarantowanym błędem. +![Kafka Streams — architektura](img/q20_kafka_streams_arch.png) + + Kluczowe: NIE potrzebujesz osobnego klastra! + Skalujesz = dodajesz instancje swojej aplikacji. + Partycje Kafki automatycznie rozdzielane między instancje. -| Algorytm | Pytanie | Pamięć | Błąd | Przykład | -|----------|---------|--------|------|----------| -| **HyperLogLog** | „Ile unikalnych?" | ~1.5 KB | ~2% | unique visitors na stronie | -| **Count-Min Sketch** | „Ile razy element X?" | d×w counters | ε·N (overestimate) | częstość IP w logach | -| **Bloom Filter** | „Czy element X był?" | m bitów | false positives, 0 false neg | cache: „czy URL widziany?" | -| **Reservoir Sampling** | „Losowa próbka k z n?" | O(k) | dokładna (nie przybliżona) | próbka logów do debugowania | -| **T-Digest** | „Jaki percentyl?" | O(δ) | <1% na ogonach | p99 latency monitorowanie | +**Przykład — Kafka Streams (zliczanie kliknięć co 5 min):** -**Dlaczego HyperLogLog zużywa O(1)?** - - Idea: hashuj każdy element, licz pozycję pierwszego bitu 1. - Jeśli widzisz dużo zer na początku → prawdopodobnie dużo unikalnych. - - 100 mln unikalnych URL-i: - - HashSet: ~800 MB pamięci (8 bajtów × 10⁸) - - HyperLogLog: 1.5 KB pamięci, odpowiedź ~100 mln ± 2% - - Oszczędność: 500 000× mniej pamięci! - -**Count-Min Sketch — jak działa:** - - Macierz d wierszy × w kolumn (np. 5 × 2048), d funkcji hashowych. - Insert("X"): dla każdego hash h_i, zwiększ cell[i][h_i("X")]++ - Query("X"): min over i of cell[i][h_i("X")] - Gwarancja: nigdy nie ZANIŻY (overestimate, no underestimate) + StreamsBuilder builder = new StreamsBuilder(); + + builder.stream("clicks") // input topic + .groupByKey() // grupuj po user_id + .windowedBy(TimeWindows.of(Duration.ofMinutes(5))) // tumbling 5 min + .count() // zlicz + .toStream() + .to("click-counts"); // output topic + + // Deploy = uruchom JAR. Skalujesz = uruchom więcej JARów. --- -### Rozwiązanie 4 — Obsługa opóźnień i spójność +**Apache Flink — architektura (klaster):** -**Problem late data:** zdarzenie z event time 14:00:01 przychodzi o 14:00:30, gdy okno [14:00–14:05] już zamknięte. +![Apache Flink — architektura](img/q20_flink_arch.png) -| Strategia | Opis | Trade-off | -|-----------|------|-----------| -| **Drop** | odrzuć spóźnione | proste, ale utrata danych | -| **Allowed lateness** | czekaj dodatkowy czas (np. +5 min) | wyższe zużycie pamięci | -| **Recompute** | przelicz okno z nowym zdarzeniem | poprawne ale kosztowne | -| **Side output** | przekieruj late events do osobnego strumienia | elastyczne, ręczna analiza | +**Exactly-once w Flinku (algorytm Chandy-Lamport):** -**Exactly-once semantics** — gwarancja, że każde zdarzenie wpływa na wynik dokładnie raz, mimo awarii: -- **At-most-once** — mogą zginąć (szybkie, proste) -- **At-least-once** — mogą się zduplikować (retry) -- **Exactly-once** — żadnych duplikatów ani strat (checkpoint + transakcje, kosztowne) +Exactly-once w Flinku działa algorytmem Chandy-Lamport: Job Manager co N ms wysyła „barrier" przez graf przetwarzania. Gdy operator otrzyma barrier → zapisuje stan do checkpointu (HDFS/S3). Po awarii → odtworzenie z ostatniego checkpointu. - Flink: distributed snapshots (algorytm Chandy-Lamport) → checkpoint co N ms - Kafka Streams: transakcje Kafka (idempotent producer + TX coordinator) - Spark: WAL (Write-Ahead Log) + idempotent sinks +**Przykład — Flink (średnia temperatura sensorów co 10s):** + + DataStream readings = env.addSource(kafkaConsumer); + + readings + .keyBy(r -> r.sensorId) // grupuj po sensorze + .window(TumblingEventTimeWindows.of(Time.seconds(10))) + .aggregate(new AvgTemperature()) // średnia w oknie + .addSink(new AlertSink()); // sink: alert jeśli > 50°C + + // Flink obsługuje event time natywnie → poprawne wyniki + // mimo out-of-order zdarzeń z sensorów --- -### Rozwiązanie 5 — CEP (Complex Event Processing) +**Spark Streaming — architektura (micro-batch):** -Wykrywanie złożonych wzorców w strumieniach zdarzeń. Reguły definiowane deklaratywnie. +![Spark Streaming — architektura](img/q20_spark_streaming_arch.png) - Pattern: "Jeśli 3 nieudane logowania z tego samego IP w ciągu 5 minut, - a potem udane logowanie z INNEGO IP → alert: konto przejęte" +ZALETA: ten sam kod co batch Spark → łatwa migracja. WADA: latencja = rozmiar micro-batcha (min ~100ms). - Flink CEP: - Pattern.begin("fails") - .where(event -> !event.isSuccess()) - .times(3).within(Time.minutes(5)) - .next("success") - .where(event -> event.isSuccess()) +--- -Zastosowania: fraud detection, cybersecurity, monitoring IoT, trading algorytmiczny. +**Architektura Lambda vs Kappa — wizualnie:** + +![Lambda vs Kappa — architektura](img/q20_lambda_vs_kappa.png) + +Lambda: 2 systemy, 2 bazy kodu, 2× utrzymanie. Kappa: 1 system, 1 kod. Potrzebujesz przeliczyć historię? → Replay z Kafka od początku. WADA: replay = dużo I/O, Kafka musi trzymać historię. + +**Porównanie Lambda vs Kappa:** + +![Lambda vs Kappa — porównanie](img/q20_lambda_kappa_table.png) + +--- + +**Exactly-once — porównanie mechanizmów:** + +![Exactly-Once — mechanizmy](img/q20_exactly_once.png) + +**Mnemonik platform — „KFS: Kawa, Filiżanka, Szklanka":** K = Kafka Streams = Kawa (szybka, w filiżance = w Twojej JVM), F = Flink = Filiżanka (elegancka, pełna = true streaming + state), S = Spark = Szklanka (duża, paczka = micro-batch). Albo: „Kafka = lekki (library), Flink = fastest, Spark = safest (batch-like)". + +--- ### Etymologia -**Flink** — niem. „flink" = zwinny/szybki (TU Berlin, 2014). **Spark** — „iskra"; Matei Zaharia (UC Berkeley, 2012). **HyperLogLog** — Philippe Flajolet et al. (2007); „Hyper" = ulepszenie LogLog; „LogLog" = zużywa log(log(n)) pamięci. **Count-Min Sketch** — Cormode & Muthukrishnan (2005); „sketch" = probabilistyczny skrót danych. **Reservoir Sampling** — Jeffrey Vitter (1985); „reservoir" = stały zbiornik prób. **Watermark** — znacznik postępu czasu zdarzeń w strumieniu. +**Flink** — niem. „flink" = zwinny/szybki (TU Berlin, 2014). **Spark** — „iskra"; Matei Zaharia (UC Berkeley, 2012). **Kafka** — Franz Kafka; Jay Kreps (LinkedIn, 2011) — „system zoptymalizowany do pisania" (jak Kafka pisarz). **Watermark** — „znak wodny" — znacznik postępu czasu zdarzeń w strumieniu, analogicznie do znaku wodnego na papierze: niewidoczny, ale wyznacza granicę. **Tumbling** — ang. „koziołkowanie" — okna „przewracają się" jedno po drugim bez przerwy. **Sliding** — ang. „przesuwanie" — okno przesuwa się po osi czasu. **Lambda/Kappa** — litery greckie; Lambda = dwie ścieżki (λ ma dwie nóżki), Kappa = jedna ścieżka (κ prostsza). ### Jak zapamiętać -- **4 okna: „TSSG"** — Tumbling, Sliding, Session, Global -- **Flink = szybki (true streaming)**, Spark = safe (micro-batch) -- **HyperLogLog = „ile unikalnych?" z kilobajtem pamięci** +- **4 okna: „TSSG"** — Tumbling (stały, rozłączne), Sliding (stały+krok, nakładające), Session (gap), Global (trigger) +- **TSSG = „TeSt SG — Testujesz Strumieniowe Grupowanie"** +- **Tumbling = Termometr** (odczyt co X, zero nakładania), **Sliding = Suwak** (przesuwasz = nakładanie) +- **Late data: „DRAS"** — Drop, Recompute, Allowed lateness, Side output +- **Platformy: „KFS"** — Kafka Streams (library, lekki), Flink (fastest, true streaming), Spark (safest, micro-batch) +- **Lambda = λ = 2 nóżki = 2 ścieżki** (batch + speed), **Kappa = κ = 1 ścieżka** (streaming only) +- **Event time vs processing time** — „kiedy NAPRAWDĘ się stało" vs „kiedy system to ZOBACZYŁ" +- **Watermark** — „linia w piasku" = poniżej niej = już na pewno dotarło diff --git a/pytania/questions/pytanie_24.md b/pytania/questions/pytanie_24.md index bc882c5..eb1407a 100644 --- a/pytania/questions/pytanie_24.md +++ b/pytania/questions/pytanie_24.md @@ -14,11 +14,215 @@ **Bounding box (prostokąt ograniczający, bbox)** — prostokąt opisujący położenie obiektu. Zwykle: (x_min, y_min, x_max, y_max) lub (x_center, y_center, width, height). Przybliżenie — obiekty rzadko są prostokątne. -**Confidence (pewność)** — wynik 0-1 mówiący jak pewny jest detektor, że wykrył obiekt danej klasy. Zwykle próg np. 0.5: detiekcje poniżej odrzucane. +**Confidence (pewność)** — wynik 0-1 mówiący jak pewny jest detektor, że wykrył obiekt danej klasy. Zwykle próg np. 0.5: detekcje poniżej odrzucane. --- -**Klasyfikator (classifier)** — model przypisujący etykietę do wejścia. Np. CNN trenowany na ImageNet: obraz → „kot" (+ prawdopodobieństwo). SAM nie lokalizuje — mówi tylko co jest na obrazie. Pytanie brzmi: jak z takiego modelu zbudować detektor? +**CNN (Convolutional Neural Network, konwolucyjna sieć neuronowa)** — typ sieci neuronowej zaprojektowany specjalnie do przetwarzania OBRAZÓW. Używany w KAŻDYM nowoczesnym detektorze (R-CNN, YOLO, SSD, DETR). Kluczowa idea: zamiast łączyć KAŻDY piksel z KAŻDYM neuronem (→ miliardy parametrów), CNN używa MAŁYCH filtrów (np. 3×3 piksele) przesuwanych po obrazie. Dzięki temu: +1. Mało parametrów (filtr 3×3 = 9 wag, niezależnie od rozmiaru obrazu) +2. Wykrywa lokalne wzorce (krawędzie, rogi, tekstury) +3. Inwariantność na przesunięcie (kot w lewym rogu = kot w prawym rogu) + + Dlaczego CNN a nie zwykła sieć neuronowa? + Obraz 224×224×3 = 150 528 pikseli. + Zwykła sieć (FC): 150 528 × 4096 neuronów = 616 MILIONÓW wag w 1 warstwie! + CNN: filtr 3×3×3 = 27 wag, przesuwany po CAŁYM obrazie → 27 wag zamiast 616M! + + Mnemonik: CNN = „Czytaj Nie Naraz" — nie bierzesz całego obrazu naraz, + tylko małe fragmenty (filtry 3×3), krok po kroku. + +**Konwolucja (convolution)** — podstawowa operacja CNN: mały filtr (macierz np. 3×3) przesuwa się po obrazie, w każdej pozycji mnoży element-po-elemencie z fragmentem obrazu i sumuje → jedna liczba na wyjściu. Wynik = „feature mapa" — mapa pokazująca GDZIE na obrazie dany wzorzec jest obecny. + + Przykład liczbowy: + Fragment obrazu 3×3: Filtr 3×3: Wynik (1 piksel feature mapy): + [1 2 3] [-1 0 1] + [4 5 6] × [-1 0 1] = 1(-1)+2(0)+3(1)+4(-1)+5(0)+6(1)+7(-1)+8(0)+9(1) + [7 8 9] [-1 0 1] = (-1+0+3) + (-4+0+6) + (-7+0+9) = 6 + + Ten filtr wykrywa PIONOWE KRAWĘDZIE (liczy różnicę prawa-lewa strona). + Duży wynik (6) = silna krawędź. Wynik ≈ 0 = brak krawędzi. + Filtr przesuwa się po CAŁYM obrazie → cała mapa cech. + + Pseudokod konwolucji: + def convolve(image, filter_3x3): + output = zeros(image.height - 2, image.width - 2) + for y in range(1, image.height - 1): + for x in range(1, image.width - 1): + patch = image[y-1:y+2, x-1:x+2] # wycinek 3×3 + output[y-1][x-1] = sum(patch * filter) # iloczyn + suma + return output + +**Filtr / Kernel** — mała macierz wag (np. 3×3, 5×5) uczona AUTOMATYCZNIE podczas treningu. CNN ma WIELE filtrów — każdy uczy się wykrywać INNY wzorzec. 64 filtry w jednej warstwie → 64 map cech. + + KLUCZOWA RÓŻNICA: w HOG cechy projektuje CZŁOWIEK. + W CNN filtry uczy się SIEĆ SAMA — to główna przewaga deep learning! + + Warstwa conv z 64 filtrami 3×3: + Filtr 1: nauczył się wykrywać pionowe krawędzie + Filtr 2: nauczył się wykrywać poziome krawędzie + Filtr 3: nauczył się wykrywać rogi + ... + Filtr 64: jakiś inny wzorzec pomocny w rozpoznawaniu + +**Feature map (mapa cech)** — wynik zastosowania JEDNEGO filtra do obrazu. Jasne piksele = „tu jest ten wzorzec". 64 filtry → 64 map cech → tensor [H × W × 64]. Feature mapy to WEWNĘTRZNA REPREZENTACJA tego, co sieć „widzi" na obrazie. + + Hierarchia cech w CNN (każda warstwa coraz bardziej abstrakcyjna): + Warstwa 1: krawędzie, gradienty (jak HOG!) + Warstwa 2: rogi, proste tekstury + Warstwa 3: fragmenty obiektów (oko, koło, ucho) + Warstwa 4+: całe obiekty (twarz = oczy+nos+usta, samochód = koła+okna+dach) + + Mnemonik: „K-R-F-O" = „Każdy Rycerz Znajduje Obiekt" + (Krawędzie → Rogi → Fragmenty → Obiekty) + +**Pooling (łączenie / podpróbkowanie)** — warstwa ZMNIEJSZAJĄCA rozmiar feature mapy. Najczęstsza: **max pooling 2×2** — z każdego bloku 2×2 pikseli zachowaj MAKSIMUM. Wynik: mapa 2× mniejsza w każdym wymiarze (= 4× mniej pikseli), ale zachowuje najsilniejsze cechy. + + Feature map 4×4: Po Max Pool 2×2: + [1 3 | 2 1] [3 2] ← max(1,3,0,3)=3 max(2,1,1,2)=2 + [0 3 | 1 2] [4 3] ← max(0,4,1,2)=4 max(1,0,3,1)=3 + ───────────── + [0 4 | 1 0] Rozmiar: 4×4 → 2×2 (4× mniej danych!) + [1 2 | 3 1] Zachowane: najsilniejsze cechy z każdego bloku + + Dlaczego max pooling? + 1. Mniej pikseli = mniej obliczeń w następnych warstwach + 2. Większe „pole widzenia" (receptive field) — warstwa „widzi" większy fragment + 3. Odporność na małe przesunięcia: obiekt ±1px → ten sam max + +**Stride (krok)** — o ile pikseli filtr przesuwa się za jednym krokiem. Stride=1: co 1 piksel (wyjście duże). Stride=2: co 2 piksele (wyjście 2× mniejsze). Max pool 2×2 ze stride 2 = typowy pooling. + +**FC (Fully Connected layer, warstwa w pełni połączona)** — warstwa, w której KAŻDY neuron jest połączony z KAŻDYM wyjściem poprzedniej warstwy. W CNN zwykle na KOŃCU sieci: feature mapy (3D) → spłaszczone do wektora 1D → FC klasyfikuje. + + CNN: Conv → Pool → Conv → Pool → [Flatten] → FC(4096) → FC(1000) → "kot" + ↑ ↑ + spłaszcz 3D→1D 1000 klas (ImageNet) + + FC = „warstwa decyzyjna" — łączy cechy z CAŁEGO obrazu w jedną decyzję. + Mnemonik: FC = „Full Connection" — każdy z każdym, jak klasa każdy-z-każdym. + Problem FC: DUŻO parametrów (np. 25088 × 4096 = 102M wag w VGG-16!) + +**Forward pass (przejście w przód)** — JEDNO przetworzenie danych przez sieć od wejścia do wyjścia. Obraz wchodzi → przechodzi przez Conv, Pool, FC → wychodzi predykcja. Nie aktualizuje wag (to backward pass / backpropagation = uczenie). + + Forward pass CNN (czasy na GPU): + Jeden obraz przez ResNet-50: ~5ms + R-CNN: 2000 regionów × 5ms = 10 SEKUND (dlatego był wolny!) + Fast R-CNN: 1 forward pass cały obraz + ROI Pool = ~200ms (50× szybciej!) + +**ReLU (Rectified Linear Unit)** — funkcja aktywacji: f(x) = max(0, x). Przepuszcza wartości dodatnie, zeruje ujemne. Standard w CNN — stosowana PO KAŻDEJ warstwie konwolucyjnej. + + Wejście: [-3, 5, -1, 2, 0, -7, 4] + ReLU: [ 0, 5, 0, 2, 0, 0, 4] + + Dlaczego potrzebna? Bez ReLU sieć = seria mnożeń macierzy = JEDNA liniowa + transformacja → nie potrafi uchwycić złożonych wzorców. + ReLU dodaje NIELINIOWOŚĆ → sieć aproksymuje DOWOLNĄ funkcję. + +**Softmax** — funkcja na WYJŚCIU klasyfikatora: zamienia surowe wyniki (logits) na prawdopodobieństwa sumujące się do 1. + + Logits: [2.0, 1.0, 0.1] + Softmax: [0.66, 0.24, 0.10] ← e^2.0 / (e^2.0 + e^1.0 + e^0.1) ≈ 0.66 + Klasy: ["kot", "pies", "ryba"] + → „66% szans, że to kot" + +**Tensor** — wielowymiarowa tablica liczb. Uogólnienie wektora i macierzy. + + Skalar = 0D tensor: 5 + Wektor = 1D: [1, 2, 3] + Macierz = 2D: [[1,2],[3,4]] + Obraz RGB = 3D: [224 × 224 × 3] ← wysokość × szerokość × kanały + Batch obrazów = 4D: [32 × 224 × 224 × 3] ← 32 obrazy naraz + Wyjście YOLO = 3D: [7 × 7 × 30] ← siatka × predykcje + +**Architektura CNN — pełny przykład (AlexNet, wygrał ImageNet 2012):** + + Obraz [224×224×3] ← 150 528 wartości (piksele RGB) + ↓ Conv1: 96 filtrów 11×11, stride 4 + [55×55×96] ← 96 map cech, każda 55×55 + ↓ MaxPool 3×3, stride 2 + [27×27×96] + ↓ Conv2: 256 filtrów 5×5 + [27×27×256] + ↓ MaxPool → Conv3-5 → MaxPool + [6×6×256] = 9 216 liczb spłaszczonych + ↓ FC(4096) → FC(4096) → FC(1000) → Softmax + → "golden retriever" (klasa 207, pewność 0.89) + + ROZMIARY MALEJĄ: 224 → 55 → 27 → 13 → 6 (kompresja przestrzenna) + KANAŁY ROSNĄ: 3 → 96 → 256 → 384 → 256 (coraz więcej wyuczonych cech) + +--- + +**Backbone (kręgosłup / sieć bazowa)** — duża, pretrenowana sieć CNN (np. ResNet-50, VGG-16) używana jako „ekstraktor cech". Backbone przetwarza obraz → feature mapa. Na wierzch dodaje się GŁOWICĘ (head) specyficzną dla zadania. + + Analogia: backbone = SILNIK samochodu, head = KAROSERIA. + Ten sam silnik (ResNet) w różnych karoseriach: + Sedan → klasyfikacja: FC head → "kot" + SUV → detekcja: RPN + ROI Pool head → bbox + klasa + Pickup → segmentacja: dekoder head → maska pikseli + + Backbone PRETRENOWANY na ImageNet (miliony obrazów). + Head TRENOWANY od zera na konkretnym zadaniu (detekcja, segmentacja). + +**Detection head (głowa detekcyjna)** — warstwy dodane NA WIERZCH backbone'u. Predykują klasy obiektów + pozycje bbox. W Faster R-CNN: RPN + ROI Pool + FC. W YOLO: warstwy conv + wyjście S×S×(B×5+C). + +**ResNet, VGG, AlexNet — popularne backbone'y:** + + Sieć Rok Warstw Parametrów Top-5 ImageNet Innowacja + ───────────────────────────────────────────────────────────────────── + AlexNet 2012 8 60M 84.7% Pierwsza głęboka CNN + VGG-16 2014 16 138M 92.7% Małe filtry 3×3 + ResNet-50 2015 50 25M 96.4% Skip connections + + Mnemonik: A → V → R = „Architektura Bardzo Rezylientna" (2012 → 2014 → 2015) + + Skip connection (ResNet): y = F(x) + x + Wejście bloku DODAWANE do wyjścia → gradient nie zanika + → można trenować 50-152 warstw (bez skip: >20 warstw = DEGRADACJA!) + +**ImageNet** — ogromny zbiór danych: 14M obrazów, 1000 klas (pies, samolot, gitara...). Standard pretrenowania w computer vision. ILSVRC (coroczne zawody) — AlexNet wygrał 2012 → rewolucja deep learning. + +**Transfer learning (uczenie transferowe)** — weź sieć pretrenowaną na dużym zbiorze (ImageNet), użyj do INNEGO zadania (detekcja, segmentacja). Backbone „wie" jak wyglądają krawędzie i kształty — trzeba tylko nauczyć nowej głowicy. + + Krok po kroku: + 1. ResNet-50 pretrenowany na ImageNet (1000 klas, miliony obrazów) + 2. Odtnij warstwę FC (klasyfikujse 1000 klas ImageNet) ← WYRZUĆ + 3. Dodaj nową głowicę detekcji (bbox + 80 klas COCO) ← NOWA + 4. Trenuj głowicę na danych detekcyjnych (COCO/VOC) + 5. Opcjonalnie: fine-tune = odmroź backbone, ucz z MAŁYM learning rate + + Dlaczego działa? Cechy niskiego poziomu (krawędzie, tekstury) SĄ UNIWERSALNE. + Kot, samochód, twarz — wszystko ma krawędzie i tekstury! + +**Fine-tuning (dostrajanie)** — forma transfer learning: odmrażasz backbone i uczysz CAŁĄ sieć z MAŁYM learning rate, żeby subtelnie dopasować cechy do nowego zadania. + +**COCO (Common Objects in Context)** — benchmark detekcji: 330K obrazów, 80 klas (samochód, osoba, pies...), 1.5M bboxów. Standard oceny detektorów. + +**Pascal VOC (Visual Object Classes)** — starszy benchmark: 20 klas. Używany w oryginalnym YOLO i R-CNN. + +**mAP (mean Average Precision)** — główna metryka jakości detekcji. Łączy trafność klasy z trafnością lokalizacji. + + mAP@0.5: detekcja „trafna" jeśli IoU ≥ 0.5 (≥50% pokrycia z prawdą) + mAP@0.5:0.95: średnia po progach 0.5, 0.55, ..., 0.95 (dużo surowsza) + + Faster R-CNN (COCO): mAP ≈ 42% + YOLOv8-X (COCO): mAP ≈ 53% + +**End-to-end (od końca do końca)** — cała sieć trenowana jako JEDNOŚĆ, jeden loss, jeden trening. Przeciwieństwo: R-CNN miał ODDZIELNIE Selective Search + CNN + SVM = 3 osobne kroki. Faster R-CNN = end-to-end → komponenty uczą się WSPÓŁPRACOWAĆ → lepsze wyniki. + +**FPN (Feature Pyramid Network)** — technika łączenia feature map z RÓŻNYCH warstw backbone'u. Wczesne warstwy (wysoka rozdzielczość) → małe obiekty. Późne warstwy (niska rozdzielczość) → duże obiekty. FPN łączy obie → wykrywa obiekty WSZYSTKICH rozmiarów. + + Backbone (ResNet): + Warstwa 1: 56×56 → dużo detali, dobre dla MAŁYCH obiektów + Warstwa 2: 28×28 → średnie obiekty + Warstwa 3: 14×14 → duże obiekty + Warstwa 4: 7×7 → bardzo duże obiekty + + FPN: łączy top-down (7×7 → 14×14 → 28×28 → 56×56) + lateral connections + → predykcje na KAŻDYM poziomie → małe I duże obiekty! + +--- + +**Klasyfikator (classifier)** — model przypisujący etykietę do wejścia. Np. CNN trenowany na ImageNet: obraz → „kot" (+ prawdopodobieństwo). Klasyfikator nie mówi GDZIE jest obiekt — mówi tylko CO jest na obrazie. Pytanie brzmi: jak z takiego modelu zbudować detektor? **Sliding window (okno przesuwane)** — najprostsza metoda budowy detektora z klasyfikatora: wytnij prostokątny fragment obrazu (wiele rozmiarów, wiele pozycji), każdy fragment sklasyfikuj. Jeśli „pozytywny" → detekcja. Ekstremalnie wolne: tysiące fragmentów × klasyfikacja per fragment. @@ -118,18 +322,120 @@ 3. NMS (Non-Maximum Suppression) → usuń duplikaty 4. Wynik: lista bounding boxów z detekcjami pieszych -**Viola-Jones (2001)** — przełomowy detektor twarzy real-time. Kluczowe innowacje: -- **Haar features** — proste cechy prostokątne (jasne/ciemne regiony) -- **Integral Image** — obliczenie dowolnej sumy prostokąta w O(1)! -- **AdaBoost cascade** — kaskada klasyfikatorów: szybkie odrzucenie 99% okien w pierwszych etapach, szczegółowa analiza tylko obiecujących +**Viola-Jones (2001)** — przełomowy detektor twarzy w CZASIE RZECZYWISTYM. Trzy kluczowe innowacje wyjasnione szczegółowo: + +**Haar features (cechy Haarowe)** — najprostsze cechy obrazowe: prostokąty podzielone na jasną i ciemną część. Wartość cechy = (suma pikseli jasnych) − (suma pikseli ciemnych). Proste, ale skuteczne — wykrywają kontrasty typowe dla twarzy. + + Przykłady cech Haar: + Krawędź pionowa: Krawędź pozioma: Linia (3 prostokąty): + ┌──────┬──────┐ ┌────────────┐ ┌────┬──────┬────┐ + │JASNY │CIEMNY│ │ JASNY │ │CIEM│JASNY │CIEM│ + │ +Σ₁ │ -Σ₂ │ │ +Σ₁ │ │ -Σ₁│ +Σ₂ │ -Σ₃│ + │ │ │ ├────────────┤ │ │ │ │ + └──────┴──────┘ │ CIEMNY │ └────┴──────┴────┘ + wartość = Σ₁ − Σ₂ │ -Σ₂ │ wartość = Σ₂ − Σ₁ − Σ₃ + └────────────┘ + + Dlaczego działa na TWARZACH? + - Oczy CIEMNIEJSZE niż czoło → cecha "krawędź pozioma" daje dużą wartość + - Nos JAŚNIEJSZY niż policzki → cecha "linia pionowa" daje dużą wartość + - Twarz = charakterystyczna KOMBINACJA takich kontrastów! + + Ile cech? W oknie 24×24 pikseli: ponad 160 000 możliwych cech Haar + (różne rozmiary × różne pozycje). AdaBoost wybiera ~200 NAJLEPSZYCH. + +**Integral Image (obraz całkowy)** — precomputed tabela pozwalająca obliczyć sumę pikseli w DOWOLNYM prostokącie w O(1) — stały czas, niezależnie od rozmiaru! To dlatego Haar features liczą się tak szybko. + + Jak? Integral Image[x,y] = suma WSZYSTKICH pikseli od (0,0) do (x,y). + + Obraz oryginalny: Integral Image (sumy kumulatywne): + [1 2 3] [ 1 3 6] + [4 5 6] [ 5 12 21] + [7 8 9] [12 27 45] + + Chcemy sumę prostokąta (1,1)-(2,2) = piksele [5,6,8,9] = 28: + Z Integral Image: II[2,2] − II[0,2] − II[2,0] + II[0,0] + = 45 − 6 − 12 + 1 = 28 ✓ + + Zawsze 4 odczyty z tabeli → O(1)! + Czy prostokąt ma 4 piksele czy 4 MILIONY — czas TEN SAM! + Bez Integral Image: O(w×h) — suma 1000×1000 = milion operacji. + Z Integral Image: O(1) — 4 operacje. ZAWSZE. + + Pseudokod: + def integral_image(img): + II = zeros_like(img) + for y in range(H): + for x in range(W): + II[y][x] = img[y][x] + II[y-1][x] + II[y][x-1] - II[y-1][x-1] + return II + + def rect_sum(II, x1, y1, x2, y2): # O(1) zawsze! + return II[y2][x2] - II[y1-1][x2] - II[y2][x1-1] + II[y1-1][x1-1] + +**AdaBoost (Adaptive Boosting)** — algorytm uczenia maszynowego łączący wiele SŁABYCH klasyfikatorów w jeden SILNY. Słaby = niewiele lepszy od losowego (>50% trafień). AdaBoost iteracyjnie: +1. Trenuj słaby klasyfikator (np. 1 cecha Haar + próg: "czy wartość > 1200?") +2. Sprawdź, które przykłady ŹLE sklasyfikował +3. Nadaj źle sklasyfikowanym WIĘKSZĄ wagę → następny klasyfikator SKUPI się na nich +4. Powtórz 200× → suma ważona 200 słabych klasyfikatorów ≈ silny klasyfikator + + Intuicja: jak PANEL EKSPERTÓW, z których każdy zna się na JEDNEJ rzeczy. + Ekspert 1: "czy okolice oczu ciemne?" (trafność 55%) + Ekspert 2: "czy nos jaśniejszy niż policzki?" (trafność 60%) + Ekspert 3: "czy brwi ciemne?" (trafność 53%) + ... + 200 ekspertów razem → trafność >95%! + Mnemonik: AdaBoost = "ADAptacyjnie BOOSTuj" słabe modele do silnego. + +**Cascade (kaskada klasyfikatorów)** — genialna optymalizacja szybkości: zamiast sprawdzać WSZYSTKIE 200 cech na każdym oknie, użyj KASKADY etapów. Każdy etap = prosty klasyfikator, który szybko ODRZUCA "na pewno nie-twarz". + + Etap 1: 2 cechy → odrzuca 50% okien (czas: ~1 μs) + Etap 2: 10 cech → odrzuca 80% reszty (czas: ~5 μs) + Etap 3: 25 cech → odrzuca 90% reszty + ... + Etap 25: 200 cech → szczegółowa analiza (czas: ~100 μs) + + Sliding window: ~500 000 okien do sprawdzenia (różne pozycje × skale) + BEZ kaskady: 500 000 × 200 cech = WOLNO + Z kaskadą: 99% okien odrzuconych w etapach 1-3 (za ~5μs każde!) + Tylko 0.01% dochodzi do etapu 25 + → CAŁY obraz w ~30ms = 30+ fps = REAL-TIME! + + Mnemonik: kaskada = "SITO" — coraz drobniejsze oczka, + na początku odpada piach, na końcu zostaje ZŁOTO (twarz). + + Pseudokod kaskady: + def cascade_classify(window): + for stage in cascade_stages: # etap 1, 2, ..., 25 + score = stage.evaluate(window) # oblicz kilka cech Haar + if score < stage.threshold: # za niski wynik + return "NIE-TWARZ" # SZYBKIE odrzucenie! + return "TWARZ" # przeszło WSZYSTKIE etapy --- ![Ewolucja detektorów: R-CNN → Faster R-CNN → YOLO](img/rcnn_evolution.png) -**R-CNN family (two-stage detectors)** — dwuetapowe: najpierw generuj propozycje regionów, potem klasyfikuj każdy region. +**R-CNN family (two-stage detectors)** — dwuetapowe: najpierw generuj propozycje regionów, potem klasyfikuj każdy region. Nazwa: Region-based CNN. -**Czym jest „region proposal" (propozycja regionu)?** — prostokąt, w którym MOŻE BYĆ obiekt. Zamiast sprawdzać miliony pozycji okna (sliding window), algorytm propozycji generuje ~2000 „obiecujących" prostokątów. Jak? Metoda Selective Search analizuje kolory, tekstury i rozmiary → łączy podobne regiony → generuje kandydatów. +**Selective Search (wyszukiwanie selektywne)** — klasyczny algorytm (NIE sieć neuronowa!) generowania propozycji regionów. Zamiast MILIONÓW pozycji okna (sliding window), inteligentnie łączy podobne fragmenty obrazu i proponuje ~2000 prostokątów, w których MOGĄ być obiekty. + + Algorytm krok po kroku: + 1. Over-segmentation: podziel obraz na ~1000 małych regionów (superpixele) + (na podstawie koloru i tekstury — algorytm Felzenszwalb) + 2. Powtarzaj aż zostanie 1 region: + a) Znajdź 2 najbardziej PODOBNE sąsiednie regiony: + - podobny kolor? (histogram kolorów) + - podobna tekstura? (histogram gradientów) + - pasujący rozmiar? (preferuj łączenie MAŁYCH regionów) + b) Połącz je w jeden → zapamiętaj bounding box nowego regionu + 3. Zebrane bbox-y ze WSZYSTKICH kroków → ~2000 propozycji + + Sliding window: ~500 000 okien → 99.9% to "tło" → marnujesz czas + Selective Search: ~2 000 regionów → ~50% zawiera coś → 250× wydajniej + RPN (Faster R-CNN): ~300 propozycji → sieć neuronowa (najszybciej!) + +**Czym jest "region proposal" (propozycja regionu)?** — prostokąt, w którym MOŻE być obiekt. Dużo mniej niż sliding window (2000 zamiast milionów), ale każda propozycja ma WYSOKIE prawdopodobieństwo trafienia obiektu. **R-CNN (2014, Ross Girshick)** — pierwszy detektor oparty na CNN. Pipeline: @@ -145,26 +451,90 @@ Dlaczego tak wolno? Bo CNN liczy cechy na KAŻDYM wyciętym regionie OSOBNO, choć regiony się częściowo nakładają → redundantne obliczenia -**Fast R-CNN (2015)** — kluczowa optymalizacja: przepuść cały obraz przez CNN RAZ, uzyskaj „mapę cech" (feature map). Potem wytnij cechy regionów z tej mapy (ROI Pooling), zamiast odpalać CNN 2000 razy. +**Fast R-CNN (2015)** — kluczowa optymalizacja: przepuść cały obraz przez CNN RAZ, uzyskaj "mapę cech" (feature map). Potem wytnij cechy regionów z tej mapy (ROI Pooling), zamiast odpalać CNN 2000 razy. - Dlaczego „ROI Pooling"? ROI = Region of Interest. Regiony mają RÓŻNE rozmiary, - ale warstwa FC wymaga stałego. ROI Pooling dzieli region na siatkę np. 7×7 - i w każdej komórce bierze MAX → stały rozmiar wyjścia niezależnie od wejścia. +**ROI (Region of Interest, region zainteresowania)** — prostokątny fragment feature mapy odpowiadający propozycji regionu na oryginalnym obrazie. Np. Selective Search zaproponował bbox (100,50)-(200,150) na obrazie 800×600 → odpowiadający ROI na feature mapie (po redukcji 16× przez pooling) to mniej więcej (6,3)-(12,9). - CNN raz na obraz → feature map → ROI Pool 2000 regionów → FC → klasy + bbox - Przyspieszenie: ~2 sec/obraz (vs 50 sec w R-CNN) +**ROI Pooling (pooling regionu zainteresowania)** — operacja zamieniająca ROI o DOWOLNYM rozmiarze na tensor o STAŁYM rozmiarze (np. 7×7). Konieczne, bo warstwa FC wymaga stałego rozmiaru wejścia! -**Faster R-CNN (2015)** — ostatni krok: zastąp Selective Search (osobny algorytm) siecią neuronową! **RPN (Region Proposal Network)** — mała sieć przesuwana po feature mapie, która w KAŻDEJ pozycji predykuje: „czy tu jest obiekt?" + proponuje bbox. Wszystko w jednej sieci, end-to-end. + Problem: region 1 = 14×10 na feature mapie, region 2 = 8×6 → RÓŻNE! + Warstwa FC wymaga np. 7×7 → STAŁY rozmiar. + Rozwiązanie — ROI Pooling: + 1. Weź ROI (np. 14×10) z feature mapy + 2. Podziel go na siatkę 7×7 (= 7 wierszy × 7 kolumn) + Każda komórka obejmuje ok. 2×1.4 pikseli feature mapy + 3. W każdej komórce weź MAX (jak max pooling) + 4. Wynik: tensor 7×7 — STAŁY rozmiar niezależnie od oryginalnego ROI! + + Przykład (ROI Pool 2×2 dla prostoty): + ROI na feature mapie [4×4]: Po ROI Pool 2×2: + [1 3 | 2 1] [5 6] ← max(1,3,0,5)=5 max(2,1,1,6)=6 + [0 5 | 1 6] [7 9] ← max(0,4,7,2)=7 max(1,0,9,1)=9 + ───────────── + [0 4 | 1 0] + [7 2 | 9 1] + + Kluczowa sztuczka Fast R-CNN: + CNN raz na CAŁY obraz → JEDNA feature mapa → ROI Pool 2000 regionów z TEJ SAMEJ mapy + (zamiast 2000× odpalać CNN jak w R-CNN!) + Przyspieszenie: ~2 sec/obraz (vs 50 sec) → 25× szybciej! + + Pseudokod ROI Pooling: + def roi_pool(feature_map, roi_bbox, output_size=7): + roi = feature_map[roi_bbox] # wycinek z feature mapy + h, w = roi.shape + cell_h, cell_w = h // output_size, w // output_size + output = zeros(output_size, output_size) + for i in range(output_size): + for j in range(output_size): + cell = roi[i*cell_h:(i+1)*cell_h, j*cell_w:(j+1)*cell_w] + output[i][j] = max(cell) # max pooling w komórce + return output # stały rozmiar 7×7! + + CNN raz → feature map → ROI Pool 2000 regionów → FC → klasy + bbox + +**Bbox regression (regresja prostokąta ograniczającego)** — sieć predykuje nie bezpośrednie współrzędne bbox, ale PRZESUNIĘCIA (offsets) od propozycji: Δx, Δy (przesunięcie środka), Δw, Δh (zmiana szerokości/wysokości). + + Propozycja (z RPN/Selective Search): (x=100, y=80, w=60, h=90) ← przybliżone + Predykcja regresji: (Δx=+5, Δy=-3, Δw=+10, Δh=+5) + Ostateczny bbox: (x=105, y=77, w=70, h=95) ← dokładniejsze! + + Dlaczego offsets a nie współrzędne bezpośrednio? + Łatwiejsze zadanie! Sieć poprawia przybliżony prostokąt O TROCHĘ, + zamiast zgadywać lokalizację od zera. + Mnemonik: bbox regression = "GPS korekta" — masz przybliżoną pozycję, + poprawiasz o parę metrów w prawo i w górę. + +**Faster R-CNN (2015)** — ostatni krok ewolucji: zastąp Selective Search (osobny algorytm) siecią neuronową! **RPN (Region Proposal Network)** — mała sieć przesuwana po feature mapie, która w KAŻDEJ pozycji predykuje: "czy tu jest obiekt?" + proponuje bbox. Wszystko w jednej sieci, end-to-end. + + Pipeline Faster R-CNN: Obraz → CNN backbone (np. ResNet) → Feature Map → RPN (proposals) → ROI Pool → FC → klasy + bbox - RPN szczegóły: - - W każdym punkcie feature mapy rozważ k=9 „anchor boxes" (3 rozmiary × 3 proporcje) - - Dla każdego anchora: P(obiekt) + przesunięcie bbox (Δx, Δy, Δw, Δh) - - Zachowaj ~300 propozycji z najwyższym P(obiekt) → do ROI Pool + RPN krok po kroku: + Feature mapa [40×60×256] ← z backbone + ↓ Filtr 3×3 przesuwa się po feature mapie + ↓ W KAŻDEJ pozycji (x,y) rozważ k=9 "anchor boxes": + + 9 anchorów = 3 rozmiary × 3 proporcje: + ┌───┐ ┌─────┐ ┌───────┐ ← 128×128, 256×256, 512×512 + │ │ │ │ │ │ × proporcje 1:1, 1:2, 2:1 + └───┘ └─────┘ └───────┘ + + ↓ Dla KAŻDEGO z 9 anchorów sieć predykuje: + - P(obiekt) = prawdopodobieństwo, że tu jest obiekt + - (Δx, Δy, Δw, Δh) = przesunięcie bbox względem anchora + + 40×60 = 2400 pozycji × 9 anchorów = 21 600 potencjalnych propozycji! + → Weź ~300 z najwyższym P(obiekt) → ROI Pool → FC → klasy + bbox Faster R-CNN: ~5 fps (~0.2 sec/obraz) — 250× szybciej niż R-CNN! + Mnemonik ewolucji R-CNN: "CORAZ MNIEJ MARNOWANIA" + R-CNN: Selective Search + 2000×CNN = 50s → WOLNE + Fast R-CNN: Selective Search + 1×CNN + ROI Pool = 2s → lepiej + Faster R-CNN: RPN (w sieci!) + 1×CNN + ROI Pool = 0.2s → 250× szybciej! + --- **One-stage detectors** — klasyfikacja i lokalizacja w jednym przejściu (bez osobnego etapu propozycji). Szybsze, ale historycznie mniej precyzyjne. @@ -192,27 +562,126 @@ Jedno przejście przez sieć → WSZYSTKIE detekcje naraz. 45-155 fps! **Anchor-free** — nowoczesne podejście (FCOS, YOLOv8): bezpośrednia predykcja środka i wymiarów, bez predefiniowanych anchorów. Prostsza architektura, mniej hyperparametrów. -**DETR (DEtection TRansformer, 2020)** — Facebook AI. Zamiast CNN + anchor + NMS, używa **transformera** z mechanizmem self-attention. Predykuje bezpośrednio ZESTAW obiektów (set prediction, nie grid). NIE potrzebuje NMS (unik duplikatów rozwiązany przez Hungarian matching w treningu). Najprostsza architektura w detekcji, ale wolniejsza w treningu. +**Transformer** — architektura sieci neuronowej pierwotnie z NLP (2017, "Attention is All You Need"), ale skutecznie zaadaptowana do wizji komputerowej (ViT, DETR). Kluczowy mechanizm: **self-attention** — każdy element wejścia "patrzy" na WSZYSTKIE inne elementy i decyduje, które są dla niego ważne. + + W tekście: słowo "bank" patrzy na "rzeka" i "pieniądze" → + attention decyduje: "w tym zdaniu chodzi o brzeg RZEKI, nie bank pieniędzy" + + W obrazie (DETR): fragment obrazu "patrzy" na inne fragmenty → + attention: "ta łapa jest częścią TEGO kota, a nie tamtego psa" + +**Self-attention (samo-uwaga)** — mechanizm: dla każdego elementu oblicz "uwagę" do KAŻDEGO innego elementu. Matematycznie: Query × Key → wagi attention → ważona suma Values. + + Uproszczony pseudokod: + def self_attention(features): # features = N elementów + Q = features × W_query # Query: "czego szukam?" + K = features × W_key # Key: "co oferuję?" + V = features × W_value # Value: "jaką informację niosę?" + + attention = softmax(Q × K^T / sqrt(d)) # macierz N×N: "kto ważny dla kogo" + output = attention × V # ważona kombinacja wartości + return output + + Złożoność: O(n^2) — każdy element z każdym → wolne dla dużych obrazów. + Dlatego DETR wolniej się TRENUJE niż YOLO (ale architektura jest PROSTSZA). + +**DETR (DEtection TRansformer, 2020)** — model Facebooka stosujący Transformer do detekcji. Radykalnie prostszy pipeline: BRAK anchorów, BRAK NMS! Sieć predykuje bezpośrednio ZESTAW N obiektów (np. N=100). + + Pipeline DETR: + Obraz → CNN backbone → Feature mapa → Transformer Encoder (self-attention) + → Transformer Decoder (z N=100 "object queries") + → N predykcji: [(klasa₁, bbox₁), ..., (klasa₁₀₀, bbox₁₀₀)] + + "Object queries" = 100 wyuczonych wektorów, każdy "szuka" jednego obiektu. + Obraz z 5 obiektami → 5 queries dopasuje się do obiektów, + 95 queries zwróci klasę "brak obiektu" (empty set). + + Pseudokod DETR: + def detr_forward(image): + features = backbone(image) # ResNet → feature mapa + encoded = transformer_encoder(features) # self-attention na feat. mapie + queries = learnable_queries(100) # 100 wyuczonych zapytań + decoded = transformer_decoder(queries, encoded) # cross-attention + predictions = [] + for q in decoded: + cls = classify(q) # "samochód" / "pies" / "brak" + box = regress(q) # (x, y, w, h) + predictions.append((cls, box)) + return predictions # 100 predykcji (większość = brak) + + Mnemonik DETR: "Detekcja Eliminująca Trikowe Redundancje" + → bez NMS, bez anchorów, prosty pipeline. + +**Hungarian matching (dopasowanie węgierskie)** — algorytm używany podczas TRENINGU DETR. Problem: sieć daje 100 predykcji, na obrazie jest 5 obiektów — która predykcja odpowiada któremu obiektowi? Algorytm węgierski znajduje OPTYMALNE dopasowanie 1:1 minimalizując łączny koszt (błąd klasy + błąd bbox). + + Predykcje DETR: Ground truth: + pred_1: "samochód" gt_1: "samochód" (bbox A) + pred_2: "pies" gt_2: "pies" (bbox B) + pred_3: "brak" + ... Hungarian matching: + pred_100: "brak" pred_1 ↔ gt_1 (najlepsze dopasowanie!) + pred_2 ↔ gt_2 + reszta ↔ "brak obiektu" + + Efekt: BRAK DUPLIKATÓW → BRAK NMS! + (Każdy obiekt dopasowany do DOKŁADNIE jednej predykcji) --- -**NMS (Non-Maximum Suppression)** — post-processing: detektor generuje wiele nakładających się bbox dla tego samego obiektu. NMS: weź najlepszą (max confidence), usuń wszystkie mocno nakładające się (IoU > prog), powtórz. +**NMS (Non-Maximum Suppression, tłumienie nie-maksymalnych)** — algorytm post-processingu usuwający ZDUPLIKOWANE detekcje. Problem: detektor generuje WIELE nakładających się bbox dla tego samego obiektu. NMS zachowuje NAJLEPSZĄ i usuwa resztę. Jedyny detektor BEZ NMS = DETR. - Detections: [bbox1, 0.95], [bbox2, 0.90], [bbox3, 0.85] (nakładające się) - NMS: zachowaj bbox1 (0.95), usuń bbox2 i bbox3 (IoU > 0.5 z bbox1) + Algorytm NMS krok po kroku: + Wejście: detekcje posortowane malejąco po confidence + [bbox_1 conf=0.95], [bbox_2 conf=0.90], [bbox_3 conf=0.85], [bbox_4 conf=0.40] -**IoU (Intersection over Union)** — miara nakładania dwóch bbox: pole przecięcia / pole sumy. IoU=1 → identyczne; IoU=0 → brak nakładania. Próg NMS typowo 0.5. + Pseudokod NMS: + def nms(detections, iou_threshold=0.5): + detections.sort(by=confidence, descending=True) + keep = [] + while detections: + best = detections.pop(0) # weź najlepszą + keep.append(best) # ZACHOWAJ ją + detections = [d for d in detections + if iou(best, d) < iou_threshold] # usuń nakładające + return keep -**Backbone** — sieć bazowa (np. ResNet, VGG) wyciągająca cechy z obrazu. Detection head (głowa detekcyjna) jest dodawana „na wierzch" backbone i predykuje bbox + klasy. Fine-tuning backbone na detekcję = transfer learning. + Krok 1: Weź bbox_1 (0.95) → ZACHOWAJ + Krok 2: IoU(bbox_1, bbox_2) = 0.82 > 0.5 → USUŃ (duplikat tego samego kota!) + IoU(bbox_1, bbox_3) = 0.75 > 0.5 → USUŃ (duplikat!) + IoU(bbox_1, bbox_4) = 0.10 < 0.5 → ZACHOWAJ (INNY obiekt!) + Krok 3: Wynik: [bbox_1, bbox_4] — 2 unikalne obiekty + + Mnemonik: NMS = "Najlepszy Ma Się dobrze" — zachowaj najlepszą, usuń resztę. + +**IoU (Intersection over Union)** — miara nakładania dwóch prostokątów. IoU = pole przecięcia / pole sumy. Wartości: 0.0 (nie nakładają się) do 1.0 (identyczne). + + bbox A: bbox B: Przecięcie: + ┌──────────┐ ┌────┐ + │ A │ ┌──────────┐ │ ∩ │ + │ ┌───┼────────┼──┐ │ └────┘ + │ │ ∩ │ │ │ B │ + └──────┼───┘ │ │ │ + └────────────┴──┘ + + IoU = pole(∩) / pole(A ∪ B) + = pole(∩) / (pole(A) + pole(B) − pole(∩)) + + Przykład liczbowy: + A = [0, 0, 100, 100] → pole = 10 000 + B = [50, 50, 150, 150] → pole = 10 000 + ∩ = [50, 50, 100, 100] → pole = 2 500 + IoU = 2500 / (10000 + 10000 − 2500) = 2500 / 17500 ≈ 0.14 + + IoU > 0.5 w NMS → "to TEN SAM obiekt" → usuń słabszą detekcję + IoU > 0.5 w mAP → "detekcja TRAFNA" → poprawna lokalizacja --- -**Jak zbudować detektor z klasyfikatora? Trzy podejścia:** -1. **Sliding window** — wytnij, sklasyfikuj, NMS. Bardzo wolne. -2. **Region proposals + klasyfikator** — Selective Search generuje ~2000 regionów, sklasyfikuj każdy + NMS. Szybsze. -3. **Fine-tune backbone** — weź pretrained classifier (np. ResNet z ImageNet), dodaj detection head (bbox regression + cls), dotrenuj na danych detekcyjnych. **Najlepsza jakość.** - -**DETR (DEtection TRansformer, 2020)** — Facebook AI. Transformer zamiast CNN, bezpośrednia predykcja zestawu obiektów (set prediction), bez NMS. Uproszczona architektura. +**Jak zbudować detektor z klasyfikatora? Trzy podejścia (+ bonus):** +1. **Sliding window** — wytnij, sklasyfikuj, NMS. Bardzo wolne (miliony klasyfikacji). +2. **Region proposals + klasyfikator** — Selective Search → ~2000 regionów → klasyfikuj + NMS. Wolne ale działa (= R-CNN). +3. **Fine-tune backbone** — weź pretrained classifier (ResNet z ImageNet), dodaj detection head (bbox regression + cls), dotrenuj na danych detekcyjnych. **Najlepsza jakość** (= Faster R-CNN, YOLO, SSD). +4. **Transformer (DETR)** — bez anchorów, bez NMS, predykcja zestawu obiektów end-to-end. --- @@ -354,11 +823,26 @@ Masz wytrenowany klasyfikator (np. ResNet na ImageNet: obraz → „kot"). Jak g ### Etymologia -**YOLO** — You Only Look Once (Joseph Redmon et al., 2016). **R-CNN** — Region-based CNN (Ross Girshick, 2014). **HOG** — Histogram of Oriented Gradients (Dalal & Triggs, 2005). **SVM** — Support Vector Machine (Vapnik, 1995). **Viola-Jones** — Paul Viola + Michael Jones (2001). **DETR** — DEtection TRansformer (Facebook AI, 2020). **SSD** — Single Shot MultiBox Detector (Liu et al., 2016). **NMS** — Non-Maximum Suppression; tłumienie nie-maksymalnych detekcji. +**CNN** — Convolutional Neural Network (sieć z konwolucjami). **YOLO** — You Only Look Once (Joseph Redmon et al., 2016). **R-CNN** — Region-based CNN (Ross Girshick, 2014). **HOG** — Histogram of Oriented Gradients (Dalal & Triggs, 2005). **SVM** — Support Vector Machine (Vapnik, 1995). **Viola-Jones** — Paul Viola + Michael Jones (2001). **DETR** — DEtection TRansformer (Facebook AI, 2020). **SSD** — Single Shot MultiBox Detector (Liu et al., 2016). **NMS** — Non-Maximum Suppression; tłumienie nie-maksymalnych detekcji. **ROI** — Region of Interest (region zainteresowania). **RPN** — Region Proposal Network (sieć propozycji regionów). **FPN** — Feature Pyramid Network (piramida cech). **IoU** — Intersection over Union (przecięcie przez sumę). **FC** — Fully Connected (w pełni połączona). **ReLU** — Rectified Linear Unit (wyprostowana jednostka liniowa). **mAP** — mean Average Precision (średnia precyzja). ### Jak zapamiętać -- **YOLO = „You Only Look Once"** — jednoetapowy, szybki +- **CNN = „Czytaj Nie Naraz"** — małe filtry 3×3 przesuwane po obrazie, nie cały obraz naraz +- **Hierarchia CNN: „K-R-F-O" = „Każdy Rycerz Znajduje Obiekt"** — Krawędzie → Rogi → Fragmenty → Obiekty +- **FC = „Full Connection"** — każdy z każdym, warstwa decyzyjna na końcu CNN +- **Backbone = SILNIK samochodu** — ten sam silnik (ResNet), różne karoserie (klasyfikacja/detekcja/segmentacja) +- **Backbone'y: A→V→R = „Architektura Bardzo Rezylientna"** — AlexNet (2012) → VGG (2014) → ResNet (2015) +- **Transfer learning** — nie ucz się od zera, przenieś wiedzę z ImageNet +- **Viola-Jones: kaskada = „SITO"** — piach odpada wcześnie, złoto (twarz) zostaje na końcu +- **AdaBoost = „ADAptacyjnie BOOSTuj"** — słabe modele razem = silny +- **Integral Image** — 4 odczyty = suma dowolnego prostokąta, zawsze O(1) +- **Selective Search** — inteligentne łączenie regionów zamiast milionów okien +- **ROI Pooling** — dowolny rozmiar → stały rozmiar (siatkowanie + max) +- **Bbox regression = „GPS korekta"** — popraw przybliżoną pozycję o Δx, Δy, Δw, Δh +- **Ewolucja R-CNN: „CORAZ MNIEJ MARNOWANIA"** — R-CNN (50s) → Fast (2s) → Faster (0.2s) +- **YOLO = „You Only Look Once"** — jednoetapowy, szybki, siatka S×S - **Faster R-CNN = CNN + RPN + ROI Pool** — dwuetapowy, dokładny -- **Detektor z klasyfikatora:** sliding window (wolno) → proposals (lepiej) → fine-tune backbone (najlepiej) +- **NMS = „Najlepszy Ma Się dobrze"** — zachowaj najlepszą detekcję, usuń duplikaty +- **DETR = „Detekcja Eliminująca Trikowe Redundancje"** — bez NMS, bez anchorów, transformer +- **Detektor z klasyfikatora:** sliding window (wolno) → proposals (lepiej) → fine-tune backbone (najlepiej) → DETR (najprościej)