improve questions 2 9 20 24
@ -468,197 +468,95 @@ Przykład — kluczowa różnica: decrease-key:
|
|||||||
|
|
||||||
### Pseudokod (Python)
|
### 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,
|
def dijkstra(graph, source):
|
||||||
# wartość = lista par (sąsiad, waga)
|
dist = {v: float('inf') for v in graph}
|
||||||
# np. graph = {'A': [('B',2), ('C',4)],
|
dist[source] = 0
|
||||||
# 'B': [('D',3)], ...}
|
visited = set()
|
||||||
# start = wierzchołek startowy, np. 'A'
|
for _ in range(len(graph)):
|
||||||
|
current = None # szukaj nieodwiedzonego wierzchołka o min dist — O(V)
|
||||||
d = {v: float('inf') for v in graph} # d = słownik odległości (distance)
|
for v in graph:
|
||||||
# Klucz: wierzchołek v
|
if v not in visited and (current is None or dist[v] < dist[current]):
|
||||||
# Wartość: najkrótsza DOTYCHCZAS ZNANA
|
current = v
|
||||||
# odległość od start do v
|
if dist[current] == float('inf'):
|
||||||
# Na początku: ∞ (nieskończoność) dla
|
break # reszta nieosiągalna
|
||||||
# wszystkich — bo jeszcze niczego
|
visited.add(current) # zamknij — NIE wracamy (zachłanność)
|
||||||
# nie odkryliśmy
|
for neighbor, weight in graph[current]: # relaksacja sąsiadów
|
||||||
# float('inf') = Python-owa nieskończoność,
|
if dist[current] + weight < dist[neighbor]:
|
||||||
# każda liczba jest od niej mniejsza
|
dist[neighbor] = dist[current] + weight
|
||||||
|
return dist # O(V²) z tablicą
|
||||||
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
|
|
||||||
|
|
||||||

|

|
||||||
|
|
||||||
**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']
|
def bellman_ford(vertices, edges, source):
|
||||||
# edges = lista krawędzi, każda to (skąd, dokąd, waga)
|
dist = {v: float('inf') for v in vertices}
|
||||||
# np. [('A','B',2), ('A','C',4), ('B','D',3), ...]
|
dist[source] = 0
|
||||||
# start = wierzchołek startowy
|
for _ in range(len(vertices) - 1): # V−1 iteracji (najdłuższa ścieżka = V−1 krawędzi)
|
||||||
# UWAGA: format inny niż w Dijkstrze!
|
for src, dst, weight in edges: # relaksuj WSZYSTKIE krawędzie
|
||||||
# Dijkstra: graf jako słownik sąsiedztwa
|
if dist[src] + weight < dist[dst]:
|
||||||
# B-F: explicite lista krawędzi
|
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
|
Przykład — graf z ujemnymi wagami (Dijkstra daje ZŁY wynik, B-F poprawny):
|
||||||
# jak w Dijkstrze. Klucz = wierzchołek,
|
|
||||||
# wartość = najkrótsza znana odległość.
|
|
||||||
# Na starcie: ∞ dla wszystkich.
|
|
||||||
|
|
||||||
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)
|
Dijkstra:
|
||||||
# DLACZEGO V−1? Bo najdłuższa najkrótsza
|
1. S(0): dist[A]=2, dist[B]=5
|
||||||
# ścieżka (bez cykli) ma co najwyżej V−1
|
2. A(2) zamknięty: dist[C]=5
|
||||||
# krawędzi. Po k iteracjach mamy poprawne
|
3. B(5): B→A = 5−4 = 1 < 2, ALE A już zamknięty → POMIJA!
|
||||||
# odległości dla ścieżek o ≤ k krawędziach.
|
Wynik: A=2, C=5 ← BŁĄD (prawidłowe: A=1, C=4)
|
||||||
# _ = zmienna, której nie używamy (konwencja)
|
|
||||||
|
|
||||||
for u, v, w in edges: # w KAŻDEJ iteracji przejrzyj WSZYSTKIE krawędzie
|
Bellman-Ford — relaksuje WSZYSTKIE krawędzie, V−1 = 3 razy:
|
||||||
# u = początek krawędzi, v = koniec, w = waga
|
Start: dist = [S:0, A:∞, B:∞, C:∞]
|
||||||
# To jest brute-force — stąd O(V·E)
|
|
||||||
|
|
||||||
if d[u] + w < d[v]: # RELAKSACJA — identyczna jak w Dijkstrze:
|
Iteracja 1:
|
||||||
# czy droga start→u→v jest krótsza niż d[v]?
|
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 ---
|
Iteracja 3: brak zmian → stabilne.
|
||||||
for u, v, w in edges: # dodatkowe (V-te) przejście po krawędziach
|
Wynik: [S:0, A:1, B:5, C:4] ← POPRAWNE
|
||||||
# Jeśli NADAL da się poprawić odległość,
|
|
||||||
# to znaczy, że istnieje cykl ujemny!
|
|
||||||
# (po V−1 iteracjach powinno być stabilne)
|
|
||||||
|
|
||||||
if d[u] + w < d[v]: # nadal można polepszyć? → cykl ujemny!
|
Wykrywanie cyklu ujemnego — dodaj krawędź C→B(−3):
|
||||||
return None # zwróć None = sygnał "cykl ujemny wykryty"
|
Cykl B→A→C→B = −4 + 3 + (−3) = −4 < 0.
|
||||||
|
Po V−1 iteracjach dist nadal maleje → V-ta iteracja:
|
||||||
return d # zwróć słownik odległości (jak Dijkstra)
|
dist[src] + weight < dist[dst] → return None
|
||||||
|
|
||||||

|

|
||||||
|
|
||||||
**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)
|
def a_star(graph, source, goal, heuristic):
|
||||||
# start = wierzchołek startowy
|
cost_so_far = {source: 0} # g(n) — faktyczny koszt dotarcia
|
||||||
# goal = wierzchołek DOCELOWY (cel)
|
priority = {source: heuristic(source)} # f(n) = g(n) + h(n)
|
||||||
# → to jedyna różnica od Dijkstry:
|
came_from = {} # do odtworzenia ścieżki
|
||||||
# szukamy ścieżki do JEDNEGO celu
|
visited = set()
|
||||||
# h = FUNKCJA heurystyczna: h(v) zwraca
|
while priority:
|
||||||
# oszacowanie odległości od v do goal
|
current = min(priority, key=priority.get) # wierzchołek o min f(n)
|
||||||
# np. h = lambda v: odl_euklidesowa(v, goal)
|
del priority[current]
|
||||||
|
if current == goal:
|
||||||
d = {start: 0} # d = słownik g(n) = faktyczny koszt
|
break # dotarliśmy — A* kończy (Dijkstra przetworzyłby wszystko)
|
||||||
# dotarcia od start do n
|
visited.add(current)
|
||||||
# Tu trzymamy TYLKO odkryte wierzchołki
|
for neighbor, weight in graph[current]:
|
||||||
# (nie inicjalizujemy ∞ dla reszty)
|
if neighbor in visited:
|
||||||
|
|
||||||
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ń
|
|
||||||
continue
|
continue
|
||||||
|
new_cost = cost_so_far[current] + weight
|
||||||
g_new = d[u] + w # g_new = potencjalny nowy koszt dotarcia do v
|
if neighbor not in cost_so_far or new_cost < cost_so_far[neighbor]:
|
||||||
# (koszt do u + krawędź u→v)
|
cost_so_far[neighbor] = new_cost
|
||||||
|
priority[neighbor] = new_cost + heuristic(neighbor)
|
||||||
if v not in d or g_new < d[v]: # jeśli v jeszcze nie odkryty
|
came_from[neighbor] = current
|
||||||
# LUB znaleźliśmy krótszą drogę
|
return came_from, cost_so_far.get(goal) # ścieżka + koszt
|
||||||
|
|
||||||
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
|
|
||||||
|
|
||||||

|

|
||||||
|
|
||||||
|
|||||||
BIN
pytania/__pycache__/generate_q9_all_diagrams.cpython-314.pyc
Normal file
1172
pytania/generate_q20_diagrams.py
Normal file
1009
pytania/generate_q9_all_diagrams.py
Normal file
BIN
pytania/img/q20_batch_vs_streaming.png
Normal file
|
After Width: | Height: | Size: 123 KiB |
BIN
pytania/img/q20_decision_tree.png
Normal file
|
After Width: | Height: | Size: 154 KiB |
BIN
pytania/img/q20_event_vs_processing_time.png
Normal file
|
After Width: | Height: | Size: 154 KiB |
BIN
pytania/img/q20_exactly_once.png
Normal file
|
After Width: | Height: | Size: 128 KiB |
BIN
pytania/img/q20_flink_arch.png
Normal file
|
After Width: | Height: | Size: 132 KiB |
BIN
pytania/img/q20_kafka_streams_arch.png
Normal file
|
After Width: | Height: | Size: 115 KiB |
BIN
pytania/img/q20_lambda_kappa_table.png
Normal file
|
After Width: | Height: | Size: 93 KiB |
BIN
pytania/img/q20_lambda_vs_kappa.png
Normal file
|
After Width: | Height: | Size: 226 KiB |
BIN
pytania/img/q20_late_data_strategies.png
Normal file
|
After Width: | Height: | Size: 138 KiB |
BIN
pytania/img/q20_platform_comparison.png
Normal file
|
After Width: | Height: | Size: 123 KiB |
BIN
pytania/img/q20_session_users.png
Normal file
|
After Width: | Height: | Size: 116 KiB |
BIN
pytania/img/q20_sliding_sla.png
Normal file
|
After Width: | Height: | Size: 81 KiB |
BIN
pytania/img/q20_spark_streaming_arch.png
Normal file
|
After Width: | Height: | Size: 127 KiB |
BIN
pytania/img/q20_streaming_ecosystem.png
Normal file
|
After Width: | Height: | Size: 136 KiB |
BIN
pytania/img/q20_true_vs_microbatch.png
Normal file
|
After Width: | Height: | Size: 125 KiB |
BIN
pytania/img/q20_tumbling_fraud.png
Normal file
|
After Width: | Height: | Size: 78 KiB |
BIN
pytania/img/q20_window_types.png
Normal file
|
After Width: | Height: | Size: 289 KiB |
BIN
pytania/img/q9_classic_problems.png
Normal file
|
After Width: | Height: | Size: 281 KiB |
BIN
pytania/img/q9_coffman_strategies.png
Normal file
|
After Width: | Height: | Size: 140 KiB |
BIN
pytania/img/q9_deadlock_scenario.png
Normal file
|
After Width: | Height: | Size: 126 KiB |
BIN
pytania/img/q9_ipc_details.png
Normal file
|
After Width: | Height: | Size: 182 KiB |
BIN
pytania/img/q9_ipc_table.png
Normal file
|
After Width: | Height: | Size: 131 KiB |
BIN
pytania/img/q9_memory_layout.png
Normal file
|
After Width: | Height: | Size: 132 KiB |
BIN
pytania/img/q9_pcb_structure.png
Normal file
|
After Width: | Height: | Size: 100 KiB |
BIN
pytania/img/q9_process_states.png
Normal file
|
After Width: | Height: | Size: 68 KiB |
BIN
pytania/img/q9_process_vs_thread.png
Normal file
|
After Width: | Height: | Size: 160 KiB |
BIN
pytania/img/q9_race_condition.png
Normal file
|
After Width: | Height: | Size: 160 KiB |
BIN
pytania/img/q9_scenario_table.png
Normal file
|
After Width: | Height: | Size: 156 KiB |
BIN
pytania/img/q9_semaphore_concept.png
Normal file
|
After Width: | Height: | Size: 77 KiB |
BIN
pytania/img/q9_speed_comparison.png
Normal file
|
After Width: | Height: | Size: 108 KiB |
BIN
pytania/img/q9_starvation_priority.png
Normal file
|
After Width: | Height: | Size: 183 KiB |
BIN
pytania/img/q9_sync_comparison.png
Normal file
|
After Width: | Height: | Size: 298 KiB |
BIN
pytania/img/q9_thread_structure.png
Normal file
|
After Width: | Height: | Size: 78 KiB |
@ -128,197 +128,95 @@ Przykład — kluczowa różnica: decrease-key:
|
|||||||
|
|
||||||
### Pseudokod (Python)
|
### 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,
|
def dijkstra(graph, source):
|
||||||
# wartość = lista par (sąsiad, waga)
|
dist = {v: float('inf') for v in graph}
|
||||||
# np. graph = {'A': [('B',2), ('C',4)],
|
dist[source] = 0
|
||||||
# 'B': [('D',3)], ...}
|
visited = set()
|
||||||
# start = wierzchołek startowy, np. 'A'
|
for _ in range(len(graph)):
|
||||||
|
current = None # szukaj nieodwiedzonego wierzchołka o min dist — O(V)
|
||||||
d = {v: float('inf') for v in graph} # d = słownik odległości (distance)
|
for v in graph:
|
||||||
# Klucz: wierzchołek v
|
if v not in visited and (current is None or dist[v] < dist[current]):
|
||||||
# Wartość: najkrótsza DOTYCHCZAS ZNANA
|
current = v
|
||||||
# odległość od start do v
|
if dist[current] == float('inf'):
|
||||||
# Na początku: ∞ (nieskończoność) dla
|
break # reszta nieosiągalna
|
||||||
# wszystkich — bo jeszcze niczego
|
visited.add(current) # zamknij — NIE wracamy (zachłanność)
|
||||||
# nie odkryliśmy
|
for neighbor, weight in graph[current]: # relaksacja sąsiadów
|
||||||
# float('inf') = Python-owa nieskończoność,
|
if dist[current] + weight < dist[neighbor]:
|
||||||
# każda liczba jest od niej mniejsza
|
dist[neighbor] = dist[current] + weight
|
||||||
|
return dist # O(V²) z tablicą
|
||||||
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
|
|
||||||
|
|
||||||

|

|
||||||
|
|
||||||
**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']
|
def bellman_ford(vertices, edges, source):
|
||||||
# edges = lista krawędzi, każda to (skąd, dokąd, waga)
|
dist = {v: float('inf') for v in vertices}
|
||||||
# np. [('A','B',2), ('A','C',4), ('B','D',3), ...]
|
dist[source] = 0
|
||||||
# start = wierzchołek startowy
|
for _ in range(len(vertices) - 1): # V−1 iteracji (najdłuższa ścieżka = V−1 krawędzi)
|
||||||
# UWAGA: format inny niż w Dijkstrze!
|
for src, dst, weight in edges: # relaksuj WSZYSTKIE krawędzie
|
||||||
# Dijkstra: graf jako słownik sąsiedztwa
|
if dist[src] + weight < dist[dst]:
|
||||||
# B-F: explicite lista krawędzi
|
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
|
Przykład — graf z ujemnymi wagami (Dijkstra daje ZŁY wynik, B-F poprawny):
|
||||||
# jak w Dijkstrze. Klucz = wierzchołek,
|
|
||||||
# wartość = najkrótsza znana odległość.
|
|
||||||
# Na starcie: ∞ dla wszystkich.
|
|
||||||
|
|
||||||
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)
|
Dijkstra:
|
||||||
# DLACZEGO V−1? Bo najdłuższa najkrótsza
|
1. S(0): dist[A]=2, dist[B]=5
|
||||||
# ścieżka (bez cykli) ma co najwyżej V−1
|
2. A(2) zamknięty: dist[C]=5
|
||||||
# krawędzi. Po k iteracjach mamy poprawne
|
3. B(5): B→A = 5−4 = 1 < 2, ALE A już zamknięty → POMIJA!
|
||||||
# odległości dla ścieżek o ≤ k krawędziach.
|
Wynik: A=2, C=5 ← BŁĄD (prawidłowe: A=1, C=4)
|
||||||
# _ = zmienna, której nie używamy (konwencja)
|
|
||||||
|
|
||||||
for u, v, w in edges: # w KAŻDEJ iteracji przejrzyj WSZYSTKIE krawędzie
|
Bellman-Ford — relaksuje WSZYSTKIE krawędzie, V−1 = 3 razy:
|
||||||
# u = początek krawędzi, v = koniec, w = waga
|
Start: dist = [S:0, A:∞, B:∞, C:∞]
|
||||||
# To jest brute-force — stąd O(V·E)
|
|
||||||
|
|
||||||
if d[u] + w < d[v]: # RELAKSACJA — identyczna jak w Dijkstrze:
|
Iteracja 1:
|
||||||
# czy droga start→u→v jest krótsza niż d[v]?
|
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 ---
|
Iteracja 3: brak zmian → stabilne.
|
||||||
for u, v, w in edges: # dodatkowe (V-te) przejście po krawędziach
|
Wynik: [S:0, A:1, B:5, C:4] ← POPRAWNE
|
||||||
# Jeśli NADAL da się poprawić odległość,
|
|
||||||
# to znaczy, że istnieje cykl ujemny!
|
|
||||||
# (po V−1 iteracjach powinno być stabilne)
|
|
||||||
|
|
||||||
if d[u] + w < d[v]: # nadal można polepszyć? → cykl ujemny!
|
Wykrywanie cyklu ujemnego — dodaj krawędź C→B(−3):
|
||||||
return None # zwróć None = sygnał "cykl ujemny wykryty"
|
Cykl B→A→C→B = −4 + 3 + (−3) = −4 < 0.
|
||||||
|
Po V−1 iteracjach dist nadal maleje → V-ta iteracja:
|
||||||
return d # zwróć słownik odległości (jak Dijkstra)
|
dist[src] + weight < dist[dst] → return None
|
||||||
|
|
||||||

|

|
||||||
|
|
||||||
**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)
|
def a_star(graph, source, goal, heuristic):
|
||||||
# start = wierzchołek startowy
|
cost_so_far = {source: 0} # g(n) — faktyczny koszt dotarcia
|
||||||
# goal = wierzchołek DOCELOWY (cel)
|
priority = {source: heuristic(source)} # f(n) = g(n) + h(n)
|
||||||
# → to jedyna różnica od Dijkstry:
|
came_from = {} # do odtworzenia ścieżki
|
||||||
# szukamy ścieżki do JEDNEGO celu
|
visited = set()
|
||||||
# h = FUNKCJA heurystyczna: h(v) zwraca
|
while priority:
|
||||||
# oszacowanie odległości od v do goal
|
current = min(priority, key=priority.get) # wierzchołek o min f(n)
|
||||||
# np. h = lambda v: odl_euklidesowa(v, goal)
|
del priority[current]
|
||||||
|
if current == goal:
|
||||||
d = {start: 0} # d = słownik g(n) = faktyczny koszt
|
break # dotarliśmy — A* kończy (Dijkstra przetworzyłby wszystko)
|
||||||
# dotarcia od start do n
|
visited.add(current)
|
||||||
# Tu trzymamy TYLKO odkryte wierzchołki
|
for neighbor, weight in graph[current]:
|
||||||
# (nie inicjalizujemy ∞ dla reszty)
|
if neighbor in visited:
|
||||||
|
|
||||||
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ń
|
|
||||||
continue
|
continue
|
||||||
|
new_cost = cost_so_far[current] + weight
|
||||||
g_new = d[u] + w # g_new = potencjalny nowy koszt dotarcia do v
|
if neighbor not in cost_so_far or new_cost < cost_so_far[neighbor]:
|
||||||
# (koszt do u + krawędź u→v)
|
cost_so_far[neighbor] = new_cost
|
||||||
|
priority[neighbor] = new_cost + heuristic(neighbor)
|
||||||
if v not in d or g_new < d[v]: # jeśli v jeszcze nie odkryty
|
came_from[neighbor] = current
|
||||||
# LUB znaleźliśmy krótszą drogę
|
return came_from, cost_so_far.get(goal) # ścieżka + koszt
|
||||||
|
|
||||||
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
|
|
||||||
|
|
||||||

|

|
||||||
|
|
||||||
|
|||||||
@ -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.
|
**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
|
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
@ -32,23 +23,20 @@
|
|||||||
- **HEAP** — pamięć alokowana dynamicznie (malloc/new), rośnie w górę
|
- **HEAP** — pamięć alokowana dynamicznie (malloc/new), rośnie w górę
|
||||||
- **STACK** — zmienne lokalne, adresy powrotu, rośnie w dół
|
- **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.
|
**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.
|
**PID (Process ID)** — unikalny identyfikator procesu w systemie. Np. `PID 1` = init/systemd w Linux.
|
||||||
|
|
||||||
**TID (Thread ID)** — unikalny identyfikator wątku.
|
**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:** 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).
|
**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ą.
|
**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.
|
**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.
|
**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):
|
**Warunki Coffmana** — 4 warunki konieczne deadlocka (wszystkie muszą zachodzić jednocześnie):
|
||||||
1. **Mutual exclusion** — zasób jest wyłączny (tylko jeden wątek)
|
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 (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`.
|
**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):**
|
**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 (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.
|
Przełączenie kontekstu = zapisanie PCB starego procesu → wczytanie PCB nowego.
|
||||||
|
|
||||||
**Stany procesu:** NEW → READY ↔ RUNNING → BLOCKED → TERMINATED.
|
**Stany procesu:** NEW → READY ↔ RUNNING → BLOCKED → TERMINATED.
|
||||||
Scheduler decyduje, który READY staje się RUNNING.
|
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
|
### Budowa wątku
|
||||||
|
|
||||||
Wątek = lekka jednostka wykonania WEWNĄTRZ procesu.
|
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.
|
**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.
|
**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.
|
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
|
### Szybkość — porównanie ilościowe
|
||||||
|
|
||||||
| Operacja | Proces | Wątek | Dlaczego różnica? |
|
| 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. |
|
| 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 (10x) | Proces: TLB flush (unieważnienie cache adresów). Wątek: te same tablice stron, TLB zostaje. |
|
| 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. |
|
| 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. |
|
| 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)
|
> **Mnemonik SZYBKOŚCI — „100, 10, 1000":**
|
||||||
pthread_create() (wątek): ~50 μs → alokacja stosu (~8 MB) + wpis w schedulerze
|
> 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ć.
|
||||||
Przełączanie kontekstu (benchmarki):
|
> Zapamiętaj: **fork() = przeprowadzka**, **pthread_create() = nowy pokój w tym samym mieszkaniu**.
|
||||||
|
|
||||||
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?
|
### Zastosowanie — kiedy proces, kiedy wątek?
|
||||||
|
|
||||||
@ -198,18 +170,13 @@ Przełączanie kontekstu (benchmarki):
|
|||||||
- Rendering: każdy wątek renderuje część klatki
|
- Rendering: każdy wątek renderuje część klatki
|
||||||
- **Responsywność UI** — wątek główny obsługuje interfejs, wątek tła liczy/pobiera dane
|
- **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?
|
> **Mnemonik ZASTOSOWANIA — „MURY vs OKNA":**
|
||||||
──────────────────────────────────────────────────────────────
|
> **Proces jak MURY** = izolacja, bezpieczeństwo, crash isolation (Chrome karty, Apache, sandboxing).
|
||||||
Serwer WWW (Apache pre-fork) Proces izolacja klientów
|
> **Wątek jak OKNA** = szybki dostęp do wspólnych danych, lekki, wydajny (gry, thread pool, UI).
|
||||||
Serwer WWW (nginx) Wątek/async szybkość, cooperacja
|
> Pytanie-test: „Czy awaria jednego ma zabić resztę?" → TAK = wątek OK, NIE = proces.
|
||||||
Przeglądarka (karty) Proces crash isolation
|
> Regułka: **„IBW → P, WSO → W"** = **I**zolacja/**B**ezpieczeństwo/**W**ieloprogramowość → **P**roces. **W**spółdzielenie/**S**zybkość/**O**bliczenia → **W**ątek.
|
||||||
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
|
### Porównanie zbiorcze
|
||||||
|
|
||||||
@ -237,18 +204,12 @@ Procesy mają **izolowane** przestrzenie adresowe — nie mogą bezpośrednio cz
|
|||||||
|
|
||||||
#### Mechanizmy IPC z przykładami
|
#### 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:
|
**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
|
$ 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:
|
Kod C:
|
||||||
int fd[2];
|
int fd[2];
|
||||||
pipe(fd); // tworzy potok: fd[0]=read, fd[1]=write
|
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).
|
**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):
|
Kod C (POSIX):
|
||||||
int fd = shm_open("/my_shm", O_CREAT|O_RDWR, 0666);
|
int fd = shm_open("/my_shm", O_CREAT|O_RDWR, 0666);
|
||||||
ftruncate(fd, 4096);
|
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ć.
|
**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 A: msgsnd(qid, &msg, size, 0) // wyślij wiadomość
|
||||||
Proces B: msgrcv(qid, &msg, size, typ, 0) // odbierz (filtruj typ)
|
Proces B: msgrcv(qid, &msg, size, typ, 0) // odbierz (filtruj typ)
|
||||||
|
|
||||||
Przewaga nad pipe: wiele nadawców/odbiorców, filtrowanie typów,
|
Przewaga nad pipe: wiele nadawców/odbiorców, filtrowanie typów,
|
||||||
wiadomość ma granice (pipe to surowy strumień bajtó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.
|
**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.
|
**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(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
|
#### Porównanie mechanizmów IPC
|
||||||
|
|
||||||
Mechanizm Kierunek Szybkość Zastosowanie
|

|
||||||
──────────────────────────────────────────────────────────
|
|
||||||
Pipe jednokier. średnia ls | grep
|
> **Mnemonik KOMUNIKACJI — „PNMSSS" (6 mechanizmów IPC):**
|
||||||
Named Pipe jednokier. średnia demon → klient
|
> **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.
|
||||||
Shared Memory dwukier. najszybsza video, bazy danych
|
> Szybkość: **Shared Memory > Pipe ≈ MsgQueue > Socket** (sieciowy najwolniejszy).
|
||||||
Message Queue dwukier. średnia wieloproducentowe
|
> Zapamiętaj: **„Shared = zero kopii"** — najszybszy bo oba procesy piszą do tej samej ramki RAM.
|
||||||
Socket dwukier. wolna (sieć) klient-serwer
|
> **Pipe = rura z wodą** (jednokierunkowa), **Socket = telefon** (dwukierunkowy, też przez sieć).
|
||||||
Signal jednokier. natychmiast. powiadomienia
|
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
@ -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.
|
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:
|
Poprawka — mutex:
|
||||||
lock(mutex);
|
lock(mutex);
|
||||||
saldo = saldo + kwota; // sekcja krytyczna
|
saldo = saldo + kwota; // sekcja krytyczna
|
||||||
unlock(mutex);
|
unlock(mutex);
|
||||||
|
|
||||||
Z mutex: A blokuje → czyta 1000 → pisze 1500 → B blokuje → czyta 1500 → pisze 1300. ✓
|
|
||||||
|
|
||||||
#### Problem 2 — Zakleszczenie (Deadlock)
|
#### Problem 2 — Zakleszczenie (Deadlock)
|
||||||
|
|
||||||
Dwa lub więcej wątków czekają na siebie nawzajem — żaden nie może kontynuować. System „zamiera".
|
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ć):
|
**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)
|
#### 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ą.
|
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)
|
#### 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!).
|
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.
|
> **Mnemonik SYNCHRONIZACJI — „WZZI" (4 problemy):**
|
||||||
L dziedziczy priorytet H (tymczasowo L=H), więc M nie może wypchać L.
|
> **W**szystkie **Z**egarki **Z**atrzymały się **I**naczej = **W**yścig, **Z**akleszczenie, **Z**agłodzenie, **I**nwersja priorytetów.
|
||||||
Mars Pathfinder (1997) — klasyczny bug priority inversion w kosmosie!
|
> 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
|
### Klasyczne problemy synchronizacji
|
||||||
|
|
||||||

|

|
||||||
|
|
||||||
#### Producent-Konsument (Bounded Buffer)
|
#### 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.
|
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:
|
Rozwiązanie z semaforami:
|
||||||
─────────────────────────────────────────────────
|
|
||||||
semaphore mutex = 1; // wyłączny dostęp do bufora
|
semaphore mutex = 1; // wyłączny dostęp do bufora
|
||||||
semaphore empty = N; // ile wolnych slotów (początkowo N)
|
semaphore empty = N; // ile wolnych slotów (początkowo N)
|
||||||
semaphore full = 0; // ile pełnych slotów (początkowo 0)
|
semaphore full = 0; // ile pełnych slotów (początkowo 0)
|
||||||
|
|
||||||
Producent: Konsument:
|
Producent: Konsument:
|
||||||
P(empty) // czekaj na P(full) // czekaj na
|
P(empty) P(full)
|
||||||
// wolny slot // pełny slot
|
P(mutex) P(mutex)
|
||||||
P(mutex) // wejdź do P(mutex) // wejdź do
|
wstaw(elem) elem = pobierz()
|
||||||
// sek. kryt. // sek. kryt.
|
V(mutex) V(mutex)
|
||||||
wstaw(elem) elem = pobierz()
|
V(full) V(empty)
|
||||||
V(mutex) // wyjdź V(mutex) // wyjdź
|
|
||||||
V(full) // +1 pełny V(empty) // +1 wolny
|
|
||||||
─────────────────────────────────────────────────
|
|
||||||
|
|
||||||
Bufor (N=4):
|
UWAGA: kolejność P(empty/full) PRZED P(mutex)!
|
||||||
┌────┬────┬────┬────┐
|
Odwrotnie (P(mutex) → P(empty)) = DEADLOCK!
|
||||||
│ 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)
|
#### Czytelnicy-Pisarze (Readers-Writers)
|
||||||
|
|
||||||
Wielu czytelników może czytać jednocześnie. Pisarz wymaga wyłącznego dostępu (ani czytelnicy, ani inni pisarze).
|
Wielu czytelników może czytać jednocześnie. Pisarz wymaga wyłącznego dostępu (ani czytelnicy, ani inni pisarze).
|
||||||
|
|
||||||
Rozwiązanie (first readers-writers):
|
Rozwiązanie (first readers-writers):
|
||||||
─────────────────────────────────────────────────
|
|
||||||
int readers = 0;
|
int readers = 0;
|
||||||
mutex rw_mutex; // pisarz LUB pierwszy/ostatni czytelnik
|
mutex rw_mutex; // pisarz LUB pierwszy/ostatni czytelnik
|
||||||
mutex count_mutex; // ochrona zmiennej readers
|
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)
|
lock(count_mutex) lock(rw_mutex)
|
||||||
readers++ // PISZ (wyłączny)
|
readers++ // PISZ (wyłączny)
|
||||||
if (readers == 1) unlock(rw_mutex)
|
if (readers == 1) unlock(rw_mutex)
|
||||||
lock(rw_mutex) // 1. czytelnik blokuje pisarzy
|
lock(rw_mutex)
|
||||||
unlock(count_mutex)
|
unlock(count_mutex)
|
||||||
// CZYTAJ (wielu jednocześnie!)
|
// CZYTAJ (wielu jednocześnie!)
|
||||||
lock(count_mutex)
|
lock(count_mutex)
|
||||||
readers--
|
readers--
|
||||||
if (readers == 0)
|
if (readers == 0)
|
||||||
unlock(rw_mutex) // ostatni odblokowuje pisarzy
|
unlock(rw_mutex)
|
||||||
unlock(count_mutex)
|
unlock(count_mutex)
|
||||||
─────────────────────────────────────────────────
|
|
||||||
|
|
||||||
Problem: pisarze mogą głodować (readers=0 nigdy nie zachodzi).
|
Problem: pisarze mogą głodować (readers=0 nigdy nie zachodzi).
|
||||||
Rozwiązanie: fairness — kolejka FIFO czytelników i pisarzy.
|
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
|
Alternatywa: semafor(4) — max 4 filozofów próbuje jeść naraz
|
||||||
→ jeden widelec zawsze wolny → brak deadlocka.
|
→ 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
|
### 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:
|
> **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.
|
||||||
│ Mutex = klucz do łazienki (1 osoba) │
|
> Reguła kciuka: sekcja **> 1 μs → MUTEX** (wątek zasypia). Sekcja **< 1 μs → SPINLOCK** (kręci się). **n wątków naraz → SEMAFOR(n)**.
|
||||||
│ Semafor(3) = parking na 3 miejsca (3 samochody naraz) │
|
> Mutex = klucz do łazienki (1 osoba). Semafor(3) = parking na 3 miejsca. Spinlock = obrotowe drzwi.
|
||||||
│ 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
|
### 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)
|
- **„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)
|
- Wątki szybsze bo nie trzeba zmieniać „mieszkania" (TLB flush)
|
||||||
- **4 warunki Coffmana** zakleszczenia: złam jeden → brak deadlocka
|
- **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
|
||||||
|
|||||||
539
pytania/questions/pytanie_09_backup.md
Normal file
@ -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.
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|
**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:
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|
#### 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 (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
|
||||||
|
|
||||||
@ -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.
|
**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)
|
|
||||||
|
|
||||||
**Strumień (stream)** — abstrakcja: nieograniczona (unbounded) sekwencja zdarzeń, każde ze stemplem czasowym. Musisz przetwarzać „w locie" — nie mieścisz wszystkiego w pamięci.
|
**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)
|
- **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).
|
- 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).
|
**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".
|
**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".
|
**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".
|
**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.
|
**Global window** — jedno okno na cały strumień. Trigger decyduje kiedy wyemitować wynik.
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
**True streaming vs Micro-batch:**
|
**True streaming vs Micro-batch:**
|
||||||
@ -61,15 +51,10 @@
|
|||||||
|
|
||||||
**Algorytmy strumieniowe (probabilistyczne):**
|
**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%.
|
**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).
|
||||||
|
|
||||||
100 mln unikalnych URL-i → HyperLogLog odpowiada "~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.
|
**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.
|
**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:**
|
**Late data strategies:**
|
||||||
@ -82,7 +67,7 @@
|
|||||||
|
|
||||||
### Rozwiązania analityczne — przegląd
|
### 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).
|
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ć |
|
| 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" |
|
| **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" |
|
| **Global** | cały strumień | — | trigger-based: „emituj po N zdarzeniach" |
|
||||||
|
|
||||||
**Przykład — Tumbling window (fraud detection):**
|
---
|
||||||
|
|
||||||
Strumień transakcji bankowych, okno = 1 minuta:
|
**Tumbling Window — szczegółowo:**
|
||||||
[14:00–14:01] → 3 transakcje z karty X → OK
|
|
||||||
[14:01–14:02] → 47 transakcji z karty X → ALERT! (>10 = podejrzane)
|
|
||||||
|
|
||||||
**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):
|
**Przykład — fraud detection (tumbling 1 min):**
|
||||||
t=14:05 → avg latency [14:00–14:05] = 120ms ✓
|
|
||||||
t=14:06 → avg latency [14:01–14:06] = 340ms ✗ → alert
|

|
||||||
|
|
||||||
|
**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):**
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
**Session Window — szczegółowo:**
|
||||||
|
|
||||||
|
Rozmiar DYNAMICZNY, zależy od aktywności. Gap (przerwa) definiuje koniec sesji.
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|
**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:**
|
|
||||||
- Okna na **event time** = poprawne biznesowo (kiedy zdarzenie faktycznie nastąpiło)
|
- Okna na **event time** = poprawne biznesowo (kiedy zdarzenie faktycznie nastąpiło)
|
||||||
- Okna na **processing time** = prostsze, ale podatne na out-of-order delivery
|
- 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** 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"):**
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
### Rozwiązanie 2 — Platformy przetwarzania strumieniowego
|
### 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ą.
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
| Cecha | Kafka Streams | Apache Flink | Spark Streaming |
|
| Cecha | Kafka Streams | Apache Flink | Spark Streaming |
|
||||||
|-------|---------------|--------------|-----------------|
|
|-------|---------------|--------------|-----------------|
|
||||||
| **Model** | event-by-event | event-by-event | micro-batch (~100ms) |
|
| **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) |
|
| **Exactly-once** | tak (Kafka TXN) | tak (checkpointing) | tak (WAL) |
|
||||||
| **State management** | RocksDB local | RocksDB + checkpoints | in-memory/external |
|
| **State management** | RocksDB local | RocksDB + checkpoints | in-memory/external |
|
||||||
| **Okna** | tumbling, sliding, session | wszystkie + custom | tumbling, sliding |
|
| **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 |
|
| **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):
|
**True streaming vs Micro-batch — wizualnie:**
|
||||||
Latencja: < 10 ms ← trade fraud, click tracking
|
|
||||||
Semantyka: event-by-event
|
|
||||||
Złożoność: wyższa (watermarks, state, exactly-once)
|
|
||||||
|
|
||||||
Micro-batch (Spark Streaming):
|

|
||||||
Latencja: ~100 ms – sekundy
|
|
||||||
Semantyka: mini-batch (prostsza, batch-like API)
|
|
||||||
Ekosystem: Spark SQL, MLlib → łatwa integracja z ML
|
|
||||||
|
|
||||||
**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
|
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
### 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.
|

|
||||||
|
|
||||||
|
Kluczowe: NIE potrzebujesz osobnego klastra!
|
||||||
|
Skalujesz = dodajesz instancje swojej aplikacji.
|
||||||
|
Partycje Kafki automatycznie rozdzielane między instancje.
|
||||||
|
|
||||||
| Algorytm | Pytanie | Pamięć | Błąd | Przykład |
|
**Przykład — Kafka Streams (zliczanie kliknięć co 5 min):**
|
||||||
|----------|---------|--------|------|----------|
|
|
||||||
| **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 |
|
|
||||||
|
|
||||||
**Dlaczego HyperLogLog zużywa O(1)?**
|
StreamsBuilder builder = new StreamsBuilder();
|
||||||
|
|
||||||
Idea: hashuj każdy element, licz pozycję pierwszego bitu 1.
|
builder.stream("clicks") // input topic
|
||||||
Jeśli widzisz dużo zer na początku → prawdopodobnie dużo unikalnych.
|
.groupByKey() // grupuj po user_id
|
||||||
|
.windowedBy(TimeWindows.of(Duration.ofMinutes(5))) // tumbling 5 min
|
||||||
100 mln unikalnych URL-i:
|
.count() // zlicz
|
||||||
- HashSet: ~800 MB pamięci (8 bajtów × 10⁸)
|
.toStream()
|
||||||
- HyperLogLog: 1.5 KB pamięci, odpowiedź ~100 mln ± 2%
|
.to("click-counts"); // output topic
|
||||||
- Oszczędność: 500 000× mniej pamięci!
|
|
||||||
|
// Deploy = uruchom JAR. Skalujesz = uruchom więcej JARów.
|
||||||
**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)
|
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
### 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.
|

|
||||||
|
|
||||||
| Strategia | Opis | Trade-off |
|
**Exactly-once w Flinku (algorytm Chandy-Lamport):**
|
||||||
|-----------|------|-----------|
|
|
||||||
| **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 semantics** — gwarancja, że każde zdarzenie wpływa na wynik dokładnie raz, mimo awarii:
|
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.
|
||||||
- **At-most-once** — mogą zginąć (szybkie, proste)
|
|
||||||
- **At-least-once** — mogą się zduplikować (retry)
|
|
||||||
- **Exactly-once** — żadnych duplikatów ani strat (checkpoint + transakcje, kosztowne)
|
|
||||||
|
|
||||||
Flink: distributed snapshots (algorytm Chandy-Lamport) → checkpoint co N ms
|
**Przykład — Flink (średnia temperatura sensorów co 10s):**
|
||||||
Kafka Streams: transakcje Kafka (idempotent producer + TX coordinator)
|
|
||||||
Spark: WAL (Write-Ahead Log) + idempotent sinks
|
DataStream<SensorReading> 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.
|

|
||||||
|
|
||||||
Pattern: "Jeśli 3 nieudane logowania z tego samego IP w ciągu 5 minut,
|
ZALETA: ten sam kod co batch Spark → łatwa migracja. WADA: latencja = rozmiar micro-batcha (min ~100ms).
|
||||||
a potem udane logowanie z INNEGO IP → alert: konto przejęte"
|
|
||||||
|
|
||||||
Flink CEP:
|
---
|
||||||
Pattern.<LoginEvent>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: 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:**
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
**Exactly-once — porównanie mechanizmów:**
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|
**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
|
### 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ć
|
### Jak zapamiętać
|
||||||
|
|
||||||
- **4 okna: „TSSG"** — Tumbling, Sliding, Session, Global
|
- **4 okna: „TSSG"** — Tumbling (stały, rozłączne), Sliding (stały+krok, nakładające), Session (gap), Global (trigger)
|
||||||
- **Flink = szybki (true streaming)**, Spark = safe (micro-batch)
|
- **TSSG = „TeSt SG — Testujesz Strumieniowe Grupowanie"**
|
||||||
- **HyperLogLog = „ile unikalnych?" z kilobajtem pamięci**
|
- **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
|
||||||
|
|
||||||
|
|||||||
@ -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.
|
**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.
|
**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
|
3. NMS (Non-Maximum Suppression) → usuń duplikaty
|
||||||
4. Wynik: lista bounding boxów z detekcjami pieszych
|
4. Wynik: lista bounding boxów z detekcjami pieszych
|
||||||
|
|
||||||
**Viola-Jones (2001)** — przełomowy detektor twarzy real-time. Kluczowe innowacje:
|
**Viola-Jones (2001)** — przełomowy detektor twarzy w CZASIE RZECZYWISTYM. Trzy kluczowe innowacje wyjasnione szczegółowo:
|
||||||
- **Haar features** — proste cechy prostokątne (jasne/ciemne regiony)
|
|
||||||
- **Integral Image** — obliczenie dowolnej sumy prostokąta w O(1)!
|
**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.
|
||||||
- **AdaBoost cascade** — kaskada klasyfikatorów: szybkie odrzucenie 99% okien w pierwszych etapach, szczegółowa analiza tylko obiecujących
|
|
||||||
|
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
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||

|

|
||||||
|
|
||||||
**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:
|
**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,
|
Dlaczego tak wolno? Bo CNN liczy cechy na KAŻDYM wyciętym regionie OSOBNO,
|
||||||
choć regiony się częściowo nakładają → redundantne obliczenia
|
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,
|
**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).
|
||||||
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.
|
|
||||||
|
|
||||||
CNN raz na obraz → feature map → ROI Pool 2000 regionów → FC → klasy + bbox
|
**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!
|
||||||
Przyspieszenie: ~2 sec/obraz (vs 50 sec w R-CNN)
|
|
||||||
|
|
||||||
**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
|
Obraz → CNN backbone (np. ResNet) → Feature Map → RPN (proposals) → ROI Pool → FC → klasy + bbox
|
||||||
|
|
||||||
RPN szczegóły:
|
RPN krok po kroku:
|
||||||
- W każdym punkcie feature mapy rozważ k=9 „anchor boxes" (3 rozmiary × 3 proporcje)
|
Feature mapa [40×60×256] ← z backbone
|
||||||
- Dla każdego anchora: P(obiekt) + przesunięcie bbox (Δx, Δy, Δw, Δh)
|
↓ Filtr 3×3 przesuwa się po feature mapie
|
||||||
- Zachowaj ~300 propozycji z najwyższym P(obiekt) → do ROI Pool
|
↓ 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!
|
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.
|
**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.
|
**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ę)
|
Algorytm NMS krok po kroku:
|
||||||
NMS: zachowaj bbox1 (0.95), usuń bbox2 i bbox3 (IoU > 0.5 z bbox1)
|
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:**
|
**Jak zbudować detektor z klasyfikatora? Trzy podejścia (+ bonus):**
|
||||||
1. **Sliding window** — wytnij, sklasyfikuj, NMS. Bardzo wolne.
|
1. **Sliding window** — wytnij, sklasyfikuj, NMS. Bardzo wolne (miliony klasyfikacji).
|
||||||
2. **Region proposals + klasyfikator** — Selective Search generuje ~2000 regionów, sklasyfikuj każdy + NMS. Szybsze.
|
2. **Region proposals + klasyfikator** — Selective Search → ~2000 regionów → klasyfikuj + NMS. Wolne ale działa (= R-CNN).
|
||||||
3. **Fine-tune backbone** — weź pretrained classifier (np. ResNet z ImageNet), dodaj detection head (bbox regression + cls), dotrenuj na danych detekcyjnych. **Najlepsza jakość.**
|
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.
|
||||||
**DETR (DEtection TRansformer, 2020)** — Facebook AI. Transformer zamiast CNN, bezpośrednia predykcja zestawu obiektów (set prediction), bez NMS. Uproszczona architektura.
|
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
@ -354,11 +823,26 @@ Masz wytrenowany klasyfikator (np. ResNet na ImageNet: obraz → „kot"). Jak g
|
|||||||
|
|
||||||
### Etymologia
|
### 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ć
|
### 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
|
- **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)
|
||||||
|
|
||||||
|
|||||||