Miss, bevor du optimierst: timeit und cProfile, was der GIL wirklich blockiert, die Genügsamkeit von Generatoren und die Speicherfallen von Strings und Slices.
Diese Lektion in Kodokon öffnenNie raten, messen. timeit isoliert einen verlässlichen Mikro-Benchmark: Es schaltet den Garbage Collector ab und wiederholt die Ausführung, um das Rauschen zu glätten. cProfile kartiert die Aufrufe eines ganzen Programms - sortiere nach cumtime (kumulierte Zeit, Unteraufrufe inbegriffen), um die wahren Schuldigen zu erkennen. Die goldene Regel jeder Performance-Arbeit: Die langsame Zeile ist fast nie die, die du vermutest.
python -m timeit "sum(x * x for x in range(1000))"
python -m timeit -s "s = 'abc' * 100" "s.upper()"
python -m cProfile -s cumtime -m timeit "min(range(50))"Der GIL (Global Interpreter Lock) garantiert, dass innerhalb eines Prozesses immer nur ein Thread Python-Bytecode ausführt. Folge: Threads beschleunigen reine Berechnung nicht. Für I/O bleiben sie jedoch perfekt, denn der GIL wird bei Netzwerk- oder Festplattenlesevorgängen freigegeben, ebenso von vielen C-Bibliotheken wie NumPy. Um mehrere Kerne mit Berechnung auszulasten, geh über multiprocessing oder concurrent.futures.ProcessPoolExecutor. Python 3.13 bietet eine experimentelle "free-threaded"-Variante ohne GIL, in der Produktion noch selten.
import sys
squares_list = [x * x for x in range(1_000_000)]
squares_gen = (x * x for x in range(1_000_000))
print(sys.getsizeof(squares_list), "bytes")
print(sys.getsizeof(squares_gen), "bytes")
print(sum(squares_gen))Ein Generator wiegt ein paar hundert Bytes, unabhängig von der Länge der Sequenz: Er speichert nur seinen Ausführungszustand und erzeugt jeden Wert auf Anfrage. Verkette sie zu Pipelines - sum(x * x for x in data) -, um Ströme zu verarbeiten, ohne je eine Zwischenliste zu materialisieren. Ein Kompromiss, den du kennen musst: Ein Generator ist einmalig nutzbar. Einmal erschöpft, erzeugt er nichts mehr, stillschweigend - eine klassische Fehlerquelle, bei der die zweite Iteration "leer" erscheint.
import timeit
def concat(n: int) -> str:
out = ""
for i in range(n):
out += str(i)
return out
def join(n: int) -> str:
return "".join(str(i) for i in range(n))
t1 = timeit.timeit(lambda: concat(20_000), number=10)
t2 = timeit.timeit(lambda: join(20_000), number=10)
print(f"concat: {t1:.3f} s / join: {t2:.3f} s")sys.getsizeof eines Generators winzig im Vergleich zur äquivalenten Liste?''.join(parts) das in einer Schleife wiederholte out += part?