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

 ( makgab | 2015. május 26., kedd - 19:44 )

Ü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ás megjelenítési lehetőségek

A választott hozzászólás megjelenítési mód a „Beállítás” gombbal rögzíthető.

tobb threadet, vagy tobb processt hasznalsz :)

--
NetBSD - Simplicity is prerequisite for reliability

+1

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.

Mi a különbség a pthread és az openmp között?
Mindegyikkel külön szálban lehet futtatni.

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

Azelőtt is lehetett, a POSIX Threads is szabvány.

Annyira szabványos, hogy Windows (és pl. Sun Solaris) alatt kapásból nincs. Eddig azért macerás volt platform független módon megoldani ezt, ehhez képest a boost/C++ szabványos megoldás tényleg jó.
--
http://naszta.hu

Windows-ra van winpthread (nem próbáltam). Sun Solaris 9 alatt van pthread és ezt használtam is régen:

Sun Microsystems Inc. SunOS 5.9 Generic May 2002
~> ls -l /usr/include/pthread.h
-rw-r--r-- 1 root bin 12296 Apr 6 2002 /usr/include/pthread.h

Újabb Solaris-okban már nincs?

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

"Annyira szabványos, hogy Windows (és pl. Sun Solaris) alatt kapásból nincs"
Ne keverjuk a dolgokat ;) A szabvany nem egyenlo az implementacioval. Attol, hogy a piac nem minden szereploje implementalja azt, attol a szabvany meg szabvany marad.

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.

Pedig lehetseges, mindossze egy ifdef kell, globalis szinten egy WSAStartup()-hoz, indulaskor egyszer es amen. Remelem nagyon nem lepett meg :-)

---
pontscho / fresh!mindworkz

Meg egy másik az errno-hoz... mert az sem működik változtatás nélkül. És persze a select()-et is el lehet felejteni.

O, valoban. Az errno-t lemakroztam, onnantol azert nem volt problema vele, a select() meg koszonte szepen anno, eleg vallalhatoan mukodott. :)

---
pontscho / fresh!mindworkz

"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 

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.

Valóban van a BSD API-nak is olyan része, ami nem hordozható, tehát nem "szabványos". ;)

pthread linkelve nem zavar, hisz az az adott rendszer thread könyvtára. Kódban viszont nem szeretem, mert úgy nem hordozható a kód olyan könnyen.
--
http://naszta.hu

Az igazán nem a POSIX hibája, hogy valami teljesen mást értesz szabványos alatt, mint amit valójában jelent...

Látom, átment a lényeg. :)
--
http://naszta.hu

POSIX és a windows? Na megint tanulok valamit?
Egyébként, ha windows akkor tessék használni a windows API -t. Itt eléggé fundamentális dolgok mellet mindenféle wrapper?

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

Igen, ezért jó a std::thread. ;)
--
http://naszta.hu

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.

Ezt célszerű az os-re bízni, ne akarjon már egy userspace sw scheduler lenni.

É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.

Idézet:
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.

A multithread persze hogy nem gáz. Hacsak abban az értelemben nem, hogy kevésbé biztonságos (közös memória tartomány).
A processzorok kiosztását tartom rázósnak.

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

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.

+1

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