Главная страница
Top.Mail.Ru    Яндекс.Метрика
Текущий архив: 2009.12.06;
Скачать: CL | DM;

Вниз

Вопрос о использовании Random в многопоточном приложении   Найти похожие ветки 

 
31512 ©   (2009-10-09 21:25) [0]

Здравствуйте, Уважаемые.
Меня интересует опыт использования функции Random в многопоточных приложениях при интенсивном её использовании.
Краткое описание проблемы.
Имеется статистическая модель, использующая метод Монте-Карло и реализованная на языке Delphi. При некоторых конфигурациях модели требуется (для получения хорошей статистики) колоссальное количество экспериментов. Речь идёт о миллиардах испытаний, что, разумеется, не обходится без солидных временных затрат. 3 млд. испытаний занимает в среднем 1 час 25 минут. Таких конфигураций нужно насчитать много. Однако, итоговые статистические матрицы обладают свойством аддитивности, что позволяет накапливать статистику частями. Т.е., если имеется двухпроцессорная машина с Xeon по 4 ядра, то запускаем 8 потоков по 375 млн. испытаний, а потом складываем результат и нормируем на общее число испытаний.
Эксперимент показал, что при таком подходе 3 млд. испытаний накапливается всего за полчаса. Однако расчёт, по полученным таким образом статданным, поплыл. Для одного объекта дал завышенный результат, для другого заниженный. И в общем физически ничем не обоснованный. Считая всё это в одном, главном, потоке приложения результат великолепный.
Тут я вспомним свою битву, когда переписывал модель на С++ для Linux. Копаясь в дебрях документации gcc обнаружил, что генератор случайных чисел в Linux очень навороченный. Там есть функции для получения случайных чисел в разных потоках отдельно (суффикс _r) и в "однопоточных" приложениях отдельно.

Всё тщательно проверено и грешить остаётся только на "проседание" Random при её интенсивном использовании из нескольких потоков.

Randomize вызывается 1 раз при старте программы.

Отсюда и возникают вопросы.
1. Есть ли подводные камни при использовании Random из нескольких потоков одновременно?
2. Нет ли в Windows API "потокобезопастного" Random?


 
KilkennyCat ©   (2009-10-09 21:39) [1]

Random можно считать генератором случайности с большой натяжкой. Для серъезных опытов используют внешние генераторы.


 
palva ©   (2009-10-09 21:40) [2]

1. Конечно есть. Обычно используется конгруэнтный метод, а он предполагает хранение предыдущего выработанного случайного числа. Если вам нужен потокобезопасный метод, то храните это число для каждого потока свое. То есть напишите свою функцию с использованием слова threadvar


 
DVM ©   (2009-10-09 21:44) [3]


> 2. Нет ли в Windows API "потокобезопастного" Random?

GetTickCount + QueryPerfomanceCounter + Загрузка ЦП + свободная память + Количество потоков + Трафик + Движения мыши + Нажатые клавиши + Количество окон и т.д. продолдать можно бесконечно. Берешь все вместе, к этому применяешь что-то вроде синуса и оборачиваешь критической секцией.


 
31512 ©   (2009-10-09 21:50) [4]


> KilkennyCat ©   (09.10.09 21:39) [1]

Сейчас экспериментирую с этим.

> palva ©   (09.10.09 21:40) [2]

threadvar тут не причём. У меня описан класс потока статистики и использую я не переменные, а поля класса.
Вопрос в другом. Если я использую Random во множестве потоков и каждый из потоков интенсивно использует это функцию, то не происходит ли того, то Random начинает выдавать в разных потоках одно и то же число. Именно так это описано в документации Linux.
Как я понял (если всё правильно понял), из своего маленького исследования, Random в Delphi использует CoCreateGUID. B если кванты времени между двумя вызовами Random очень малы, то он генерит число близкое к предыдущему.
Пытаюсь разобраться.


 
DVM ©   (2009-10-09 21:53) [5]


> Random в Delphi использует CoCreateGUID. B если кванты времени
> между двумя вызовами Random очень малы, то он генерит число
> близкое к предыдущему.

CoCreateGUID использует время потому что


 
31512 ©   (2009-10-09 22:00) [6]

С двумя потоками получилось 42 минуты на 3 млд. экспериментов.
8 потоков давали полчаса. Интересный результат.


 
palva ©   (2009-10-09 22:07) [7]


> не происходит ли того, то Random начинает выдавать в разных потоках одно и то же число

Теоретически это вполне возможно.

> threadvar тут не причём

threadvar один из способов решить проблему. У вас генератор внутри класса? Как я понимаю это в разы снижает производительность. Если для такой пустяковой функции, как выработка очередного, числа требуется обратиться к свойству, то это ухудшит производительность раза в два... Но это так, замечание в сторону. Вопрос в том как хранить предыдущее выработанное число свое для каждого потока. Если treadvar не подходит храните его не внутри функции, а вместе с переменными потока. У вас же есть такие переменные (всякие счетчики), которые свои для каждого потока. Вот вместе с ними и храните. И передавайте как дополнительный параметр функции random. Хотя на вашем месте я и функцию такую не писал бы, а прямо вставил формулу в код. Время бы сэкономил.


 
palva ©   (2009-10-09 22:11) [8]


> Random в Delphi использует CoCreateGUID

Если так (с удивлением об этом узнал), то откажитесь от Random Delphi, - сэкономите время. Есть много других отличных и простых генераторов.


 
palva ©   (2009-10-09 22:15) [9]


> Random в Delphi использует CoCreateGUID

Может, Randomize использует? Тогда другое дело.


 
31512 ©   (2009-10-09 22:57) [10]


> palva ©   (09.10.09 22:11) [8]

Скорее я чего-то неправильно понял.


 
TUser ©   (2009-10-09 23:01) [11]

Вообще, сомневаюсь, что проблема в рандоме. ОС переключает потоки, как ей взумеатся, то етсь если ситуация заывисит от порядка работы потоков, то результата должен быть б.м. случаен.

К каким общим ресурсам еще обращаются потоки?


 
31512 ©   (2009-10-09 23:07) [12]


> palva ©   (09.10.09 22:07) [7]

В внутри класса у меня нет генератора случайных чисел. Я использую Random.
В том-то всё и дело. Сейчас изучаю генераторы случайных чисел, которые смог найти в сети. Но все они так или иначе в итоге используют Random.
Или такой вот эксклюзив:

procedure TMaster.DoGenerate(Sender:TObject);
var i:Integer;
   g:array[0..4] of TGUID;
   b:array[0..63] of Byte absolute g;
   AList:TStrings;

 procedure FillRandBools;
 var j,k:Integer;
 begin
   with lbBool do
   begin
     Clear;
     AList:=Items;
   end;

   for j:=Low(b) to High(b) do
   for k:=0 to 7 do AList.Add(TrueFalse[b[j] and (1 shl k) > 0]);
 end;

 procedure FillRandInts;
 var j:Integer;
 begin
   with lbInt do
   begin
     Clear;
     AList:=Items;
   end;

   for j:=Low(b) to High(b) do AList.Add(IntToStr(b[j]));
 end;

begin
 for i:=Low(g) to High(g) do CreateGUID(g[i]);
 FillRandBools;
 FillRandInts;
end;


 
31512 ©   (2009-10-09 23:13) [13]


> TUser ©   (09.10.09 23:01) [11]

Потоки абсолютно независимы.
> то етсь если ситуация заывисит от порядка работы потоков

Никак не зависит. Это просто n независимых задач. По окончании работы потоков матрицы суммируются.
На двух потоках отклонение существенно меньше, чем на восьми.

Идеально только в одном потоке.


> результата должен быть б.м. случаен.


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


 
palva ©   (2009-10-09 23:25) [14]

Я посмотрел в System.pas, как всё это устроено.
Промежуточное значение хранится в переменной
RandSeed: longint
и хранится не потокобезопасно.
Вы можете хранить ее в своих переменных потока и перед обращением к Random войти в критическую секцию, переменную RandSeed записать из своей переменной, а после обращения снова сохранить у себя и выйти из критической секции. Так будет потокобезопасно. Но насколько это скажется на производительности - не знаю.

> Но все они так или иначе в итоге используют Random.

Это вы зря. Ищите сишную реализацию и переделывайте на паскале. А зачем вам это нужно? Вас что, не устраивает линейный конгруэнтный метод?


 
palva ©   (2009-10-09 23:29) [15]

Вот здесь интересные примеры:
http://www.gnu.org/software/gsl/manual/html_node/Random-number-generator-algorithms.html
Но найдете еще, если не будете ограничиваться паскалем.


 
31512 ©   (2009-10-09 23:43) [16]


> palva ©   (09.10.09 23:25) [14]

Ок. Вот что я сделал. Я перенёс всю логику в поток. Загнал туда же генератор случайных чисел из ALGLIB. В принципе это дало больший квант времени между обращениями к Random.
Результат: теперь 3 млд. на восьми ядрах считается не за полчаса, а за 14 минут.
Сходимость результатов:

1.45337418476940E+0002 (8 потоков)/1.45362162532453E+0002 (0 потоков) = 99.982977650385824621397117929928%

Твой подход спытаю непременно чуть позже.


 
palva ©   (2009-10-09 23:54) [17]


> Твой подход спытаю непременно чуть позже.

И не надо. Наверняка медленнее будет.


 
31512 ©   (2009-10-10 10:11) [18]


> palva ©   (09.10.09 23:54)
Это будет не наверняка, а точно медленнее, поскольку каждому потоку придётся ждать, когда любой другой потока не "освободит Random". Поэкспериментировать, однако, тоже полезно будет.



Страницы: 1 вся ветка

Текущий архив: 2009.12.06;
Скачать: CL | DM;

Наверх




Память: 0.53 MB
Время: 0.014 c
2-1255841231
NaRuTo
2009-10-18 08:47
2009.12.06
Фокус 2-х и более TCustomControl ов


2-1256050979
CodeRz
2009-10-20 19:02
2009.12.06
Найти число длиной N


15-1254506831
fics)
2009-10-02 22:07
2009.12.06
Windows&Сom порт


1-1227707159
DmitriyG
2008-11-26 16:45
2009.12.06
На этапе компиляции определить подключен или нет модуль


2-1256020739
123123
2009-10-20 10:38
2009.12.06
ASCII символы