LINUX.ORG.RU

Мне всегда казалось, что несколько потоков должны грузить все ядра процессора...

 , , ,


1

5

Всем привет.

UPD2: добавлены реализации boost и OpenMP, почищен код, добавлены комменты в код, немного переформулировано ТЗ (суть не поменялась).

Признаюсь: первый опыт с многопоточной программой, так что прошу ногами сильно не бить.

Решил написать простую программу генерации простых чисел по методу вычеркивания. В развитии этой темы, написал 6 алгоритмов, 4 из которых многопоточные, близнецы, но используют std:thread (метод Initiate3), std:async (Initiate4), boost (Initiate5), OpenMP (Initiate6). Изначально ожидал, что многопоточные алгоритмы загрузят все ядра процессора на 100% и дадут увеличение скорости работы программы. Результаты удивили:
- Все реализации выполняются примерно за одно время (даже OpenMP).
- Многопоточные реализации не быстрее однопоточной, а иногда даже медленнее!!!
std:thread - грузит одно ядро на 100%
std:async - грузит два ядра, общая нагрузка не более 100% одного ядра.
boost - грузит одно ядро
OpenMP - грузит два ядра (суммарная нагрузка 200%), но при этом выполняется не быстрее (обогреватель воздуха помещения)!

Вопрос 1: почему Initiate3,4,5 не грузит все ядра на полную мощность?
Вопрос 2: почему Initiate6 (OpneMP) грузит все ядра, но пи этом не быстрее?
Вопрос 3: Как получить профит от многопоточности в данной конкретной задаче?

Для тех, кто решится помочь - заранее спасибо, и вот инфа в помощь:

Краткое представление алгоритма. Создается массив, элементы которого соответствует числам 3, 5, 7... (ведь четные числа не простые априори). Если соотв. число простое, то значение элемента массива 1, если не простое - 0 (для чистоты эксперимента специально заменил bool на unsigned char). Заполняет массив функция Initiate, в параметрах которой - максимальное число до которого искать. Initiate1 втупую проверяет каждое число, Initiate2 - при нахождении простого числа проходит массив вперед и убирает (помечает не-простыми) соотв. числа, Initiate3,4,5,6 - то же, но с многопоточностью. MarkNonPrime2 и MarkNonPrime3 - близнецы, но вторая предназначена для запуска в отдельном потоке. Остальное, думаю, будет понятно из кода.

Запускать так:

$ g++ -std=c++11 -lpthread -L/usr/lib -lboost_thread prime.cpp -fopenmp -O0 -o prime && time ./prime

Выбор реализации - в main() вызвать нужный метод InitiateX.

Длительность выполнения программы регулировать изменяя параметр InitiateX в main.

Собственно код (prime.cpp):

#include <iostream>
#include <iomanip>
#include <vector>

#include <thread>

#include <future>

#include <boost/thread/thread.hpp>

using namespace std;

// Maximum threads that could be spawned (except OpenMP)
#define MAX_THREAD 100

class CPrimeNumbers{
public:
  vector<unsigned char> numbers; // Array starts with 3 and has a step of 2: 3, 5, 7, 9 ...
  
  inline uint NumberToIndex(const uint number) {return (number-3)/2;}; // BUG: check whether number is < 3
  inline uint IndexToNumber(const uint index) {return index*2+3;};
  
  void MarkNonPrime2(const uint v,const uint max); // Single thread version: mark all N*v numbers (N - integer, N*v<=max) as non-prime
  static void MarkNonPrime3(CPrimeNumbers *sno, const uint v,const uint max); // Multithreading version: mark all N*v numbers (N - integer, N*v<=max) as non-prime
public:
  // Find all prime numbers - different realizations
  void Initiate1(const uint max=6); // Simple algorithm - checking each number
  void Initiate2(const uint max=6); // Marking-forward algorithm, single thread
  void Initiate3(const uint max=6); // Marking-forward algorithm, multithreading using C++11, std::thread
  void Initiate4(const uint max=6); // Marking-forward algorithm, multithreading using C++11, std::async
  void Initiate5(const uint max=6); // Marking-forward algorithm, multithreading using Boost
  void Initiate6(const uint max=6); // Marking-forward algorithm, multithreading using OpenMP
  
  void Print(); // Outhput prime numbers found
  bool IsPrime(uint number); // Check whether given number is prime
};

bool CPrimeNumbers::IsPrime(uint number)
{
  uint i;
  
  // BUG: check whether number is =0
  if (number<4)
    return true;
  
  if (number%2==0)
    return false;
  
  for (i=0;i<NumberToIndex(number);i++)
    if ( numbers[i]==1 && number%IndexToNumber(i) == 0 )
      return false;
    
  return true;
};

/*static*/ void CPrimeNumbers::MarkNonPrime3(CPrimeNumbers *sno, const uint v,const uint max)
{
  uint n;
  
  n=v*3;
  while (n<=max) {
    sno->numbers[sno->NumberToIndex(n)]=0;
    n+=v*2;
  }
};

void CPrimeNumbers::MarkNonPrime2(const uint v,const uint max)
{
  uint n;
  
  n=v*3;
  while (n<=max) {
    numbers[NumberToIndex(n)]=0;
    n+=v*2;
  }
};

void CPrimeNumbers::Initiate6(const uint max)
{
  uint head,i;
  
  numbers.assign(NumberToIndex(max)+1,1);
  
  for (head=3;head<=max;head+=2) {
    if ( numbers[NumberToIndex(head)]==1 ) { // Is prime/unchecked yet
      if (!IsPrime(head)) { // Is not prime
        numbers[NumberToIndex(head)]=0;
      }
      else{ // Is prime
        /*********** Multithreading using OpenMP ***********/
        #pragma omp parallel
        {
          MarkNonPrime3(this,head,max);
        }
      }
    }
  };
};

void CPrimeNumbers::Initiate5(const uint max)
{
  uint head,i;
  vector<boost::thread> threads;
  
  numbers.assign(NumberToIndex(max)+1,1);
  
  for (head=3;head<=max;head+=2) {
    if ( numbers[NumberToIndex(head)]==1 ) { // Is prime/unchecked yet
      if (!IsPrime(head)) { // Is not prime
        numbers[NumberToIndex(head)]=0;
      }
      else{ // Is prime
        /*********** Multithreading using Boost ***********/
        if ( threads.size()==MAX_THREAD) {
          threads[0].join();
          threads.erase(threads.begin());
        }
        threads.push_back( boost::thread(MarkNonPrime3,this,head,max) );
      }
    }
  };
  
  for (auto t=threads.begin();t!=threads.end();t++)
    t->join();
};

void CPrimeNumbers::Initiate4(const uint max)
{
  uint head,i;
  
  numbers.assign(NumberToIndex(max)+1,1);
  
  for (head=3;head<=max;head+=2) {
    if ( numbers[NumberToIndex(head)]==1 ) { // Is prime/unchecked yet
      if (!IsPrime(head)) { // Is not prime
        numbers[NumberToIndex(head)]=0;
      }
      else{ // Is prime
        /*********** multithreading using C++11, std::async ***********/
        async(launch::async,  MarkNonPrime3,this,head,max );
      }
    }
  };
};

void CPrimeNumbers::Initiate3(const uint max)
{
  uint head,i;
  vector<thread> threads;
  
  numbers.assign(NumberToIndex(max)+1,1);
  
  for (head=3;head<=max;head+=2) {
    if ( numbers[NumberToIndex(head)]==1 ) { // Is prime/unchecked yet
      if (!IsPrime(head)) { // Is not prime
        numbers[NumberToIndex(head)]=0;
      }
      else{ // Is prime
        /*********** Multithreading using C++11, std::thread ***********/
        if ( threads.size()==MAX_THREAD) {
          threads[0].join();
          threads.erase(threads.begin());
        }
        threads.push_back( thread(MarkNonPrime3,this,head,max) );
      }
    }
  };
  
  for (auto t=threads.begin();t!=threads.end();t++)
    t->join();
};

void CPrimeNumbers::Initiate2(const uint max)
{
  uint head,i;
  
  numbers.assign(NumberToIndex(max)+1,1);
  
  for (head=3;head<=max;head+=2) {
    if ( numbers[NumberToIndex(head)]==1 ) { // Is prime/unchecked yet
      if (!IsPrime(head)) { // Is not prime
        numbers[NumberToIndex(head)]=0;
      }
      else{ // Is prime
        /*********** Single thread ***********/
        MarkNonPrime2 (head,max);
      }
    }
  };
  
};

void CPrimeNumbers::Initiate1(const uint max)
{
  uint i;
  uint head;
  
  numbers.assign(NumberToIndex(max)+1,1);
  
  for (head=3;head<=max;head+=2) {
    if (! IsPrime(head) )
      numbers[NumberToIndex(head)]=0;
  };
  
};

void CPrimeNumbers::Print()
{
  uint n;
  
  n=3;
  for (auto i:numbers) {
    if (i) cout << n << " ";
    n+=2;
  }
  cout << endl;
}

int main()
{
  CPrimeNumbers s;
  
  /*********** Choose the algorithm ***********/
  s.Initiate1(100000);
  s.Print();
  
  return 0;
}

★★★★★

Последнее исправление: Kroz (всего исправлений: 5)

Ответ на: комментарий от ziemin

Пока что ни с чем не согласен, кроме отладочных сообщений. Ты прав: они сугубо для меня.

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

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

Ну, что такое ЛОР я прекрасно знаю и осознаю. Просто у меня, как мне кажется, вопрос тоже нубовский.

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

несколько тысяч строк кода, при том, что хуже, у тебе придётся кому-то дать свою программу потестировать
перекомпилирование будет занимать не пол секукнды, а пару минут

На велосипеды машины не ставят катапульты как на реактивные самолеты. Сложность логов, комментариев и т. п. соответствует ТЗ: код не вырастет до тех размеров, чтобы он компилился минуты, да и давать я ему никому не буду.

И, да (с точки зрения расширения кругозора), контр-примеров не увидел.

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

Топикстартеру: 2 тоже простое число;)

IsSimple это учитывает:

if ( number<4 )
    return true;
P. S. 0 (ноль) пока не проверяет - знаю.

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

Я про описание, в коде да, гуд:) Кстати, может указатель на класс передавать? а то создаете копию объекта и передаете в тред.

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

Интересно какой именно индустрией? конторами клепающими сайты? кодерами на пехапе?

Центрами разработки ПО, входящими в Топ100, чьи решения используются на государственном уровне, обслуживают информационные, логистические и финансовые потоки масштабов государств.

Представляю какая простыня кода тут была бы по TDD...

Не представляешь, т.к. ты некомпетентен в TDD.

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

это ещё одно подтверждение того, что затраты при запуске потока превосходят все затраты на вычисления.

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

Я про описание

// Array starts with 3 and has a step of 2: 3, 5, 7, 9


Вроде все ok: начинаем с 3, шаг 2

Кстати, может указатель на класс передавать? а то создаете копию объекта и передаете в тред.

О! Да это баг, который делал функцию MarkSimple3 бесполезной.

Kroz ★★★★★
() автор топика
Последнее исправление: Kroz (всего исправлений: 1)
Ответ на: комментарий от anonymous

Можно ссылку на ТОП100 с подтверждением что там TDD? Здравый смысл человеку дан чтобы отсеивать бесполезную информацию коей и является ваше TDD, можете считать некомпетентным, обычных юнит тестов хватит. А тут действительно в 2-3 раза кода было бы больше.

Я про описание к коду: Краткое представление алгоритма. Создается массив, элементы которого соответствует числам 3, 5, 7... (ведь четные числа не простые априори).

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

Еще к слову: по данным time многопоточный код (Initiate3) работает медленне двух предыдущих алгоритмов (Initiate1 и Initiate2).

Любопытно, а на много медленнее?

AndreyKl ★★★★★
()

не работает ...

ошибся когда убирал дебаг? кинь рабочую версию куда нить.

 ./test

terminate called after throwing an instance of 'std::system_error'
  what():  Operation not permitted
Аварийный останов (сделан дамп памяти)

AndreyKl ★★★★★
()

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

чтобы проверить попробуй задать сильно бОльший максимум. раз в сто что ли. в конце концов если версия верна нагрузка на второе ядро должна возрастать ближе к концу приложения.

больше скажу когда кинешь рабочую версию.

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

зачем, мягко говоря, тебе нужны эти потоки? используй std::future & std::async.

Использование async возымело интересный эффект: теперь ядра грузятся примерно одинаково, но все равно общая нагрузка не превышает 100% одного ядра. Такое впечатление, что где-то стоит какой-то барьер...

Kroz ★★★★★
() автор топика

и честно говоря я не понимаю алгоритма.

вот в методе MarkNonSimple3 ты получаешь v. потом умножаешь это v на три, потом почему то прибавляешь к n v*2 и помечаешь их всех как 0.

т.е. мы помечаем как непростые все числа которые кратны v. это понятно. непонятно почему мы начинаем с n=v*3; а потом прибавляем по v*2. вроде как логика подсказыает что надо начинать с v*2 и идти с шагом v. т.е. n=v*2, n+=v. почему не так? хотя это конечно придирки, всё равно эта часть не очень то работает потому что ты передаешь объект по значению а не по ссылке, но всё таки?

AndreyKl ★★★★★
()
Ответ на: комментарий от Kroz
$ gcc -v
Используются внутренние спецификации.
COLLECT_GCC=gcc
COLLECT_LTO_WRAPPER=/usr/lib/gcc/x86_64-linux-gnu/4.7/lto-wrapper
Целевая архитектура: x86_64-linux-gnu
Параметры конфигурации: ../src/configure -v --with-pkgversion='Ubuntu/Linaro 4.7.2-2ubuntu1' --with-bugurl=file:///usr/share/doc/gcc-4.7/README.Bugs --enable-languages=c,c++,go,fortran,objc,obj-c++ --prefix=/usr --program-suffix=-4.7 --enable-shared --enable-linker-build-id --with-system-zlib --libexecdir=/usr/lib --without-included-gettext --enable-threads=posix --with-gxx-include-dir=/usr/include/c++/4.7 --libdir=/usr/lib --enable-nls --with-sysroot=/ --enable-clocale=gnu --enable-libstdcxx-debug --enable-libstdcxx-time=yes --enable-gnu-unique-object --enable-plugin --enable-objc-gc --disable-werror --with-arch-32=i686 --with-tune=generic --enable-checking=release --build=x86_64-linux-gnu --host=x86_64-linux-gnu --target=x86_64-linux-gnu
Модель многопоточности: posix
gcc версия 4.7.2 (Ubuntu/Linaro 4.7.2-2ubuntu1)

если что

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

вроде как логика подсказыает что надо начинать с v*2

Это будет четное число. Четных чисел в массиве нет. Все четные числа (ну, кроме 2) по определению не простые.
Массив состоит из ряда 3, 5, 7, 9, 11, 13, ...
Допустим нашли число простое число 3. Его не нужно помечать как не-простое. Ок, следующее число 3*2; но в массиве нет четных чисел. Следующее 3*3 - с него и начинаем. 3*3+3 пропускаем потому, что оно четное; значит следующее 3*3+3*2. Ну и т. п.

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

Вот исходник: http://pastebin.com/mXhi1U2z
Запускаю:
$ g++ -std=c++11 -lpthread simple.cpp -o simple && time ./simple

P. S. Поменяй, плиз, внизу s.Initiate4(150000); на s.Initiate3(150000); . Я просто далее экспериментирую, успел добавить четвертую реализацию - многопоточность через std::async . Работает так же, ядра теперь грузит равномерно, но все равно общая нагрузка не превышает 100% одного ядра. Где-то стоит блок.

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

Как специалист по промышленному программированию, подтверждаю - в топике типичный промышленный код, такой же можно увидеть в каждом втором промышленном проекте.

queen3 ★★★★★
()

С boost та же фигня!

boost:thread тоже грузит одно ядро под 100% и все.

Я ничего не понимаю!

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

Если в заголовок добавить
#include <boost/thread/thread.hpp>

, в Initiate3 заменить
vector<thread> threads;
на
vector<boost::thread> threads;

threads.push_back( thread(MarkNonSimple3,this,head,max) );
на
threads.push_back( boost::thread(MarkNonSimple3,this,head,max) );

а в опции компилятора добавить -L/usr/lib -lboost_thread

то эффект будет тот же!!! :( 100% загрузка только одного ядра.
Что это все делает, думаю, понятно. Но я не понимаю почему так!

Kroz ★★★★★
() автор топика
Последнее исправление: Kroz (всего исправлений: 1)
Ответ на: комментарий от Kroz

то эффект будет тот же!!! :( 100% загрузка только одного ядра.

может дело все в простоте кода вызываемого в нитке (быстром исполнении) и сложности кода создающего нитки? для проверки можно усложнить код нитки следующим кодом:

/*static*/ void CSimpleNumbers::MarkNonSimple3(CSimpleNumbers *sno, const uint v,const uint max)
{
  volatile int i,j;
  for(i=0;i<10000;i++)
    for(j=0;j<10000;j++);
  ...

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

OpenMP ждет тебя

Ихахаха, ухахаха !
Написал реализацию с OpenMP. Да, оба ядра теперь грузятся на 100%. Только программа от этого быстрее не работает!!!

Отличная библиотека для обогрева квартиры!!!

Kroz ★★★★★
() автор топика
Последнее исправление: Kroz (всего исправлений: 1)

Мне всегда казалось, что несколько потоков должны грузить все ядра процессора...

мне всегда казалось, что на С++, далеко не всегда 😼

darkenshvein ★★★★★
()

Вопрос 1: почему Initiate3,4,5 не грузит все ядра на полную мощность?

профилирование кода по времени поможет понять проблему.

Вопрос 3: Как получить профит от многопоточности в данной конкретной задаче?

вынести в нитки больше кода?

/*static*/ void CPrimeNumbers::solvePrime3_2(CPrimeNumbers *sno, const uint head, const uint max)
{
  if ( sno->numbers[sno->NumberToIndex(head)]==1 ) { // Is prime/unchecked yet
    if (!sno->IsPrime(head)) { // Is not prime
      sno->numbers[sno->NumberToIndex(head)]=0;
    }
    else{ // Is prime
      MarkNonPrime3(sno,head,max);
    }
  }
};

void CPrimeNumbers::Initiate3_2(const uint max)
{
  uint head,i;
  vector<thread> threads;
  
  numbers.assign(NumberToIndex(max)+1,1);
  
  for (head=3;head<=max;head+=2) {
    if ( threads.size()==MAX_THREAD) {
      threads[0].join();
      threads.erase(threads.begin());
    }
    threads.push_back( thread(solvePrime3_2,this,head,max) );
  }
  
  for (auto t=threads.begin();t!=threads.end();t++)
    t->join();
};

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

короче говоря у меня всё (и новая версия, и старая) валятся на строчке

threads.push_back( thread(&CSimpleNumbers::MarkNonSimple3, this, head, max) );

что у меня надо сказать вызывает некоторый интерес. с чего бы.. идеи?

а, ну да, валится с

terminate called after throwing an instance of 'std::system_error'
  what():  Operation not permitted
AndreyKl ★★★★★
()
Последнее исправление: AndreyKl (всего исправлений: 1)

Потоки должны? Ты должен будешь, за форд фокус и большую часть оставшейся жизни.

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

короче говоря у меня всё (и новая версия, и старая) валятся на строчке

Круто. Я даже не знаю что сказать.

У меня получается скомпилировать версию в шапке (да, обновил, добавил boost, OpenMP) даже так:

$ g++ -std=c++11 -lboost_thread -fopenmp -o prime prime.cpp && time ./prime

Я бы на твоем месте разобрался что у тебя творится.

Да, вот, может поможет:

$ gcc -v
Using built-in specs.
COLLECT_GCC=/usr/i686-pc-linux-gnu/gcc-bin/4.7.3/gcc
COLLECT_LTO_WRAPPER=/usr/libexec/gcc/i686-pc-linux-gnu/4.7.3/lto-wrapper
Target: i686-pc-linux-gnu
Configured with: /var/tmp/portage/sys-devel/gcc-4.7.3/work/gcc-4.7.3/configure --prefix=/usr --bindir=/usr/i686-pc-linux-gnu/gcc-bin/4.7.3 --includedir=/usr/lib/gcc/i686-pc-linux-gnu/4.7.3/include --datadir=/usr/share/gcc-data/i686-pc-linux-gnu/4.7.3 --mandir=/usr/share/gcc-data/i686-pc-linux-gnu/4.7.3/man --infodir=/usr/share/gcc-data/i686-pc-linux-gnu/4.7.3/info --with-gxx-include-dir=/usr/lib/gcc/i686-pc-linux-gnu/4.7.3/include/g++-v4 --host=i686-pc-linux-gnu --build=i686-pc-linux-gnu --disable-altivec --disable-fixed-point --without-cloog --without-ppl --disable-lto --enable-nls --without-included-gettext --with-system-zlib --enable-obsolete --disable-werror --enable-secureplt --disable-multilib --enable-libmudflap --disable-libssp --enable-libgomp --with-python-dir=/share/gcc-data/i686-pc-linux-gnu/4.7.3/python --enable-checking=release --disable-libgcj --enable-libstdcxx-time --with-arch=i686 --enable-languages=c,c++,fortran --enable-shared --enable-threads=posix --enable-__cxa_atexit --enable-clocale=gnu --enable-targets=all --with-bugurl=http://bugs.gentoo.org/ --with-pkgversion='Gentoo 4.7.3 p1.0, pie-0.5.5'
Thread model: posix
gcc version 4.7.3 (Gentoo 4.7.3 p1.0, pie-0.5.5)
$ emerge -pv gcc

These are the packages that would be merged, in order:

Calculating dependencies... done!
[ebuild   R    ] sys-devel/gcc-4.7.3:4.7  USE="cxx fortran mudflap nls nptl openmp (-altivec) -doc (-fixed-point) -gcj -go -graphite -gtk (-hardened) (-libssp) -lto (-multilib) -multislot -nopie -nossp -objc -objc++ -objc-gc -regression-test -vanilla" 0 kB

P. S. Спасибо что пытался помочь :)

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

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

g++ -std=c++11 test4.cpp -o test4 -lpthread


а не той что ты дал. Разница в том что у меня -lpthread в конце, а у тебя в начале.

я с этим приколом встречался уже много раз. но никак руки не доходят разобраться что там к чему. в целом пользуюсь эмпирическим правилом что опции компоновщика надо указывать в самом конце, иначе они не имеют никакого эффекта. запутало тут меня то что у тебя всё работает. но это вдидимо бывает.

ща буду глядеть дальше :)

AndreyKl ★★★★★
()
Последнее исправление: AndreyKl (всего исправлений: 2)
Ответ на: комментарий от AndreyKl

Мне всегда казалось, что несколько потоков должны грузить все ядра процессора... (комментарий)

Он походу прав. Посмотрел только что внимательнее на код с async. Без циклов top с H показывает один поток, загружающий одно ядро на 100%. С цикламы - тысячи их.

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

ты имеешь ввиду что прав анонимус? я пробовал увеличивать нагрузку на тред путём увеличения max-значения в initiate3. эффект примерно тот же, т.е. загружен один процессор. правда появляюсят детали. время от времени становятся загружены оба процессора, но ооочень редко. тут возможны два варианта

1) либо тред не нагружается значительно сильнее от увеличения максимума (вроде как должен ибо надо ведь проходить по всем значениям..)

2) либо там всё не так просто. т.е. дело не в том что мы банально тратим больше времени на создание.

короче ща напишу нормальную версию и кину топикстартеру как пример. а потом изучу повнимательней то что имеем, может напущу валгринд на него.

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

void PrimeNumbers::initiate_with_async(const unsigned max)
{
  unsigned head;
  unsigned i;

  numbers_.assign(number_to_index(max)+1,1);

  vector <future <void> > fs;

  for (head=3; head<=max; head+=2) {
    if (numbers_[number_to_index(head)]==1) { // Is prime/unchecked yet
      if (!is_prime(head)) { // Is not prime
        numbers_[number_to_index(head)]=0;
      } else { // Is prime
        fs.push_back(async(launch::async, &PrimeNumbers::mark_non_prime3, *this, head, max));
      }
    }
  };

  for_each(fs.begin(), fs.end(), [] (future <void> &f) {
    f.get();
  });
};

void PrimeNumbers::mark_non_prime3(const unsigned v,const unsigned max)
{
  unsigned n;

  n=v*3;
  while (n<=max) {
    numbers_[number_to_index(n)]=0;
    n+=v*2;
  }
  volatile int i,j; //*
  for(i=0;i<10000;i++) //* 
    for(j=0;j<10000;j++); //*
};

вот такое даёт адское число процессов. если убрать строки со звёздочкой, будет один поток, загружающий одно ядро на 100%. Но это только факты. Я только предполагаю, что анон прав.

п.с. компилил без -O

nanoolinux ★★★★
()
Последнее исправление: nanoolinux (всего исправлений: 1)
Ответ на: комментарий от nanoolinux

вот такое даёт адское число процессов.

В Initiate3 (реализация с std::thread) добавил механизм с MAX_THREAD только потому, что на больших числах программа вылетала с «Resource temporarily unavailable». Мне показалось, что это защита unix от fork-бомбы. Эффект повторяется, если установить MAX_THREAD в 1000 , и сделать Initiate3(10000).

FYI

Kroz ★★★★★
() автор топика
Последнее исправление: Kroz (всего исправлений: 1)
Ответ на: комментарий от Kroz

да vector, может лучше queue (для thread-ов) в данном случае?

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

чёрд, анонимус. где ж ты был. совсем мы тут все без тебя тупые.

AndreyKl ★★★★★
()

ТС, тебя не смущает что у тебя наиболее ресурсозатратная функция находится в главном потоке?

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

ТС, тебя не смущает что у тебя наиболее ресурсозатратная функция находится в главном потоке?

Это которая?
То, что меня по-настоящему смущает, так это нагрузка только на одно ядро; притом в аккурат под 100%; притом независимо от количества расчетов; притом по факту делая свое дело (меньше вызывается IsPrime для не-простых чисел, а в однопоточной версии (2) - совсем не вызывается, а без проходов вперед (1) - дофига раз вызывается - все правильно) притом не влияя на скорость выполнения программы. Хотя все говорит о том, что потоков плодиться много.

Kroz ★★★★★
() автор топика
Ответ на: комментарий от Kroz
#include <iostream>
#include <vector>
#include <iomanip>
#include <thread>
#include "tbb/concurrent_queue.h"

#define THREADS_CNT 10

using namespace std;
using namespace tbb;


class CSimpleNumbers {
	private:
		concurrent_queue<int> queue;
		bool working = true;
public:
	vector<unsigned char> numbers; // Array starts with 3 and has a step of 2: 3, 5, 7, 9 ...

	inline uint NumberToIndex(const uint number) {return (number-3)/2;}; // TODO: number should not be < 3
	inline uint IndexToNumber(const uint index) {return index*2+3;};
	void MarkNonSimple(const uint v,const uint max);
	void consumer(const uint me, const uint max); //worker
public:
	void Initiate(const uint max=6); // Marking with multithreading
	void Print();
	bool IsSimple(uint number);
};

bool CSimpleNumbers::IsSimple(uint number)
{
	uint i;

	if ( number<4 )
		return true;

	if ( number % 2 == 0 )
		return false;

	for (i=0; i<NumberToIndex(number); i++)
		if ( ( numbers[i] == 1 ) && ( number % IndexToNumber(i) == 0 ) )
		  return false;
	
	return true;
};

void CSimpleNumbers::MarkNonSimple(const uint v,const uint max)
{
  uint n = v*3;
  while (n <= max) {
    numbers[NumberToIndex(n)] = 0;
    n += v*2;
  }
};

void CSimpleNumbers::Print()
{
	uint n = 3;
	for (auto i : numbers) {
		if (i == 1)
		  cout << n << " ";
		n += 2;
	}
	cout << endl;
};

void CSimpleNumbers::Initiate(const uint max)
{
  numbers.assign(NumberToIndex(max) + 1, 1);

	vector<thread> threads;

	for(uint i = 0; i < THREADS_CNT; i++) {
		threads.push_back( thread(&CSimpleNumbers::consumer, this, i, max) );
	}

  for (uint head=3;head<=max;head+=2) {
    if ( numbers[NumberToIndex(head)] == 1 ) // Is simple/unchecked yet
      queue.push(head);
  }

	while(!queue.empty())
		std::this_thread::sleep_for(std::chrono::milliseconds(250));

	working = false;

	for(auto& t : threads)
		t.join();
};

void CSimpleNumbers::consumer(const uint me, const uint max) {
	while(working) {
		int n;
		if(!queue.empty()) {
			if(queue.try_pop(n)) {
				if ( ! IsSimple(n) ) { // Is not simple
		      numbers[NumberToIndex(n)] = 0;
		    } else { // Is simple
		      MarkNonSimple(n, max);
		    }
			}
		} else { //queue.empty()
			std::this_thread::sleep_for(std::chrono::milliseconds(10));
		}
	}
}

int main() {
  CSimpleNumbers s;
  s.Print();
  s.Initiate(150000);
  s.Print();
  return 0;
}


конпеляем
g++ -I/home/myname/tmp/tbb41_20130314oss/includes -std=c++11 testqueue.cpp -o testqueue -L/home/myname/tmp/tbb41_20130314oss/build/linux_intel64_gcc_cc4.7_libc2.15_kernel3.5.0_release/ -ltbb

запускаем
LD_PRELOAD=/home/myname/tmp/tbb41_20130314oss/build/linux_intel64_gcc_cc4.7_libc2.15_kernel3.5.0_release/libtbb.so.2 ./testqueue

не пиши через жопу, пиши номрально. и всё будет работать. (hint: у тебя через жопу, у меня нормально).

твой код буду глядеть завтра. но чует моё сердце там банально выполняется работа только внутри IsSimple. поглядим.

AndreyKl ★★★★★
()
Последнее исправление: AndreyKl (всего исправлений: 1)
Ответ на: комментарий от Kroz
$ time ./simple.3 >simple.3.out ---------------------------
54:51.414693127

real	0m14.731s
user	0m14.148s
sys	0m0.540s
55:06.147527702
$ time ./simple.3_2 >simple.3_2.out -----------------------
55:06.148773745

real	0m11.722s
user	0m15.024s
sys	0m2.044s
55:17.872201307

подробности

anonymous
()
Ответ на: комментарий от anonymous
$ time ./simple.3 >simple.3.out ---------------------------
13:32.897122282

real	0m14.255s
user	0m14.076s
sys	0m0.388s
13:47.153443770
$ time ./simple.3_2 >simple.3_2.out -----------------------
13:47.154690413

real	0m2.302s
user	0m1.188s
sys	0m2.288s
13:49.458212828

подробности:

bool CPrimeNumbers::IsPrime(uint number)
{
  // BUG: check whether number is =0
  if (number<4)
    return true;
  
  if (number%2==0)
    return false;
  
  uint i=3;
  int k = NumberToIndex(number);
  for(auto num = numbers.begin(); num != numbers.end() && i<*num; ++num, i+=2)
    if ( *num==1 && number%i == 0 )
      return false;
    
  return true;
};

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

следующий этап оптимизации, отдавать ниткам диапазон проверяемых чисел, а не одно число.

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

тем не менее, по мере роста исследуемого числа IsPrime становится все сложнее вычислить, а MarkNonPrime - все проще выполнить.

anonymous
()

Добавь в параметры компиляции -O2, будет побыстрее считать.

И про OpenMP - не вижу смысла использовать прагму перед блоком, в котором стоит только вызов функции. На инициализацию потоков время уходит (>1000 flops), а потоки молотят одно и то же скорее всего.

Либо надо разбить на две (или сколько там потоков) секции и каждой выделить примерно поровну работы (#pragma parallel sections и т.д.), либо использовать распараллеливание цикла for, если можно алгоритм переписать с возможностью его использования. И делать #pragma parallel for

И ещё. Куча вложенных вызовов функций для скоростного счета противопоказана.

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

ну и да, я думаю если исправишь код так чтобы поместить IsSimple внурть порождаемых потоков, то распределение по ядрам у тебя будет равномерное.

AndreyKl ★★★★★
()

если тебе всё ещё интересно что там к чему, то вот фотка валгринда.

как и указал анонимус, всё дело в том что основную работу выполняет ф-ция IsSimple.

у тебя получается примрено такая последовательность вызовов

1. выполнить IsSimple для числа 2. в зависимости от результатат IsSimple запустить поток

т.е. получается что ты _для каждого_ порождённого потока запускаешь IsSimple хотя бы один раз. И количество работы не в функции IsSimple несравненно больше чем во всех остальных функциях твоей программы. Получается что всю основную работу ты делаешь в главном потоке программы. А порождённым потокам работы вообще не даешь (т.е. ты даешь, но они завершают её моментально в сравнении с временем выполнеиня IsSimple).

вот и весь фокус. я ж тебе говорил что ошибка детская. и когда анонимус на неё указал, уже не было смысла в общем-то даже валгринд расчехлять. Но так сказать для чистоты эксперимента, пусть уже будет...

AndreyKl ★★★★★
()
Последнее исправление: AndreyKl (всего исправлений: 1)
Ответ на: комментарий от AndreyKl

Спасибо большое. Наконец-то накидали полезной инфы. Как доберусь до компа - вникну, отпишусь.

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