LINUX.ORG.RU

boost::thread VS openMP!!! Кто кого???


0

0

В программе есть большой внешний цикл (~10000 итераций) в который вложены еще некоторые более мелкие циклы с мат вычислениями (~2500 итераций), при попытке раскидать внешний цикл на несколько потоков столкнулись с проблематичной синхронизацией потоков (слишком сильная зависимость между итерациями), поэтому решили «раскидывать» на потоки вложенный цикл.
Но встали перед вопросом чем воспользоваться boost::thread или openMP?

Насчет openMP документации нашел много, и везде указывалось что на разветвление программы и потом обратный сбор уходит довольно много времени (пишут порядка 2000 операций), насчет boost::thread подобных замечаний не нашел. И даже прочитал где-то что мол boost::thread при организации мелких циклов имеет преимущество по производительности по сравнению с openMP. Ну и естественно решил попробовать....

Написал 2 идентичные по объему операций программки:

На openMP:
_______________________________________
#include <iostream>
#include <omp.h>
#include <time.h>
#define Npar 4
#define Nmas 1000
int main(int argc, char* argv[])
{
float* a = new float[Nmas];
float* b = new float[Nmas];
float* c = new float[Nmas];
for (int k = 0; k < 100000; k++)
{
#pragma omp parallel shared(a, b, c) num_threads(Npar)
{
int myid = omp_get_thread_num();
for (int i = myid; i < Nmas; i += Npar)
{
a[i] = (float) i;
b[i] = a[i] * a[i];
c[i] = 0.3 * a[i] - b[i] / a[i];
}
}
}
delete[] a;
delete[] b;
delete[] c;
std::cout << clock() << std::endl;
return 0;
}
_________________________________________

И на boost::thread :
_________________________________________
#include <boost/thread/thread.hpp>
#include <iostream>
#include <time.h>
#define Nmas   1000
#define Npar   4
using namespace std;

void proga(int ind, float* a, float* b, float* c)
{
for (int i = ind; i < Nmas; i += Npar)
{
a[i] = (float) i;
b[i] = a[i] * a[i];
c[i] = 0.3 * a[i] - b[i] / a[i];
}
}

int main(int argc, char* argv[])
{
float* a = new float[Nmas];
float* b = new float[Nmas];
float* c = new float[Nmas];
for (int j = 0; j < 100000; j++)
{
boost::thread my_thread1(&proga, 0, a, b, c);
boost::thread my_thread2(&proga, 1, a, b, c);
boost::thread my_thread3(&proga, 2, a, b, c);
boost::thread my_thread4(&proga, 3, a, b, c);
my_thread1.join();
my_thread2.join();
my_thread3.join();
my_thread4.join();
}
delete[] a;
delete[] b;
delete[] c;
std::cout << clock() << endl;
return 0;
}
________________________________________

Компилятор g++-4.3, ОС Kubunta 9.04,
проц. CPU Intel Core 2 Quad Q8300 2.5ГГц.

Откомпилил обе проги запустил проверить кто же быстрее и на сколько, и в ответ получил замечательные данные:
(программы запускал по 10 раз каждую привожу средние данные)
по данным ф-ции clock():
boost 8700000 (8.7c)
openMP 8800000 (8.8c)

по данным ф-ции time:
boost
real 0m8.588s
user 0m4.616s
sys 0m4.176s
__________________
openMP
real 0m7.438s
user 0m6.828s
sys 0m3.132s
__________________

В сфязи с чем у меня возникает 4 вопроса:
1. кому верить time или clock???
2. так кто же быстрее boost или openMP???
3. для каких случаев обычно используют тот или другой метод распараллеливания???
4. может я что-то не так делаю?

> 1. кому верить time или clock???

clock отдает количество тиков которые поток проводит в активном состоянии. если мерять сколько CPU% пожрал поток - clock. если «реальное время» - time.

про остальные вопросы не знаю

waker ★★★★★
()
Ответ на: комментарий от waker

>clock отдает количество тиков которые поток проводит в активном состоянии

Тогда почему так сильно разняца его значения при разных запусках программы? колеблется от 8300000 до 9500000!!!

minipooh
() автор топика
Ответ на: комментарий от minipooh

> Тогда почему так сильно разняца его значения при разных запусках программы? колеблется от 8300000 до 9500000!!!

потому что многозадачность.

waker ★★★★★
()

for (int i = ind; i < Nmas; i += Npar)

Попробуйте делить вектор не как сейчас, а цельными кусками. Тогда locality лучше.

bvvv
()

Если я ни чего не путаю boost::thread порождает каждый создаваемый поток. OpenMP же создаст один раз потоки (по числу ядер) и потом их будет в нужный момент активировать. Создатели TBB(действующий аналогично) заявляют стократное приемущество по сравнению с системными нитями во времени инициализации.

В твоем случае imho быстрее OpenMP (сравнивай real время из time), но тест imho просто рвет многопоточность на куски. Т.к. действий каждому потоку достается очень мало и плюс imho суровый false-sharing. Дели данные непрерывными кусками и не допускай параллельной записи в близкие участки памяти. Из-за false-sharing прога у тебя работает в разы медленее и непредсказуемо по времени.

P.S. можешь еще протестить Intel TBB, но imho должно быть примерно то же самое.

YesSSS ★★★
()
Ответ на: комментарий от YesSSS

>OpenMP же создаст один раз потоки (по числу ядер) и потом их будет в нужный момент активировать

Ну на сколько я понял он создает столько потоков сколько укажешь, потому что когда я с ним экспериментировал, то создавалось и 20 потоков на 4-х ядрах без проблем. И все FAQи по нему утверждают что потоки создаются непосредственно в момент

#pragma omp parallel...

именно поэтому они не советуют распараллеливание помещать в цикл. Поскольку процесс создания потоков занимает много времени.

А вот про boost почемуто писали наоборот мол быстрое создание потоков малые затраты процессорных ресурсов.... А оказывается что АБСОЛЮТНО такие же, а иногда даже похуже.

действий каждому потоку достается очень мало

сравнивал результат работы на 1 цикле но с 200000000 итераций который параллелил этими 2 средствами, результат тот-же (времена конечно в разы меньше) openMP рвет!!!

minipooh
() автор топика

Мне просто интересно, может я их неправильно сравниваю...

Но скажите мне кто-нибудь какое преимущество у boost-а? когда лучше будет использовать его вместо openMP? или это на вкус и цвет, как говориться, одна херня???

minipooh
() автор топика
Ответ на: комментарий от minipooh

Нити не всегда используются для подобных вычислений. Бустовыми нитями вы можете управлять. openMP сами решают, что и когда им делать. В случаях числодроблений это, как правило, сильно упрощает жизнь.

staseg ★★★★★
()
Ответ на: комментарий от minipooh

> Ну на сколько я понял он создает столько потоков сколько укажешь, потому что когда я с ним экспериментировал, то создавалось и 20 потоков на 4-х ядрах без проблем. И все FAQи по нему утверждают что потоки создаются непосредственно в момент >#pragma omp parallel...

Отпрофилировал одно свое приложение valgrind-ом(--tool=callgrind), ясно видны 3(Core Quad) вызова pthread_create. Попробуйте тоже отпрофилировать и понять сколько их там создается в вашем случае.

именно поэтому они не советуют распараллеливание помещать в цикл. Поскольку процесс создания потоков занимает много времени.

Даже активировать уже созданную нить - весьма дорого, именно поэтому этого не стоит делать часто, да и вычислений на одну нить должно приходиться много. Imho с 100000 тактов уже имеет смысл активировать нити.

когда лучше будет использовать его вместо openMP?

OpenMP - упрощенные(с точки зрения колва кода) нити для простых вычислительных задач, если тебе нужна сложная логика взаимодействия нитей может быть удобнее использовать или boost::threads или tbb.

YesSSS ★★★
()

Кстати вот как имеет смысл переписать твой цикл:

#pragma omp parallel shared(a, b, c) num_threads(Npar)

{ int myid = omp_get_thread_num(); for (int i = myid; i < Nmas; i += Npar) { a[i] = (float) i; b[i] = a[i] * a[i]; c[i] = 0.3 * a[i] - b[i] / a[i]; } }

#pragma omp parallel for for (int i = 0; i < Nmas; i++) { a[i] = (float) i; b[i] = a[i] * a[i]; c[i] = 0.3 * a[i] - b[i] / a[i]; }

Дело в том, что shared подразумевается по умолчанию а число нитей лучше или не задавать вообще(определяется по кол-ву ядер) или задать один раз при старте программы. Кстати в случае этого цикла итерации разобьются непрерывными блоками и не будет суровых проблем с кэшем(false sharing).

YesSSS ★★★
()
Ответ на: комментарий от YesSSS

отпрофилировать попробовал... Действительно openMP создает столько ветвей сколько укажешь в num_threads либо (если не указывать) количество ядер - 1 (поскольку один поток это основная программа), причем создает их с самого начала, даже если функция (или метод) содержащая распараллеливание не вызывается вовсе. А вот boost на каждую инициализацию своего класса создает поток, т.е. столько раз сколько его запускал...

minipooh
() автор топика
Ответ на: комментарий от minipooh

с boost::thread должно быть както так (потоки создаются при старте, а не на каждую итерацию):

#include <boost/thread/thread.hpp>
#include <iostream>
#include <time.h>
#define Nmas 1000
#define Npar 4
using namespace std;

void proga(int pos0, int pos1, float* a, float* b, float* c)
{
	for(int it = 0; it < 100000; ++it)
	{
		for (int i = pos0; i < pos1; ++i)
		{
			a[i] = (float) i;
			b[i] = a[i] * a[i];
			c[i] = 0.3 * a[i] - b[i] / a[i];
		}
	}
}

int main(int argc, char* argv[])
{
	float* a = new float[Nmas];
	float* b = new float[Nmas];
	float* c = new float[Nmas];
	size_t num_threads = boost::thread::hardware_concurrency();
	boost::thread_group threads;
	for (int i=0; i<num_threads; ++i)
		threads.create_thread(boost::bind(&proga, i*Nmas/num_threads, (i+1)*Nmas/num_threads, a, b, c));
	threads.join_all();
	delete[] a;
	delete[] b;
	delete[] c;
	std::cout << clock() << endl;
	return 0;
}

stpg
()
Ответ на: комментарий от stpg

OpenMP удобен тем, что ты не руками управляешь временем создания потоков(в данном примере это не сложно). Т.е. если у тебя есть несколько OpenMP-циклов в разных местах программы тебе не потребуется руками писать переход нитей между ними, с OpenMP они сами заснут после окончания одного цикла и возобновятся в начале следующего.

YesSSS ★★★
()
Вы не можете добавлять комментарии в эту тему. Тема перемещена в архив.