В ответ Ивану.
Да вроде везде сделано одинаково. Надо бы, конечно, "причесать" и поприличней сделать, а то сплошной Copy-Paste. Но, тем не менее, мог где-то и ошибиться. Так что взгляд со стороны будет не лишним.
В управляющем потоке есть такое объявление:
PT : array of TSingleCoreTester; // массив потоков для параллельного теста
Далее процедура для запуска этих потоков:
procedure TTestControl.TestInt64Parallel;
var
j : integer;
begin
PrepareMessage('Testing thread has started test PARALLEL INT64');
if not(Terminated) then
begin
id := 0;
Prefix := 'PINT64DIV';
SetLength(PT, ProcessorCount);
for j := 0 to High(PT) do
PT[j] := TSingleCore_Euclid64.Create(Self, MaxInt, MaxInt + LC[id]);
LC[id] := LC[id] * ProcessorCount;
for j := 0 to High(PT) do
PT[j].Start;
Sleep(500);
WaitForThreadsFinished;
Synchronize(ShowIntResults);
Synchronize(WriteIntResult);
PrepareMessage('Testing thread has finished testing INT64DIV.');
end;
// здесь аналогичный код для бинарного варианта
PT := nil;
end;
WaitForThreadsFinished тупо ожидает, пока все вычислительные процессы закончатся:
procedure TTestControl.WaitForThreadsFinished;
var
Fin : boolean;
w : integer;
begin
Fin := false;
while not(Fin) and not(Terminated) do
begin
Fin := true;
w := 0;
repeat
Fin := Fin and PT[w].Finished;
inc(w);
until not(Fin) or (w = ProcessorCount);
if not(Fin) then
Sleep(100);
end;
for w := 0 to High(PT) do
FreeAndNil(PT[w]);
end;
Ну и собственно код самого потока:
procedure TSingleCore_Euclid64.Execute;
var
T1, T2 : Int64;
begin
QueryPerformanceCounter(T1);
gr_InvElASM(c_pr_number64_1, St, Fin);
QueryPerformanceCounter(T2);
Owner.CS_TS.Acquire;
if Owner.TS1 > T1 then
Owner.TS1 := T1;
if Owner.TS2 < T2 then
Owner.TS2 := T2;
Owner.CS_TS.Release;
ReturnValue := 0;
end;
Здесь процедура gr_InvElASM вызывает расчет обратных элементов по модулю c_pr_number64_1 для чисел от St до Fin-1. Идея была сократить количество сохранений регистров в стек. Она на ассемблере написана, но не сильно ускорила расчет по сравнению с циклом for на Pascal.
Поэтому для бинарного варианта так делать не стал, просто в цикле Pascal вызываю функцию расчета обратных элементов. Все остальное аналогично.
Вроде все хорошо! Я бы, наверное, время считал целиком в основном потоке замеряя время затраченное на 2 цикла по потокам: сначала Start, потом WaitFor, но это так личные предпочтения, не думаю что это как-то на тест повлияет.
ОтветитьУдалитьПопробовал у себя, ядра работают, прирост явно заметнее:
- i5-7267U (мобильный, 4 ядра, 8 потоков): в 2.9 раз, судя по мониторингу процессор упирается в тепловой баланс, но он мобильный, ему простительно.
- i7-8700K (десктоп, 6 ядер, 12 потоков): в 10 раз! Даже hyper threading участвует.
Несколько идей:
1. Замерить разницу между минимальным и максимальным T1: на сколько потоки стартуют одновременно, а также для T2. По идеи разброс должен быть минимальным.
2. Owner.TS1 и TS2 - не инициализируются, но это как я понимаю просто не показано?
3. Может Delphi MFENCE, XCHG регистр-память или что-нибудь еще в таком роде использует, что неявно синхронизирует потоки?
4. Проверить частоту и температуру процессора во время теста, вдруг он перегревается и частота падает. Но это прямо совсем невероятно, у Вас такой парк машин :)
О, да! Очевидный косяк, который я просмотрел таки!
УдалитьНедосмотрел п.2, все же Copy-Paste - зло.
Спасибо, Иван, а то я бы так и грешил на процессоры )))
Надо теперь будет заново затестить всё.
> Я бы, наверное, время считал целиком в основном потоке замеряя время затраченное на 2 цикла по потокам:
> сначала Start, потом WaitFor,
Думал об этом. Но погрешность измерения будет выше. Ведь после того, как закончится последний вычислительный поток, мы не знаем, когда планировщик Windows запустит управляющий поток. В результате каждый раз будет добавляться какое-то случайное значение по времени к измеренному.
Бывает :)
УдалитьWaitFor на сколько я понимаю реализовано через мьютекс, который интегрированный с планировщиком, думаю если и будет погрешность - то совсем минимальная, конечно если сама задача занимает приличное время.