21.12.2020

Как я тестирую несколько потоков

В ответ Ивану.

Да вроде везде сделано одинаково. Надо бы, конечно, "причесать" и поприличней сделать, а то сплошной 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 вызываю функцию расчета обратных элементов. Все остальное аналогично.

3 комментария:

  1. Вроде все хорошо! Я бы, наверное, время считал целиком в основном потоке замеряя время затраченное на 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. Проверить частоту и температуру процессора во время теста, вдруг он перегревается и частота падает. Но это прямо совсем невероятно, у Вас такой парк машин :)

    ОтветитьУдалить
    Ответы
    1. О, да! Очевидный косяк, который я просмотрел таки!
      Недосмотрел п.2, все же Copy-Paste - зло.
      Спасибо, Иван, а то я бы так и грешил на процессоры )))

      Надо теперь будет заново затестить всё.

      > Я бы, наверное, время считал целиком в основном потоке замеряя время затраченное на 2 цикла по потокам:
      > сначала Start, потом WaitFor,
      Думал об этом. Но погрешность измерения будет выше. Ведь после того, как закончится последний вычислительный поток, мы не знаем, когда планировщик Windows запустит управляющий поток. В результате каждый раз будет добавляться какое-то случайное значение по времени к измеренному.

      Удалить
    2. Бывает :)

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

      Удалить