C/C++ párhuzamos futtatás több magos processzornál

Fórumok

Üdv!

C/C++-ben milyen lehetőségek vannak a többmagos CPU-k kihasználására? (párhuzamos futtatás)
Nincs konkrét feladat, egyelőre csak érdekel a téma.

Egy OpenMP-t találtam erre a célra:
http://en.wikipedia.org/wiki/OpenMP
http://openmp.org

Egy demonstrációs jellegű példát is találtam:

# g++ -Wall -fopenmp test.cpp
#include < iostream >
using namespace std;

#include

int main(int argc, char *argv[])
{
int th_id, nthreads;
#pragma omp parallel private(th_id) shared(nthreads)
{
th_id = omp_get_thread_num();
#pragma omp critical
{
cout << "Hello World from thread " << th_id << '\n';
}
#pragma omp barrier

#pragma omp master
{
nthreads = omp_get_num_threads();
cout << "There are " << nthreads << " threads" << '\n';
}
}

return 0;
}

Szóval milyen lehetőségek vannak a nevezett feladatra? Ki mit használ? Mi a tapasztalat?

Hozzászólások

tobb threadet, vagy tobb processt hasznalsz :)

--
NetBSD - Simplicity is prerequisite for reliability

A közelmúltban egy aritmetikai feladat kapcsán próbáltam ki, hogy mit lehet kezdeni. Igaz C++ helyett C-ben.

- 4 szállal (4 magos CPU)
- folytatás lett volna 1024 szállal (OpenCL GPU-ra)

Ami kellett hozzá:
- thread használata --> pthread.h (és az alábbi szemaforozáshoz semaphore.h)
- input queue + thread szemaforozása (rész)feladat leemeléskor
- output queue + thread szemaforozása (rész)eredmény beírásakor

Eddig nem nehéz, szépen leprogramozható. A valódi nehézséget a kitűzött feladat megfelelő ideig futó részekre való elvi felbontása okozta. Ugyanis

- ha túl apró a részfeladat, akkor nagy a járulékos költsége a queue kezelésnek
- ha túl hosszúra nyúlhat egy-egy a részfeladat, akkor adott esetben elképzelhető hogy ez az extra hosszúra nyúlt részfeladat számítása még fut, míg a több száz másik részfeladat már el is készült és a vár a további feldolgozó erre az egy részeredményre. Illetve felesleges késleltetést (latency) okoz.

Erre az elvi szétbontásra fel kell készülni és sok feladatnál nem is olyan egyszerű.
Viszont a jó hír, hogy sok feladat szépen felosztható, így adja magát a többszálusításhoz.

A kérdés jó. Bár a szál startolás vége így is úgyis clone()+mprotect()+madvise() kernelhívás lesz, a szemaforozásból pedig set_robust_list() és futex() kernelhívás. Tehát nem a C programon belül vagy liben belül intéződik. És ez a C++ 2011 esetére is igaz.

Amit írtam hogy első lépésként kockás papíron azt sakkozd ki, hogyan osztod fel 4 párhuzamos szálra a feladatodat hogy a végén ne legyen egyetlen szálra sok várakozás se rövid részfeladatok miatti túl nagy overhead. Ha sikerül értelmesen felosztani papíron, akkor lehet csak elkezdeni a kódolást.

Tényleg nem a magával a szál elstartolásával és a queue-kkal meg a szemaforinggal van a legnagyobb probléma, hanem az adott feladattípus esetén magának a feladatnak az ügyes szétrobbantásával.

Majdnem semmi. Az OpenMP alja is egy pthread (majd clone()+whatever) tortenet lesz, cserebe nem kell "kezzel" gondolkodnod a thread/semaphore/signaling/mutex managementen, csak a mar fent vazolt feladat szegmentacion, mert a tobbit elintezi a compiler openmp supportja, cserebe kapsz nemi compiler/env fuggoseget.

---
pontscho / fresh!mindworkz

Amikor nekem kellett csinálni, QThread vagy boost::thread volt, csak megnéztem, hogy mi ketyeg a háttérben. Platform független kódban egy-két normális függőség belefér, hogy ne találjuk fel a kereket.

pthread for Win: gondoltam, hogy van, de nagyjából én is boost::thread-be csomagolnám, akkor meg minek.

Szóval ma is tanultam valamit, bár használni valószínűleg nem fogom. :)

Már én is rég lőttem Solarisra, akkor (2002-2003, nyilván akkor sem legfrissebb) mintha nem lett volna.
--
http://naszta.hu

Fura dolog ez. Igazad is van, meg nem is. Igazad van, mert a POSIX része a pthread, ilyen értelemben nem vitázom veled, mert igazad van. Másfelöl nincs, mert nekem az szabványos, amire nyugodtan építkezhetek platform függetlenül. Pl.: a BSD TCP/IP API de facto szabványos volt akkor is, mikor erről nem volt papírja, mert minden platformon a rendszer része volt.
--
http://naszta.hu

Valoban fura dolog ez, annyira, h ha jol csal a memoriam, a winsock (amit altalaban hasznalnak/hasznaltak BSD API cimszoval windowson) elso verziojat megcsak nem is a Microsoft keszitette. Innentol kezdve a winpthreads-szel sincs ilyen gond, legfeljebb csomagolod a binarisod melle.

---
pontscho / fresh!mindworkz

Nincs is nagyon min vitatkozni. Egyebkent a BSD egy jo pelda. Engem pl. meglepne, ha irni tudnal egy par soros programot, ami BSD socketet hasznal, fordul Windowsra es Linuxra es nincs benne ifdef ;) Vagy irj platformfuggetlen kodot, ami a socketpair() fv-t hivja (ez is a BSD API resze, de ketlem, hogy Windowson lenne).

A std::thread-hez visszakanyarodva: orvendetes, hogy a C++ szabvany vegre eljutott ide, de a fuggosegektol nem fogsz olyan gyorsan megszabadulni, mert pl. a GCC Linuxon alapertelmezetten tovabbra is a pthread library-t hasznalja, csak te ezt mar nem latod.

"es nincs benne ifdef" Egy sem! :p Mivel ha van, akkor mar biztosan nem irsz platformfuggetlen kodot. Egyebkent egy nem eleg, mert be kell huznod a megfelelo headert, hivnod kell a WSAStartup()-ot es a vegen elvileg a WSACleanup()-ot is. Tehat minimum harom es ez meg bonyolodhat, ha teljes hibakezelest is akarsz. Mindegy, annyira nem erdekes ;)

van az ilyenre is jopar megoldas, az egyik erdekes az ehhez hasonlo:


home@home:/tmp/test$ cat test.c 
#include <stdio.h>

void
foo(void)
{

        return;
}

int
main(int argc, char **argv)
{
        HOOK_1

        foo();

        HOOK_2
}
home@home:/tmp/test$ cat Makefile 
UNAME_S := $(shell uname -s)
ifeq ($(UNAME_S),Linux)
HOOK_1= 'printf("linux\n");'
HOOK_2= 'return (2);'
else
HOOK_1= 'return (1);'
HOOK_2= ''
endif

CFLAGS+= -DHOOK_1=${HOOK_1} -DHOOK_2=${HOOK_2}

all:    test

viszont ezt meg lehet oldani szebben is, ha kiszervezed a hook-okat kulon platform specifikus fileokba es a megfelelohoz linkelsz.

Vmit szerintem felreertettel. Nem az volt a kerdes, hogyan lehet a platformfuggo kodreszeket implementalni, hanem hogy lehet-e a BSD API hasznalataval platformfuggetlen kodot irni, mivel naszta ezt hozta fol "szabvanyositott" peldakent ;) A BSD API egy de facto szabvany, nem kotelez semmire, ebbol kifolyolag adodnak elteresek az implementacioban az egyes platformok kozott, lasd pl. Windows. Ennyi.

Azt lehet "szabályozni", hogy egy szál melyik magra kerüljön? Vagy ezt az OS kezeli?

Ha pthreads-et használsz és nem mondod meg pontosan, melyik magra kerüljön, akkor a kernel fogja eldönteni magától és az egyenletes terhelés érdekében ide-oda pakolgatja menet közben a magok között (legalábbis ezt láttam a htop-ban). Ha magad akarod szabályozni, akkor lásd pthread_setaffinity_np(...) - nekem bevált.

Én eddig pthreads-et használtam, de egy ideje szeretném kipróbálni az OpenMP-t is. Ahogy én látom (javítsatok ki, ha tévedek), az OpenMP egy olyan megközelítést próbál adni a párhuzamos végrehajtáshoz, amit egyszerűbb bizonyos feladatokra használni, mint a pthreads-et. Pl. van egy függvényed, és azon belül egy ciklusod, és szeretnéd, ha ez a ciklus egyszerre több magon futna le, akkor OpenMP-vel ezt könnyen meg tudod oldani, míg pthreads-el sokkal macerásabb lenne, mert az alapvetően az 1 szál == 1 függvény megközelítést használja. Persze valószínűnek tartom, hogy az OpenMP valahol belül pthreads-jellegű hívásokra vezet vissza, de elrejti a programozó elől a sok szöszmötölést ;)

Nincs sok tapasztalatom a témában.
Amit viszont tudok, az az hogy "szűk keresztmetszet" általában nem a CPU hanem a memória, abban az értelemben, hogy akár multi- thread vagy process. Ráadásul (valószínűsítem) hogy marakodás tárgya, nem a memória hanem a cache. Így a kérdés még bonyolultabbá válik, hiszen nem pusztán a processzor mips a fontos hanem, hogy mennyi programot kell berántani a cache -be, milyen sűrűn kell váltogatni.
A hosszadalmas aritmetikai feladatok ráadásul a lebegőpontos aritmetikai magot mozgatják, ahol a számábrázolás is extra.
Mindehhez tessék hozzáadni, hogy az operációs rendszer mi minden kezel, csinál a háttérbe.
Itt jön az hogy gőzöm nincs mi alapján osztja ki a magokat az operációs rendszer - erre is figyelemmel kell lenni. Ebbe belenyúlni eléggé meredek dolognak tűnik, már ha az overál stabilitás fontos.
Szerintem a felhasználói programok szintjén nem hoz akkora nyereséget a dolog, mint amennyi munkát kell belefektetni és kockázatot vállalni.
Ha nem elég egy gép kapacitása, legyen kettő vagy több gép, esetleg be lehet fogni a GPU -t (vagy több GPU majdnem = több gép). Laza csatolású multiprocesszoros rendszerek.
Játéknak érdekes és tanulságos lehet, de akkor először a kernelt kell tanulmányozni. A különféle ráfejlesztett eszközök csak elfedik a képet.

* Én egy indián vagyok. Minden indián hazudik.

Ebbe belenyúlni eléggé meredek dolognak tűnik

Nem teljesen értem, itt mire gondolsz.

Remélem, nem arra, hogy a program többszálúsítása a gáz, hanem inkább a processzorhoz rendelés.

Annak pont az az előnye, amit írtál: ha a szál egyszer az egyik és egyszer a másik processzoron fut, akkor mindenhová újra be kell tölteni az adatokat. Ha ugyanazon a magon fut egymás után kétszer, akkor simán lehet, hogy a cache-ben még benne van az, ami kell.

Ettől függetlenül elsőre biztos én is rábíznám az oprendszerre, ossza csak be magának ahogy akarja.

Azert talaltak ki a kernelt, meg a numa-t, meg az smp-t, meg a tobbi ilyen uri huncutsagot, h az esetek nagyon nagy tobbsegeben ne is kelljen ezzel foglalkozni. Foleg ha valaki OpenMP-re epitkezik. Es ugyanezen esetek tobbsegeben nem is fontos a user-level smp tuning, mert nem nyer annyit vele az ember, mint amennyi munkat bele kellene fektetni. Tobbek kozott ezert is tud terjedgetni az OpenMP/TBB/whatever. A fent emlitett vandorlasi problema nagyjat is megoldja a L1 alatti cache, amibol manapsag mar ujra van az L2 mellett L3, tipustol fuggoen L4. Igy az ezekhez kepest eleg kis meretu L1 cache-hit problemaja mar nem olyan husbavago, h meg is erje szuttyogni ezzel.

---
pontscho / fresh!mindworkz

Tanulási célból, kalandvágyból érdemes ilyennel foglalkozni. Jól jöhet ha érted mi is zajlik a háttérben.
Viszont ha megbízható applikációkat akarsz akkor nem szabad az oprendszer dolgába belenyúlni, hacsak nincs valami spec. vasad - driver.
Az én gyakorlatomban fontosabb a megbízhatóság mint hogy az utolsó mips -ig facsarjuk ki a processzort.

* Én egy indián vagyok. Minden indián hazudik.

Inkább a célfeladat felől érdemes ezt megközelíteni:
-ha egyszerűen egy gépen szeretnéd kihasználni az összes magot a felhasználói programodban, akkor pthread, openmp esetleg tbb
-ha van egy nagy számításigényű tudományos feladatod (én ilyenekkel foglalkozom) akkor érdemes lehet több gépet, esetleg GPU-t bevonni a játékba. Ehhez lehet jó az MPI (ez egy szabvány) illetve ennek adott implementációi (OpenMPI, MPICH).
-ha olyan a feladat akkor lehet, hogy van hozzá függvénykönyvtár ami eleve használja az MPI-t és így tud több szálon/processzoron futni (pl. PETSc)

ahogy már többen írták, mindegyik esetben előbb ülj le gondolkodni, mert lehet, hogy a probléma jellegéből adódóan nem is érdemes beleölni az időt a párhuzamosításba.

Ha olyan programot akarsz írni, ami csak a lokális gépen levő CPU core-okat használja ki, akkor
- Az OpenMP egy jó választás lehet, viiszont nagyon oda kell figyelni a paraméterezésére, mert rossz paraméterekkel a for ciklusod simán lassabban fog futni, mintha egy szálon menne (gondolok a cache-hit/cache-miss értékekre).
- Inkább foglalkoznék a CUDA/OpenCL iránnyal, amivel - szerintem - jobban párhuzamosíthatóak a nagy számításigényű feladatok.

Ha olyan programot akarsz írni, ami egy gépen is több szálon tud futni, de akár több gépen is tud futni egyszerre, akkor
- javaslom, hogy az MPI irányba menj el. Azon belül is az OpenMPI (nem OpenMP !) egy elég kiforrott megvalósítása az MPI-nak.
Az MPI-nak az a nagy előnye, hogyha egyszer megírod a programot, akkor azt egy gépen is le tudod futtatni több szálon, de olyan paraméterezéssel is el lehet indítani, hogy a szálaid több gépen futnak. A paraméterezést persze nem neked kell implementálni, hanem az MPI részeként levő mpirun utasítást kell felkonfigurálni.
Pár dologra azért itt is figyelni kell, főleg arra, hogy a szálak között minél kevesebb adatot kelljen másolni.

Elég általános a kérdés, sokféle feladatra van sokféle megoldás.
Elég alacsony szintűeket mondtak neked eddig.
Egy magasabb szintű keretrendszer pl. az ACE.

Ez a leghülyébb első openmp példa amit valaha láttam. Inkább nézz körül a parallel for háza táján...

Ha c++, akkor a pthread-et felejtsd el. std::async, std::future kell neked, lehetőleg c++17 feature-ökkel (.then() és társai). Legújabb fordítók talán tudják, boost biztosan. Ez olyan mid-level. IO külön threadbe rakására, GUI thread felszabadítására, stb.

Low-level maszatolásra std::thread, std::mutex és társai. Tanuláshoz jók, érteni nem árt mi történik, való életben ritkán kell ilyen alacsony szinten szórakozni.

High-level dolgokhoz (parallel for, pipeline, stb.) tbb. Numerikus számításokhoz én ezt használom.

OpenMP-t személy szerint nem szeretem, nem szabványos c++, és nem is látsz bele mit is csinál.

"...handing C++ to the average programmer seems roughly comparable to handing a loaded .45 to a chimpanzee." -- Ted Ts'o