Gepubliceerd op: 12 februari 2019
Expert:
Klaas van Gend MSc
Trainer
Lees meer over Klaas van Gend
Deel

In de praktijk is het schrijven van parallelle software nog steeds lastig. Je blijft tegen onvoorziene problemen aanlopen als je niet elk niveau van het probleem begrijpt, zegt Klaas van Gend.

In 2019 zou Multicore software gemakkelijker dan ooit moeten zijn om te schrijven. Moderne programmeertalen zoals Scala en Rust worden volwassen, programmeerframes worden eenvoudiger te gebruiken en C # en good old C ++ omarmen parallellisme als onderdeel van hun standaardbibliotheken.

In de praktijk is het echter nog steeds een rommelig proces. Het geheel blijkt moeilijk te synchroniseren en als de software eenmaal werkt, draait het meestal niet of nauwelijks sneller op een multicore processor. En om het nog erger te maken, heeft het de neiging om allerlei ongrijpbare fouten aan te tonen.

Parallel programmeren is gewoon een heel lastig onderwerp, waarbij je tegen allerlei subtiele, onverwachte effecten aanloopt als je niet begrijpt wat er op alle niveaus gebeurt, vertelt Klaas van Gend, software architect bij Sioux. Ik heb mensen horen praten over het delen van nodes op een supercomputer met behulp van virtuele machines. Maar die verpesten elkaars processor cache; ze lopen elkaar alleen maar in de weg.’

'At university it was all about Dijkstra, which means mutexes, locks and condition variables. But the moment you turn on a lock, you only ensure that the code is executed on one core whilst the others temporarily do nothing. So, you really only learn how not to program for multicore.'

Volgens Van Gend is het probleem dat veel ontwikkelaars tijdens hun informaticaopleiding geen pedagogisch verantwoorde basis hebben gekregen. Op de universiteit ging het allemaal over Dijkstra, wat betekent mutexen, sloten en conditievariabelen. Maar op het moment dat je een lock inschakelt, zorg je er alleen maar voor dat de code op één core wordt uitgevoerd, terwijl de anderen tijdelijk niets doen. Je leert dus eigenlijk alleen maar hoe je niet moet programmeren voor multicore,” zegt hij.

Daarom heeft Van Gend de multicore training van zijn oude werkgever Vector Fabrics uit de mottenballen gehaald. Tot een paar jaar geleden richtte Vector Fabrics zich op tooling om inzicht te geven in de perikelen van parallelle software. Samen met cto Jos van Eijndhoven en andere medewerkers gaf Van Gend trainingen over dit onderwerp. Het bedrijf ging in 2016 failliet, maar Van Gend heeft zich in zijn huidige baan gerealiseerd dat het probleem nog steeds relevant is. Nadat hij de training opnieuw heeft gegeven bij zijn huidige werkgever, biedt hij deze nu ook aan onder de vlag van High Tech Institute, voor derden.


Klaas van Gend is de docent van de 3-daagse training‘Multicore programmeren in C++‘.

Een probleem op elk niveau

Een van de belangrijkste zaken bij het schrijven van parallelle software is om uit te vinden hoe je het duidelijk kunt laten werken op meerdere niveaus, legt Van Gend uit. Hij maakt dit punt altijd met een eenvoudig voorbeeld: Conway’s Game of Life, de cellulaire automaat waarbij vakjes in een raster bij elke nieuwe ronde zwart of wit worden, afhankelijk van de status van hun directe buren. Op het onderste niveau van je programma moet je controleren wat je buurcellen zijn. Dat kun je doen met twee for-runs/loops. En dan heb je een lus voor een complete rij, en daarboven voor de complete set rijen.’

De meeste programmeurs beginnen te parallelliseren bij die onderste lussen. Dat is heel natuurlijk, want dat is een stuk code dat je nog kunt begrijpen, dat nog in je hoofd past. Maar het is veel logischer om op een hoger niveau te zitten/beginnen en die buitenste lus te nemen. Dan verdeel je het veld in meerdere blokken rijen en is je workload per core veel groter.

Als je de zaken op die manier bekijkt, wordt het al snel duidelijk dat er veel dingen zijn waar je op moet letten. Er zijn ook programma’s waarbij de belasting variabel is. ‘We hebben bijvoorbeeld een oefening om de eerste honderd priemgetallen uit te rekenen. Tussen priemgetal tien en priemgetal negenennegentig zit al meer dan een factor honderd. Dan moet je load balancing berekenen.’

Er zijn ook verschillen in wat je kunt parallelliseren: de data of de taak. ‘Dataparallellellisme is over het algemeen geschikt voor heel specifieke toepassingen, maar anders kom je al snel uit bij een soort decompositie van je taak. Dat kan met een actormodel of met een Kahn-procesnetwerk, maar dataparallellisme kan daar weer onderdeel van zijn. In de praktijk zul je zien dat je altijd op mengvormen uitkomt. Het gaat al een tijdje niet meer alleen om algoritmes; de onderliggende hardware speelt een belangrijke rol. Als de programmeur bijvoorbeeld geen rekening houdt met de cachingmechanismen van de processor, kan het probleem van false sharing ontstaan. Ik heb enorme applicaties op de knieën zien gaan”, zegt Van Gend. Stel dat je twee threads hebt die allebei metrics verzamelen. Als je die rommelig verdeelt, kunnen tellers van verschillende threads in dezelfde cache-regel terechtkomen. De twee processoren moeten dan tegelijkertijd met dezelfde cache werken en je cachemechanisme sleept de regels constant heen en weer. Dat verlaagt de prestaties enorm. Om die reden is Van Gend ook sceptisch over het gebruik van high-level talen in multicore ontwerpen; die hebben de neiging om de details over de geheugenlayout te abstraheren. ‘Met een taal als C+ is het nog heel duidelijk dat je met basisprimitieven bezig bent en dat kun je goed zien. Maar hoogstaande talen scheren vaak over de details van de datatypes heen, waardoor het systeem nooit echt soepel kan draaien.’

'If you only partially understand the model, then you will run into problems. It works well for certain specific situations, but it can’t be used everywhere.'

Van Gend denkt in ieder geval dat nieuwe talen geen wondermiddel zijn tegen het multicore probleem. Ze gaan in de regel uit van een specifieke aanpak die helemaal niet goed /noodzakelijk bij de applicatie hoeft te passen. Talen zoals Scala of Rust leunen zwaar op het actormodel om threading gemakkelijker te maken. Als je het model maar gedeeltelijk begrijpt, dan zul je tegen problemen aanlopen. Het werkt goed voor bepaalde specifieke situaties, maar het kan niet overal gebruikt worden.

De verkeerde aanname

De moderne versies van C+ bieden ook uitbreidingen om parallel programmeren mogelijk te maken. Atomics zijn nu bijvoorbeeld volledig betrokken. Hiermee kun je vaak gegevens uitwisselen zonder iets te stoppen. We werken ook aan een bibliotheek waarin het locken helemaal niet meer zichtbaar is voor gebruikers. Als het nodig is, gebeurt het zonder dat de gebruiker het ziet en ook met de kortst mogelijke scope, zodat de lock zo snel mogelijk wordt vrijgegeven,” zegt Van Gend. Ook hier is het belangrijk om te begrijpen wat je doet. Van Gend is bijvoorbeeld een stuk minder enthousiast over de toevoeging van de ‘execution policies’ aan de standaardbibliotheek in C++ 17. Dit maakt het mogelijk om een reeks basisalgoritmen te gebruiken voor het uitvoeren van algoritmen. Hiermee kunnen een aantal basisalgoritmes zoals find, count, sort en transform parallel worden uitgevoerd door simpelweg een extra parameter toe te voegen in de functieaanroep. ‘Maar dat werkt alleen voor sommige academische voorbeelden; in de praktijk zal het niet werken’, zegt Van Gend. ‘Deze api’s zijn gebaseerd op een verkeerde basisaanname. En in de C # api hebben ze weer dezelfde fout gemaakt.’

Het probleem is dat je met deze benadering alleen afzonderlijke stappen kunt maken. Het stimuleert de individuele parallellisatie van elke bewerking. Je deelt je dataset opnieuw met elke bewerking, doet iets, maakt het dan weer heel en gaat verder met de volgende bewerking. Het is altijd parallel, sequentieel, parallel, sequentieel, enzovoort. Dat is conceptueel heel duidelijk, maar je moet al die tijd wachten tot alle threads klaar zijn en dan doorgaan. Dat is een complete tijdverspilling. Aan de andere kant, met een library zoals Openmp wordt de hele reeks bewerkingen gewoon verdeeld over de threads. Dit betekent dus dat je niet onnodig hoeft te wachten.

'The funny thing is that Microsoft also played a large part in the Par Lab at the University of Berkeley. This has resulted in a fairly large collection of design patterns for parallel programming, which I deal with extensively in the training course.'

De gcc-compiler biedt geen ondersteuning voor deze parallelle functies. Visual Studio wel, omdat de toevoegingen uiteindelijk van Microsoft komen. ‘Het grappige is dat Microsoft ook een grote rol heeft gespeeld in het Par Lab aan de universiteit van Berkeley. Dit heeft geresulteerd in een vrij grote verzameling design patterns voor parallel programmeren, die ik uitgebreid behandel in de training. Microsoft heeft laten zien dat ze precies begrijpen hoe het goed moet.’

Dit artikel is geschreven door Pieter Edelman, tech-redacteur van Bits&Chips.

Recommendation by former participants

By the end of the training participants are asked to fill out an evaluation form. To the question: 'Would you recommend this training to others?' they responded with a 8.6 out of 10.

High Tech Institute organiseert één of twee keer per jaar de training Multicore programmeren in C++. Daarnaast is de training beschikbaar voor in-company trainingen.