4

Я выполняю операцию, разрешаю ее CalculateSomeData. CalculateSomeData работает в последовательных «поколениях», пронумерованных 1..x. Количество поколений во всем прогоне фиксируется входными параметрами для CalculateSomeData и известно априори. Одно поколение занимает от 30 минут до 2 часов. Некоторая часть этой изменчивости обусловлена ​​входными параметрами и не может контролироваться. Тем не менее, часть этой изменчивости обусловлена ​​такими вещами, как аппаратные возможности, загрузка процессора из других процессов, пропускная способность сети и т. Д. Одним из параметров, который можно контролировать за поколение, является количество потоков, используемых CalculateSomeData. Сейчас это исправлено и, вероятно, неоптимально. Я хотел бы отслеживать время, которое занимает каждое поколение, и затем иметь некоторый алгоритм, с помощью которого я настраиваю количество потоков, чтобы каждое последующее поколение улучшалось по времени вычисления предыдущего поколения (минимизируя время). Какой подход я должен использовать? Насколько применимы генетические алгоритмы? Intuition говорит мне, что диапазон будет довольно жестким - может быть, от 1 до 16 потоков на двухъядерном процессоре с четырьмя ядрами.Алгоритм оптимизации # потоков, используемых при вычислении

любые указатели, псевдокоды и т. Д. Очень ценятся.

+0

Я думаю, вы не используете C# или Microsoft C++, существует TPL (http://msdn.microsoft.com/en-us/library/dd460717.aspx), которые могут координировать ядра потоков. – Iain

+0

Я фактически используя C#/TPL. CalculateSomeData не полностью встречается внутри .net. Я выполняю некоторые .net-взаимодействия с Excel. Я нашел TPL удобным способом добавления параллельной обработки, но для моих конкретных вычислений я обнаружил, что он делает ужасную работу по выбору правильного количества потоков. Обычно это слишком много. Я предпочитаю указывать количество потоков и иметь алгоритм, который изменяет число до тех пор, пока оно не будет оптимизировано. – SFun28

+0

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

ответ

2

Как насчет эволюционного алгоритма.

Начать с предположения. 1 поток на ядро ​​процессора кажется хорошим, но зависит от задачи.

Измерьте среднее время для каждой задачи в генерации. Сравните его со временем, которое было принято предыдущим поколением. (Предположим эффективно бесконечное время и 0 потоков для генерации 0).

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

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

Пока оптимальное количество потоков не слишком близко к 1, вы, вероятно, закончите колебание между 3 значениями, которые все достаточно близки к оптимальным. Вы можете явно обнаружить этот случай и заблокировать себя в центральном значении, если у вас есть большое количество поколений.

+0

Митчелл - вот что я искал! Поскольку мы минимизируем время, я думаю, что мы предполагаем бесконечное время для gen 0. 0 нитей для gen 0 не является строго необходимым, потому что gen 1 всегда будет лучше, чем gen 0 (<бесконечность). Конечно, поскольку вы изначально устанавливали # threads = # core, система должна произвольно решать, увеличивать или уменьшать на 1 для gen 2 (поскольку нет понятия «того же направления, что и вы сделали последний шаг», пока у вас не будет 2 поколения полный). Звучит ли это правильно? – SFun28

+0

Да, ты прав. Бесконечное время для G0 работает лучше. Я сформулировал это так, что мы должны всегда увеличивать потоки на 1 для G2 –

+0

Алгоритм, который вы описываете, звучит скорее как жадный подъем холма, чем эволюционный. В этом случае, вероятно, это хорошее решение, но я не вижу, как этот подход является эволюционным. – JohnIdol

2

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

Если ваши расчеты связаны с ввода-выводами, сетью, синхронизацией или чем-то еще, что блокирует выполнение, вы должны найти предельный ресурс и измерить использование. Вам нужно следить за использованием и медленно добавлять больше потоков, пока использование не приблизится к 100%. У вас должно быть как можно меньше потоков, чтобы насытить ваш лимитирующий ресурс.

+1

Hi Albin - Это кажется довольно сложным по сравнению с измерением времени, которое требуется для поколения, и минимизации этого с помощью алгоритма оптимизации. Расчеты связаны с ЦП, диском, сетью, и мне трудно сказать, какова пропорция и как она изменяется с течением времени. Но я точно знаю одно: чем быстрее поколение работает, тем лучше ... так что решение не должно быть привязано к времени генерации? – SFun28

+0

@ SFun28 - Что произойдет, если вы запустите 2 потока для каждого ядра, вы получаете ~ 100% процессор? –

+0

@ Donal Fellows - я бы предпочел избежать ручной настройки. Я мог бы сидеть там часами или днями, настраивая его. Разумеется, простой алгоритм выполнил бы более эффективную работу, чем при выборе правильных # потоков. – SFun28

1

Вы должны разделить свои поколения на множество мелких задач и поставить их в очередь. Создайте один поток на ядро ​​и попросите каждую нить выполнить задачу, запустите ее до завершения и повторите.

Вы хотите, чтобы у вас было больше задач, чем ядер, чтобы убедиться, что вы не закончите с одной задачей, запущенной в конце поколения, и все остальные потоки простаивают. Это то, что может произойти, если вы установите #tasks = #threads = #cores, как предлагает Альбин (если вы не можете гарантировать, что все задачи занимают ровно столько же времени).

Возможно, вы также не хотите больше потоков, чем ядра. Контекстное переключение не очень дорого, но больший объем кеша, который поставляется с одновременным активностью более чем в # часах, может повредить вам (если в ваших задачах не используется очень мало памяти).

+0

Привет Кит - см. Комментарий к моему вопросу и в ответ на Альбина. Нерест один поток на ядро ​​прост (я на самом деле делаю это прямо сейчас), но я считаю его неоптимальным. – SFun28

+0

Возможно, вы могли бы использовать еще несколько потоков, если части вашего вычисления связаны с IO. Но вам не понадобится намного больше. Что-то вроде #cores + #disk head будет хорошо. –