Het parallelliseren van een 'for'-lus in Python voor betere prestaties houdt in dat de iteraties van de lus over meerdere processorkernen worden verdeeld. Er bestaan verschillende benaderingen, elk met zijn eigen sterke en zwakke punten:
1. `multiprocessing` gebruiken: Dit is over het algemeen de beste aanpak voor CPU-gebonden taken (taken die het grootste deel van hun tijd aan berekeningen besteden). Het creëert meerdere processen, die elk een deel van de lus uitvoeren.
```python
multiprocessing importeren
def proces_item(item):
"""De functie die moet worden toegepast op elk item in de lus."""
# Hier vindt u uw code om één item te verwerken
resultaat =item * 2 # Voorbeeld:verdubbel het item
resultaat terug
if __name__ =='__main__':# Belangrijk voor Windows-compatibiliteit
items =lijst(bereik(1000)) # Voorbeeldlijst met items
met multiprocessing.Pool(processes=multiprocessing.cpu_count()) als pool:
resultaten =pool.map(process_item, items)
afdrukken(resultaten)
```
* `multiprocessing.Pool`: Creëert een pool van werkprocessen. `multiprocessing.cpu_count()` bepaalt het optimale aantal processen op basis van de kernen van uw systeem. Indien nodig kunt u dit aantal aanpassen.
* `pool.map`: Past de functie 'process_item' toe op elk item in de iterabele 'items'. Het zorgt voor een efficiënte verdeling van het werk en het verzamelen van de resultaten.
* `if __name__ =='__main__':`: Dit is cruciaal, vooral op Windows, om te voorkomen dat meerdere processen recursief worden gemaakt.
2. `concurrent.futures` gebruiken: Biedt een interface op een hoger niveau voor multiprocessing en threading, wat meer flexibiliteit biedt.
```python
importeer gelijktijdige.futures
def proces_item(item):
"""De functie die moet worden toegepast op elk item in de lus."""
# Hier vindt u uw code om één item te verwerken
resultaat =item * 2 # Voorbeeld:verdubbel het item
resultaat terug
als __naam__ =='__hoofd__':
items =lijst(bereik(1000))
met concurrent.futures.ProcessPoolExecutor() als uitvoerder:
resultaten =lijst(uitvoerder.map(process_item, items))
afdrukken(resultaten)
```
Dit lijkt erg op 'multiprocessing', maar wordt vaak beschouwd als meer Pythonisch en gemakkelijker te gebruiken. `ProcessPoolExecutor` gebruikt processen, terwijl `ThreadPoolExecutor` threads gebruikt (beter voor I/O-gebonden taken).
3. Met `threading` (voor I/O-gebonden taken): Als uw lus veel wachten met zich meebrengt (bijvoorbeeld netwerkverzoeken, bestands-I/O), kunnen threads efficiënter zijn dan processen. De Global Interpreter Lock (GIL) in CPython beperkt echter het echte parallellisme voor CPU-gebonden taken binnen threads.
```python
draadsnijden importeren
def proces_item(item, resultaten):
"""De functie die moet worden toegepast op elk item in de lus."""
# Hier vindt u uw code om één item te verwerken
resultaat =item * 2 # Voorbeeld:verdubbel het item
resultaten.append(resultaat)
als __naam__ =='__hoofd__':
items =lijst(bereik(1000))
resultaten =[]
draden =[]
voor artikel in artikelen:
thread =threading.Thread(target=process_item, args=(item, resultaten))
threads.append(thread)
draad.start()
voor draad in draad:
draad.join()
afdrukken(resultaten)
```
Dit voorbeeld is complexer omdat u threads en een gedeelde resultatenlijst expliciet moet beheren. `concurrent.futures.ThreadPoolExecutor` vereenvoudigt dit aanzienlijk.
De juiste methode kiezen:
* CPU-gebonden: Gebruik `multiprocessing` of `concurrent.futures.ProcessPoolExecutor`. Processen omzeilen de GIL en maken echt parallellisme mogelijk.
* I/O-gebonden: Gebruik `concurrent.futures.ThreadPoolExecutor`. Threads zijn lichter van gewicht dan processen, en de overhead van het wisselen van context is lager. Als de I/O erg traag is, kan dit zelfs met de GIL de prestaties verbeteren.
* Gemengd: Als uw lus zowel CPU-gebonden als I/O-gebonden delen heeft, heeft u mogelijk een meer geavanceerde aanpak nodig, waarbij mogelijk threads en processen worden gecombineerd of asynchrone programmering wordt gebruikt (bijvoorbeeld `asyncio`).
Belangrijke overwegingen:
* Overhead: Het creëren en beheren van processen of threads brengt overhead met zich mee. Parallellisatie levert alleen voordeel op als het werk dat per item wordt gedaan substantieel genoeg is om deze overhead te compenseren.
* Gegevens delen: Het delen van gegevens tussen processen is complexer dan het delen van gegevens tussen threads. Overweeg indien nodig het gebruik van wachtrijen of andere communicatiemechanismen tussen processen.
* Foutopsporing: Het debuggen van parallelle code kan een uitdaging zijn. Begin met kleine voorbeelden en verhoog geleidelijk de complexiteit.
Vergeet niet om uw code te profileren om de prestatieverbetering na parallellisatie te meten. Het is mogelijk dat parallellisatie geen significant voordeel oplevert, of zelfs de zaken vertraagt, als de overhead te hoog is of de taak niet geschikt is voor parallellisatie. |