Iterators en generators : hoe je gegevens kunt verwerken met minder geheugengebruik

Iterators en generators : hoe je gegevens kunt verwerken met minder geheugengebruik
3 min lezen

Een script dat het betalingslogboek moest samenvatten, werkte een jaar zonder problemen, voordat het faalde op een te groot bestand. Een tutorial gewijd aan Python iterators en generators laat zien hoe je verwerkingsketens bouwt die gegevens één record per keer lezen, zonder ze allemaal in het geheugen te laden.

  • Een lijst houdt alle records tegelijk in het geheugen, wat bij een bestand van 9 GB tot een MemoryError leidt.
  • Iterators en generators verwerken één record per keer en beperken zo het geheugengebruik.
  • Functies zoals list(), sorted() of het bouwen van een DataFrame laden de volledige stroom toch weer in het geheugen.
  • Globale sortering en exacte deduplicatie vragen een steekproefmethode of een extern hulpmiddel.

In het beschreven geval opende het script het bestand, bouwde een lijst van geanalyseerde records op, filterde de fouten eruit en toonde vervolgens een tabel. Na een wijziging in de logrotatie belandde een week aan verkeer in één enkel bestand van 9 GB. De nachtelijke verwerking werd onderbroken met een MemoryError voordat ook maar een samenvatting was geproduceerd. Volgens de auteur lag de oorzaak niet in de analyselogica: het probleem kwam van de lijst, die dwingt alle records tegelijkertijd in het geheugen te houden.

De werking berust op een eenvoudig contract. Een iterable, zoals een lijst, een tekenreeks of een geopend bestand, kan een iterator leveren, dat wil zeggen een cursor die element voor element vooruitgaat en nooit terugkeert. De for-lus roept deze stappen automatisch aan, en het einde van de reeks wordt aangegeven door de StopIteration-exceptie, wat geen fout is. Een uitgeputte iterator wordt niet gereset: een functie die « niets » teruggeeft bij een tweede leesbeurt is niet per se leeg, hij is simpelweg al verbruikt.

De tutorial raadt aan te beginnen met het bestandsobject, dat al een iterator is. Een bestand lezen met f.readlines() creëert een lijst van alle regels, terwijl for line in f slechts één gebufferde blok per keer leest. Generatorfuncties, die het sleutelwoord yield gebruiken, maken het vervolgens mogelijk de analyse op te delen in benoemde stappen. De instructie yield from koppelt meerdere rotatiebestanden als één enkele stroom aan elkaar, en generatorexpressies, geschreven tussen haakjes, dienen voor kleine tussenliggende transformaties. De auteur wijst op een valkuil: het meest linkse deel van een generatorexpressie wordt onmiddellijk geëvalueerd.

Meten voor je vertrouwt

De tutorial benadrukt ook de punten waar de lijst stiekem terugkeert. Aanroepen als list(), sorted(), een woordenboek dat records groepeert op aanvraag-ID, of het bouwen van een DataFrame brengen de volledige stroom weer terug in het geheugen. Generators zijn ook niet sneller dan lijsten: hun voordeel is een begrensd geheugengebruik en herschikbare stappen. Voor batchverwerking biedt Python 3.12 itertools.batched(), en een equivalent recept op basis van islice wordt gegeven voor eerdere versies.

Om het werkelijke gedrag te controleren, raadt de auteur de module tracemalloc aan, die het door Python toegewezen geheugen meet, zowel de huidige waarde als de piek. Op een synthetisch logboek van 200.000 regels, ongeveer 17 MB groot, bereikt de « lijst »-versie een piek van 198 MB op een laptop, tegenover 0,4 MB voor de stroomversie. De auteur preciseert dat dit de resultaten zijn van een enkele uitvoering, en geen benchmark. Hij herinnert er ook aan dat tracemalloc de toewijzingen van C-extensies en de cache van het systeem niet ziet.

De grenzen van stroomverwerking

Sommige verwerkingen kunnen niet als stroom worden uitgevoerd. Een globale sortering, exacte deduplicatie over het hele bestand of een percentiel berekend over alle latentiewaarden vereisen kennis van alle records. In dat geval noemt de tutorial een benaderend algoritme gebaseerd op een steekproef, of een extern hulpmiddel dat op schijf sorteert.

De tutorial preciseert tot slot dat deze aanpak puur Python blijft. Pandas, Spark, Dask of asynchrone stromen zijn oplossingen voor vergelijkbare problemen, maar zijn niet nodig voor een logbestand, een export of een data dump die op de schijf past maar niet in het werkgeheugen.

S’abonner
Notification pour
guest

0 Commentaires
Le plus ancien
Le plus récent Le plus populaire
0
Nous aimerions avoir votre avis, veuillez laisser un commentaire.x