Russian Qt Forum
Ноябрь 26, 2024, 06:00 *
Добро пожаловать, Гость. Пожалуйста, войдите или зарегистрируйтесь.
Вам не пришло письмо с кодом активации?

Войти
 
  Начало   Форум  WIKI (Вики)FAQ Помощь Поиск Войти Регистрация  

Страниц: 1 ... 4 5 [6] 7 8 ... 13   Вниз
  Печать  
Автор Тема: К вопросу об организации взаимодействия пула производителей и одного потребителя  (Прочитано 66121 раз)
m_ax
Джедай : наставник для всех
*******
Offline Offline

Сообщений: 2095



Просмотр профиля
« Ответ #75 : Сентябрь 16, 2019, 10:13 »

И так, выкладываю алгоритм.
Речь идёт о поиске минимума функции в многомерном пространстве (обычно это 100 - 1000 мерное пространство и более).
Суть алгоритма опишу, для простоты, на примере одномерной задачи (в прикреплённом проекте реализация для многомерного пространства в многопоточном и однопоточной реализации).
Алгорит маботает так:
Код
C++ (Qt)
 
for(uint_type steps = 0; !tol(radius, steps); ++steps)
       {
           mean_x = x;
 
           for (uint_type i = 0; i < point_per_step; ++i)
           {
               std::uniform_real_distribution<real_type> dist(mean_x - radius, mean_x + radius);
               vector_type c = dist(rng);
               real_type cur_val = function(cx);
 
               if (cur_val < min)
               {
                   x = std::move(cx);
                   min = cur_val;
               }
           }
 
           dx = x - mean_x;
           real_type sqr_ds = dx*dx; /* ds^2 = (dx, dx) */
 
           if (sqr_ds < radius*radius*mean_sqr_radius)
               radius *= m_params.alpha;
           else
              radius = std::min(m_params.init_radius, radius*m_params.beta);
 
           observer(x, min, steps);
       }
 
 
Т.е. на каждом шаге, мы генерируем point_per_step точек относительно некоторой mean_x, равномерно распределённых в интервале от [mean_x - radius, mean_x + radius].
Если значение функции в какой из точки оказывается меньше, то мы устанавливаем новое значение минимума и точки минимума.

После того, как было проверено point_per_step точек, мы смотрим как далеко от изначальной точки мы ушли. Если это расстояние меньше чем radius^2 * vector.size/3 то мы  уменьшаем радиус в alpha раз (alpha < 1), в противном случае мы его растягиваем в beta раз (beta > 1).

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

Т.е. алгоритм на входе требует 4 параметра: начальный радиус: init_radius, point_per_step и alpha и beta.  

В проекте две реализации этого алгоритма: многопоточный (класс minsearch) и однопоточный вариант (класс minsearch_st)

Код
C++ (Qt)
#include <iostream>
 
#include "minsearch.h"
#include "model_energy.h"
#include "potential.h"
 
static constexpr unsigned int NAtomsA = 50; // Number of atoms type A
static constexpr unsigned int NAtomsB = 50; // Number of atoms type B
static constexpr unsigned int NAtoms = NAtomsA + NAtomsB; // Total number of atoms
static constexpr unsigned int Dim = 2; // Physical dimension
 
int main()
{
 
   ABModelEnergy<double, NAtomsA, NAtomsB> modelEnergy(std::bind(potential, std::placeholders::_1, 1.0, -10.0, 1.0),
                                                           std::bind(potential, std::placeholders::_1, 1.0, -12.0, 1.0),
                                                           std::bind(potential, std::placeholders::_1, 0.0, -20.0, 4.0));
 
   std::vector<double> x(Dim*NAtoms, 0.0); // The initial phase vector (all atoms are in the zero point now)
 
   double init_radius = 0.1;
   unsigned int point_per_step = 8*10;
   double alpha = 0.9;
   double beta = 2.0;
 
   //thread pool realisation
   specmath::minsearch<double> minsearch(init_radius, point_per_step, alpha, beta);
 
   auto start = std::chrono::high_resolution_clock::now();
 
   const double eps = 1e-6;
   const unsigned long max_steps = 3000000000;
 
   double res = minsearch.find_minimum(modelEnergy, x, specmath::breaker<double>(eps, max_steps));
 
   std::cout << " (multi threads) Energy = " << res << std::endl;
 
   auto stop = std::chrono::high_resolution_clock::now();
 
   auto duration = std::chrono::duration_cast<std::chrono::seconds>(stop-start).count();
 
   std::cout << "duration = " << duration << " sec" << std::endl;
 
   // single thread realisation
   specmath::minsearch_st<double> minsearch_st(init_radius, point_per_step, alpha, beta);
 
   for (auto & xi : x)
       xi = 0.0;
 
   start = std::chrono::high_resolution_clock::now();
 
   res = minsearch_st.find_minimum(modelEnergy, x, specmath::breaker<double>(eps, max_steps));
 
   std::cout << "(single threads) Energy = " << res << std::endl;
 
   stop = std::chrono::high_resolution_clock::now();
 
   duration = std::chrono::duration_cast<std::chrono::seconds>(stop-start).count();
 
   std::cout << "duration = " << duration << " sec" << std::endl;
 
   return 0;
}
 

Выйгрыш во времени с данными настройками, примерно от 5 до 7 раз (есть фактор случайности) на 8 ядрах (AMD Vishera FX-8350).
« Последнее редактирование: Сентябрь 16, 2019, 11:43 от m_ax » Записан

Над водой луна двурога. Сяду выпью за Ван Гога. Хорошо, что кот не пьет, Он и так меня поймет..

Arch Linux Plasma 5
ViTech
Гипер активный житель
*****
Offline Offline

Сообщений: 858



Просмотр профиля
« Ответ #76 : Сентябрь 16, 2019, 13:18 »

Речь идёт о поиске минимума функции в многомерном пространстве (обычно это 100 - 1000 мерное пространство и более).

Имхо, при работе с такими размерностями хорошо бы рассчитывать и выводить примерную общую длительность вычислений по времени. А то может оказаться, что ждать окончания расчётов придётся несколько тысяч лет Улыбающийся.
Записан

Пока сам не сделаешь...
Igors
Джедай : наставник для всех
*******
Offline Offline

Сообщений: 11445


Просмотр профиля
« Ответ #77 : Сентябрь 16, 2019, 13:54 »

Имхо, при работе с такими размерностями хорошо бы рассчитывать и выводить примерную общую длительность вычислений по времени. А то может оказаться, что ждать окончания расчётов придётся несколько тысяч лет Улыбающийся.
Все верно, только не с "такими", а с "любыми", прогон одного теста "на скорость" не должен превышать 10-15 сек, иначе невыносимо. Поэтому мне сразу же пришлось уменьшить max_steps до 1000 (было 3.0e+9). Ладно с исходными данными (4 ядра, clang, release, OSX 10.14.3)

Цитировать
NAtomsA = NAtomsB = 50, point_per_step = 80, max_steps = 1000

 (multi threads) Energy = 3.55041e+09
duration = 4.32 sec
(single threads) Energy = 4.44666e+09
duration = 10.292 sec
scale = 2.38241
Вполне хорошо масштабит, особенно учитывая что OSX держит одно ядро "для себя", т.е. получить масштаб > 3 на 4 ядрах не удается по-любому (это чисто мои наблюдения, ничего не читал). Ну ладно, то подробности моей платформы. А вот воспроизводимость (Energy) все-таки должна быть.

Хорошо, теперь уменьшаем задачу.

Цитировать
NAtomsA = NAtomsB = 16, point_per_step = 16, max_steps = 1000 * 100

 (multi threads) Energy = -16527.9
duration = 6.509 sec
(single threads) Energy = -16761.4
duration = 7.699 sec
scale = 1.18282

 (multi threads) Energy = -17795.5
duration = 3.995 sec
(single threads) Energy = -17198.4
duration = 5.478 sec
scale = 1.37121
И вот уже КПД село вдвое. Еще уменьшаем
Цитировать
NAtomsA = NAtomsB = 8, point_per_step = 8, max_steps = 1000 * 100

 (multi threads) Energy = -7481.85
duration = 2.594 sec
(single threads) Energy = -7155.09
duration = 0.563 sec
scale = 0.217039

 (multi threads) Energy = -6643.89
duration = 0.613 sec
(single threads) Energy = -7320.58
duration = 0.674 sec
scale = 1.09951
Еще село + нестабильность. Или я неправильно уменьшаю? И хотелось бы конечно иметь статистику сколько задач посчитано

И не понял этого
Код
C++ (Qt)
for(uint_type steps = 0; !tol(radius, steps); ++steps)
       {
           mean_x = x;
           std::atomic_int ntests(m_params.point_per_step);
 
           for (uint_type i = 0; i < m_num_threads; ++i)
               results[i] = pool.add_task(std::bind(task, radius, std::ref(ntests)));
 
           for (auto & res : results)
               res.wait();
           ...
 
Зачем подавать число задач == числу ниток и затем всякий раз ждать окончания? Запихивайте все и ждите когда все сварится, затем и очередь делалась.
Записан
m_ax
Джедай : наставник для всех
*******
Offline Offline

Сообщений: 2095



Просмотр профиля
« Ответ #78 : Сентябрь 16, 2019, 14:32 »

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

Цитировать
Или я неправильно уменьшаю? И хотелось бы конечно иметь статистику сколько задач посчитано
Ну это уж слишком до безобразия малое колличество атомов..  Улыбающийся Желательно порядка 1000 атомов в системе иметь)

Цитировать
Зачем подавать число задач == числу ниток и затем всякий раз ждать окончания? Запихивайте все и ждите когда все сварится, затем и очередь делалась.
А как по другому? Да, я могу задать число тасков == числу point_per_step.. Но это не самый оптимальный вариант, поскольку мы здесь нарываемся на возможную ситуацию пробки (когда потоки скапливаются в очередь за задачами). Я не могу двигаться дальше, пока все воркеры не отработали, поэтому и жду..
« Последнее редактирование: Сентябрь 16, 2019, 14:37 от m_ax » Записан

Над водой луна двурога. Сяду выпью за Ван Гога. Хорошо, что кот не пьет, Он и так меня поймет..

Arch Linux Plasma 5
Igors
Джедай : наставник для всех
*******
Offline Offline

Сообщений: 11445


Просмотр профиля
« Ответ #79 : Сентябрь 16, 2019, 15:11 »

Это ещё фигня, там и до суток может время расчётов доходить)
Так что, каждый прогон теста = сутки? Улыбающийся

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

А как по другому? Да, я могу задать число тасков == числу point_per_step.. Но это не самый оптимальный вариант, поскольку мы здесь нарываемся на возможную ситуацию пробки (когда потоки скапливаются в очередь за задачами). Я не могу двигаться дальше, пока все воркеры не отработали, поэтому и жду..
Вот у Вас машина с 8-ю ядрами (8 воркеров). У клиента машина всего с 4-мя, или наоборот с 16. Расчеты/рез-ты ведь от этого зависеть не должны. Поэтому Вы не можете двигаться дальше пока все ЗАДАЧИ не завершены, а сколько воркеров их делало - влияет на время исполнения, но не на рез-ты.

"Пробка" - да, возможна, но она зависит от числа воркеров вкупе с размером задачи - а вовсе НЕ от числа задач. Продолжим пример что Вы давеча приводили. Пусть босс раздает задачи на полчаса. Воркер (добросовестный) через полчаса придет за новой задачей, а в это время босс дает задачу другому, придется ждать. Время воркеров будет тратиться в основном на беготню к шефу. Но это никак не связано с тем что "много задач"
Записан
kuzulis
Джедай : наставник для всех
*******
Offline Offline

Сообщений: 2812


Просмотр профиля
« Ответ #80 : Сентябрь 16, 2019, 15:42 »

Я немного ос стороны зайду (особо не вчитывался). А имеются ли в здешнем "конечном решении" такие фичи, как "дорасчет оставшихся задач" из других ядер?
Например, имеем 4 ядра, на каждое при старте выделено по 100 задач... Пусть первое ядро выполнило свои задачи быстрее всех, а у трех оставшихся ядер осталось, 11, 22, и 33 задачи (от балды). Может ли первое ядро забрать себе еще (отобрать) несколько задач у других, дабы быстрее все посчиталось?

Или данный вопрос не рассматривается в этой теме?    
Записан

ArchLinux x86_64 / Win10 64 bit
Old
Джедай : наставник для всех
*******
Offline Offline

Сообщений: 4350



Просмотр профиля
« Ответ #81 : Сентябрь 16, 2019, 16:29 »

Или данный вопрос не рассматривается в этой теме?    
Рассматривался и пул потоков для этого как раз и предназначен.
Все зависит от пользователя пула, как он разделит задачу на подзадачи и заполнит очередь.
Записан
m_ax
Джедай : наставник для всех
*******
Offline Offline

Сообщений: 2095



Просмотр профиля
« Ответ #82 : Сентябрь 16, 2019, 18:30 »

Цитировать
Так что, каждый прогон теста = сутки?  Улыбающийся
Это нормально. Я уже не помню, когда свой комп в последний раз выключал)

Цитировать
Ну если у Вас такой жирный, шикарный кластер, то проходит что угодно, любой тул. О чем уже не раз говорилось, такой случай просто неинтересно обсуждать.
"Наивное" решение, когда каждый раз создаётся и уничтожается пул потоков работает заметно медленнее..
Но в целом да, я согласен..

Цитировать
Вот у Вас машина с 8-ю ядрами (8 воркеров). У клиента машина всего с 4-мя, или наоборот с 16. Расчеты/рез-ты ведь от этого зависеть не должны. Поэтому Вы не можете двигаться дальше пока все ЗАДАЧИ не завершены, а сколько воркеров их делало - влияет на время исполнения, но не на рез-ты.
И сейчас число воркеров никак на результаты не влияет. Влияет только общее время. Вы задаёте параметр point_per_step и оно всегда отрабатывает ровно столько же раз.
Код
C++ (Qt)
auto task = [&](const real_type & r, std::atomic_int & ntests)
       {
           static thread_local std::random_device rd;
           static thread_local rng_type rng(rd());
           static thread_local std::uniform_real_distribution<real_type> dist;
           typedef typename std::uniform_real_distribution<real_type>::param_type range_t;
 
           // ntests - это и есть атомарный счётчик point_per_step
           while (std::atomic_fetch_sub(&ntests, 1) > 0)
           {
               vector_type cx = mean_x;
               std::transform(mean_x.begin(), mean_x.end(), cx.begin(), [&](const real_type & x0){ return dist(rng, range_t(x0-r, x0+r)); });
               real_type cur_val = function(cx);
 
               std::lock_guard<std::mutex> locker(mutex);
               if (cur_val < min)
               {
                   x = std::move(cx);
                   min = cur_val;
               }
           }
       };
 

Каждая таска берёт себе задачу, декрементируя значение ntests. В результате мы всегда получим point_per_step вычислений на каждом шаге.

Цитировать
"Пробка" - да, возможна, но она зависит от числа воркеров вкупе с размером задачи - а вовсе НЕ от числа задач. Продолжим пример что Вы давеча приводили. Пусть босс раздает задачи на полчаса. Воркер (добросовестный) через полчаса придет за новой задачей, а в это время босс дает задачу другому, придется ждать. Время воркеров будет тратиться в основном на беготню к шефу. Но это никак не связано с тем что "много задач"
Для жирных задач, да, проблема пробок нивилируется (здесь ей можно пренебречь), согласен..

Цитировать
Я немного ос стороны зайду (особо не вчитывался). А имеются ли в здешнем "конечном решении" такие фичи, как "дорасчет оставшихся задач" из других ядер?
Например, имеем 4 ядра, на каждое при старте выделено по 100 задач... Пусть первое ядро выполнило свои задачи быстрее всех, а у трех оставшихся ядер осталось, 11, 22, и 33 задачи (от балды). Может ли первое ядро забрать себе еще (отобрать) несколько задач у других, дабы быстрее все посчиталось?
Да, сейчас так и реализовано, как только таска заканчивает работу она тут же берёт следующую. Т.е. ситуации, когда одно ядро выполнело свои задачи быстрее остальных нет. Поскольку нет как таковых "своих задачь".  Улыбающийся
« Последнее редактирование: Сентябрь 16, 2019, 18:37 от m_ax » Записан

Над водой луна двурога. Сяду выпью за Ван Гога. Хорошо, что кот не пьет, Он и так меня поймет..

Arch Linux Plasma 5
Igors
Джедай : наставник для всех
*******
Offline Offline

Сообщений: 11445


Просмотр профиля
« Ответ #83 : Сентябрь 17, 2019, 09:14 »

Например, имеем 4 ядра, на каждое при старте выделено по 100 задач... Пусть..
Чтобы выделить именно "на ядро" надо сильно постараться, и возможно это не на всех платформах. В (подавляющем) большинстве случаев это не нужно, наверное Вы имели ввиду "по 100 задач на воркера", т.е. на рабочую нитку, а как ОС их будет перебрасывать с одного ядра на другое - это его личное дело.

Но и в этом случае сделать руками "по N на рыло" часто очень непросто. Нужно городить доп структуры, знать число задач и как-то выбрать этот злосчаcтный N. Время выполнения одной задачи может быть любым, напр оказалось рез-т уже в кеше, считать опять не нужно. Да и вообще, увидев текст задачи (пусть всего страничку текста) сможете сказать сколько она будет выполняться? Насколько точен будет этот ответ?  Улыбающийся

Пулы оперируют с теми задачами что кладутся в пул, как говорят, "по одной не ошибешься", нагрузка всегда распределяется автоматычно. Но в этом случае возникает проблема "мелких" задач.  На первый взгляд кажется что получить на 32 ядрах МЕДЛЕННЕЕ чем на одном - ну это надо быть уж совсем криворуким Улыбающийся На самом деле это "элементарно" - просто совать в пул слишком маленькие задачи - и пул будет не ускорять а тормозить. Что считать "мелким" - см мои тесты в этой теме
Записан
Igors
Джедай : наставник для всех
*******
Offline Offline

Сообщений: 11445


Просмотр профиля
« Ответ #84 : Сентябрь 17, 2019, 09:40 »

Код
C++ (Qt)
auto task = [&](const real_type & r, std::atomic_int & ntests)
       {
           static thread_local std::random_device rd;
           static thread_local rng_type rng(rd());
           static thread_local std::uniform_real_distribution<real_type> dist;
           typedef typename std::uniform_real_distribution<real_type>::param_type range_t;
 
           // ntests - это и есть атомарный счётчик point_per_step
           while (std::atomic_fetch_sub(&ntests, 1) > 0)
           {
               vector_type cx = mean_x;
               std::transform(mean_x.begin(), mean_x.end(), cx.begin(), [&](const real_type & x0){ return dist(rng, range_t(x0-r, x0+r)); });
               real_type cur_val = function(cx);
 
               std::lock_guard<std::mutex> locker(mutex);
               if (cur_val < min)
               {
                   x = std::move(cx);
                   min = cur_val;
               }
           }
       };
 

Каждая таска берёт себе задачу, декрементируя значение ntests. В результате мы всегда получим point_per_step вычислений на каждом шаге.
Лучше так
Код
C++ (Qt)
              ...
              real_type cur_val = function(cx);
 
               if (cur_val < min) {
                 std::lock_guard<std::mutex> locker(mutex);
                 if (cur_val < min)
                 {
                     x = std::move(cx);
                     min = cur_val;
                 }
               }
 
Улыбающийся
Записан
m_ax
Джедай : наставник для всех
*******
Offline Offline

Сообщений: 2095



Просмотр профиля
« Ответ #85 : Сентябрь 17, 2019, 10:58 »

Цитировать
Чтобы выделить именно "на ядро" надо сильно постараться, и возможно это не на всех платформах. В (подавляющем) большинстве случаев это не нужно, наверное Вы имели ввиду "по 100 задач на воркера", т.е. на рабочую нитку, а как ОС их будет перебрасывать с одного ядра на другое - это его личное дело.
Да, имеется в виду один воркер выполняет несколько задач, в среднем: point_per_step/num_threads. Воркеров столько, сколько сколько потоков в пуле.
И такой вариант организации будет в целом более "быстрым" чем когда мы в пул кладём point_per_step задач.

Цитировать
Но и в этом случае сделать руками "по N на рыло" часто очень непросто. Нужно городить доп структуры, знать число задач и как-то выбрать этот злосчаcтный N. Время выполнения одной задачи может быть любым, напр оказалось рез-т уже в кеше, считать опять не нужно. Да и вообще, увидев текст задачи (пусть всего страничку текста) сможете сказать сколько она будет выполняться? Насколько точен будет этот ответ?
Да ничего подобного) Число задач N на "рыло"  мы явно и не вычисляем, нам достаточно знать сколько всего задач вообще. И сколько мы организуем воркеров в пуле, которые эти задачи будут решать. 

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

Над водой луна двурога. Сяду выпью за Ван Гога. Хорошо, что кот не пьет, Он и так меня поймет..

Arch Linux Plasma 5
Igors
Джедай : наставник для всех
*******
Offline Offline

Сообщений: 11445


Просмотр профиля
« Ответ #86 : Сентябрь 17, 2019, 11:37 »

И такой вариант организации будет в целом более "быстрым" чем когда мы в пул кладём point_per_step задач.
Даже не зная "какой вариант" можно смело утверждать что он будет хуже чем положить все необходимые point_per_step задач в очередь Улыбающийся

Да ничего подобного)
"Остыньте" Улыбающийся
Число задач N на "рыло"  мы явно и не вычисляем, нам достаточно знать сколько всего задач вообще. И сколько мы организуем воркеров в пуле, которые эти задачи будут решать. 
Это я отвечал на др пост, с Вашей задачей никак не связано. Вам не нужно знать ни число задач ни число воркеров (глухая общая схема).

Вот поэтому сейчас реализовано так, как реализовано  Улыбающийся
Ну так и хорошо  Улыбающийся
Записан
m_ax
Джедай : наставник для всех
*******
Offline Offline

Сообщений: 2095



Просмотр профиля
« Ответ #87 : Сентябрь 17, 2019, 11:45 »

Цитировать
Даже не зная "какой вариант" можно смело утверждать что он будет хуже чем положить все необходимые point_per_step задач в очередь  Улыбающийся
Нет, результаты показывают ровно обратное) И это легко проверить.
Записан

Над водой луна двурога. Сяду выпью за Ван Гога. Хорошо, что кот не пьет, Он и так меня поймет..

Arch Linux Plasma 5
Old
Джедай : наставник для всех
*******
Offline Offline

Сообщений: 4350



Просмотр профиля
« Ответ #88 : Сентябрь 18, 2019, 19:46 »

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

Мне их самому придумать?
Не, уже все придумано.
Ждем.
Записан
m_ax
Джедай : наставник для всех
*******
Offline Offline

Сообщений: 2095



Просмотр профиля
« Ответ #89 : Сентябрь 20, 2019, 20:13 »

Цитировать
Так когда ожидать ваш чудо вариант?  Улыбающийся
Дык, нет другого разумного решения) В контексте данной задачи, всё так или иначе сводится к пулу Улыбающийся
Так что мы здесь не увидим решения чисто на атомиках, без усыпления и пробуждения потоков)

P.S. Задача о лифтах: Самое простое (но не единственное) решение: сделать так, чтоб одна половина лифтов ходила только по чётным этажам, а другая - только по нечётным  Улыбающийся  
« Последнее редактирование: Сентябрь 20, 2019, 20:20 от m_ax » Записан

Над водой луна двурога. Сяду выпью за Ван Гога. Хорошо, что кот не пьет, Он и так меня поймет..

Arch Linux Plasma 5
Страниц: 1 ... 4 5 [6] 7 8 ... 13   Вверх
  Печать  
 
Перейти в:  


Страница сгенерирована за 0.29 секунд. Запросов: 23.