AlgoDatSoSe26/praktika/05_b_baum/aufgabe3_disk_io.py
2026-05-18 14:48:56 +02:00

89 lines
3.2 KiB
Python

import sys
import os
_root = os.path.abspath(os.path.join(os.path.dirname(__file__), '..', '..'))
_l06 = os.path.join(_root, 'vorlesung', 'L06_b_baeume')
if _l06 not in sys.path: sys.path.insert(0, _l06)
if _root not in sys.path: sys.path.insert(0, _root)
import matplotlib.pyplot as plt
from b_tree import BTree
from b_tree_node import BTreeNode
from utils.algo_context import AlgoContext
from utils.algo_array import Array
def count_loads(node: BTreeNode) -> int:
if node is None:
return 0
total = node.loaded_count
for child in node.children:
total += count_loads(child)
return total
def count_saves(node: BTreeNode) -> int:
if node is None:
return 0
total = node.saved_count
for child in node.children:
total += count_saves(child)
return total
N = 500
orders = [2, 3, 5, 10, 20]
stats = {}
ctx = AlgoContext()
base = Array.random(N, -1000, 1000, ctx)
for m in orders:
ctx.reset()
tree = BTree(m, ctx)
for cell in base:
tree.insert(cell)
stats[m] = {
"comparisons": ctx.comparisons,
"loads": count_loads(tree.root),
"saves": count_saves(tree.root),
"height": tree.height(),
}
print(f"{'m':>4} {'Vergleiche':>12} {'Loads':>8} {'Saves':>8} {'Höhe':>6}")
for m, s in stats.items():
print(f"{m:>4} {s['comparisons']:>12} {s['loads']:>8} {s['saves']:>8} {s['height']:>6}")
fig, axes = plt.subplots(2, 2, figsize=(10, 8))
labels = ["comparisons", "loads", "saves", "height"]
titles = ["Vergleiche", "Disk-Loads", "Disk-Saves", "Baumhöhe"]
for ax, label, title in zip(axes.flat, labels, titles):
ax.plot(orders, [stats[m][label] for m in orders], marker="o")
ax.set_xlabel("Ordnung m")
ax.set_ylabel(title)
ax.set_title(title)
plt.tight_layout()
plt.show()
# ── Antworten zu den Analysefragen ───────────────────────────────────────────
# Warum sinkt die Zahl der Disk-Zugriffe mit wachsendem m?
# Größeres m → mehr Schlüssel pro Knoten → weniger Knoten insgesamt →
# geringere Baumhöhe. Jeder Einfügepfad durchläuft weniger Knoten,
# also werden weniger load()/save()-Aufrufe ausgelöst.
# Warum steigen die internen Vergleiche mit wachsendem m?
# Die Suche innerhalb eines Knotens ist immer linear: bis zu 2m-1 Schlüssel
# werden verglichen. Die Gesamtkosten pro Einfügung betragen daher
# O(m) · O(log_m n) = O(m / log(m) · log n).
# Da m/log(m) monoton wächst, steigen die Vergleiche mit jedem größeren m —
# nicht ab einem Kipppunkt, sondern kontinuierlich. Das ist der grundlegende
# Trade-off: mehr m spart Disk-I/O, kostet aber mehr CPU-Vergleiche.
# Was bedeutet das für die Wahl von m bei Blockgröße 4 KB?
# Ein Knoten enthält 2m-1 Integer-Schlüssel (je 8 Byte) und 2m Kindzeiger.
# Die Kindzeiger sind Blocknummern auf dem Speichermedium (je 8 Byte) —
# also keine Speicheradressen, sondern Verweise auf andere Disk-Blöcke.
# m wird so gewählt, dass ein Knoten genau in einen 4-KB-Block passt:
# (2m-1) · 8 + 2m · 8 = 4096
# 8 · (4m - 1) = 4096 → m ≈ 128
# Disk-I/O-Kosten dominieren gegenüber CPU-Vergleichen → großes m ist optimal.