26.12.2020

Результаты целочисленного теста

Ну вот, готовы результаты целочисленного теста после исправления досадной, грубой ошибки, допущенной мною по невнимательности. Зато добавился новый участник теста: уже немного старенький, но десктопный Intel i5-6500.

Напомню, что тест основан на вычислении обратного элемента в кольце вычетов по модулю N. И фактически тестирует регистровые целочисленные 64-битные операции в двух вариантах: с использованием операции деления или на основе операций сложения и сдвига.

Запустил сначала однопоточный тест на своем AMD FX-4350, получил результаты и подумал, что, наверное, делать многопоточный тест смысле нет: все же чистая синтетика, 100% регистровых операций, ни одного обращения к памяти... Но потом вспомнил про HyperThreading, CMT, SMT и вот это вот всё. Поэтому все же прогнал многопоточный тест ради интереса.

В качестве базы я, как обычно взял свой AMD FX-4350, поэтому его производительность везде принята за единицу. Производительность сложения и сдвига я считаю в 1.5 раза более ценной, чем операции деления (так как встречаются в коде они гораздо чаще), как и производительность однопоточного выполнения. Сначала интегральная производительность.

Как видно из графика, что-то производительность регистровых целочисленных операций у процессоров Intel особо не задалась. Лишь относительно новый i5-8300H в интегральной производительности слегка обгоняет мой старенький FX-4350.

Недавно читал, что за последние лет пять производительность процессоров выросла примерно вдвое. Ну, почти так и есть. Ryzen 5 3600 в 1.82 раза быстрее моего процессора. Но надо заметить, что в основном ускорение пошло экстенсивным путем ‒ за счет увеличения количества ядер, в то время как однопоточная производительность увеличилась всего лишь на жалкие 16%.

Несколько удивляет крайне низкий результат i5-6500, который лишь чуть-чуть быстрее весьма древнего мобильного AMD A10-4600M. Впрочем, тест проводил не я лично, возможно, тестирование было выполнено не совсем аккуратно.

Как можно видеть, в целочисленных регистровых операциях даже относительно новые, хоть и мобильные процессоры Intel с трудом тягаются со стареньким десктопным AMD. Ну а относительно новый Ryzen 5 просто закрепляет победу.
Так что, похоже, и в самом деле архитектура Ryzen очень хороша. Хоть она, похоже, чуть-чуть хуже в операциях с плавающей точкой, но опережает в целочисленных, что для большинства приложений важнее. За исключением, может быть, трехмерных игр и приложений ИИ.

Посмотрим более детально.

Вариант с использованием операции деления.


Если в функции встречается всего лишь одна операция деления, то тут даже старенький процессор от AMD рвет как Тузик грелку все процессоры от Intel, участвовавшие в тестировании. А Ryzen еще немножко добавляет.

Бинарный вариант на сложениях и сдвигах.


Тут Intel в последних поколениях мобильных процессоров в однопотоке немного обогнала PileDriver, но Ryzen 3000 это компенсировал и на обычных целочисленных регистровых операциях AMD и Intel сейчас на равных, но за счет большего количества ядер в общем зачете все же AMD впереди. Из любопытного: все процессоры, за исключением двух, показывают большее ускорение при параллельном выполнении функции на основе деления, что в общем-то понятно при использовании технологии HyperThreading: пока одно виртуальное ядро выполняет длинную операцию деления, другое ядро может вовсю пользоваться остальной частью физического АЛУ. Аномальное поведение замечено лишь у Ryzen 5 3600 и i5-6500. Не могу даже предположить, с чем это может быть связано.

Ну и напоследок табличка с абсолютной производительностью процессоров (кол-во вычисленных обратных элементов в секунду): 


Строки с префиксом P ‒ многопоточные данные.

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

Тестирую целочисленные операции

Ну уж коли я тут как-то намедни написал функции поиска обратного элемента, грех этим было не воспользоваться, что бы оценить целочисленную производительность современных процессоров. Что я собственно и сделал.
Запустил сначала однопоточный тест на своем
AMD FX-4350, получил результаты и подумал, что, наверное, делать многопоточный тест смысле нет: все же чистая синтетика, 100% регистровых операций, ни одного обращения к памяти...
Но потом вспомнил про HyperThreading, CMT, SMT и вот это вот всё. Поэтому все же прогнал многопоточный тест и сильно удивился!

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

Мой родной AMD FX-4350 показал в многопотоке на чисто регистровых операциях ускорение всего-то в 1.7 раза на классическом Евклиде и в 1.4 (!) на бинарном. Честно говоря, я был в шоке! Попялился на всякий случай на код и ничего там криминального не нашел.
Надо было не тупо пялиться, а ошибку искать. 😕
Что же меня смутило? Вообще-то процессор четырехядерный (на самом деле не совсем), все операции регистровые, поэтому ускорение теоретически должно было бы равняться числу ядер, то есть четырем.
В реальности мой процессор состоит из двух модулей (то есть физически у него два ядра), но в каждом модуле по два полноценных АЛУ, которые представлены как два виртуальных ядра. Это так называемая технология CMT (
Clustered Multithreading) от AMD, некий аналог HyperThreading от Intel.
Но даже если с CMT все так плохо, то все же двукратное ускорение должно же было бы получиться. Но ведь и его не выходит! Причем бинарный вариант, использующий самые простые арифметические операции: сложение и сдвиги, показывает наихудший коэффициент ускорения в многопоточном варианте!
Честно говоря, я не знаю, в чем тут дело. Складывается впечатление, что одно АЛУ вообще общее на все ядра. Поэтому одновременное исполнение максимум четырех команд возможно лишь когда активно одно виртуальное ядро. А другие ядра в это время простаивают или ждут завершение длинной операции, такой как обращение к памяти, умножение или деление.
Похоже, именно поэтому
вариант поиска обратного элемента на классическом алгоритме Евклида показывает более высокое ускорение: пока один поток ждет завершение операции деления, остальные успевают сделать несколько простых вспомогательных арифметических операций.

Впрочем, такое поведение характерно не только для PileDriver. Наименьшее ускорение для бинарного варианта (1.1) показали мобильные AMD A10-4600M и Intel i3-3227U.
Впрочем, более мощные процессоры не намного лучше:
Intel i5-8300H и Intel i7-6700HQ показывают ускорение 2.1 и 2.0 соответственно (по 4 физических ядра), а Ryzen 5 3600 ‒ 2.8 при 6 физических ядрах.
И все они показывают лучшее ускорение при использовании алгоритма с делением.

В связи с чем я с ностальгией вспоминаю свой старенький шестиядерный Phenom II. Похоже, это был последний процессор с относительно честной многоядерностью.
К сожалению, протестировать его нет возможности, так как несколько лет назад сгорела мать, при ее замене я недооценил степень развития собственной дальнозоркости и со слепу нечаянно спалил и
Phenom II при замене материнки.
При замене на
AMD FX-4350 я сразу заметил, что система стала менее отзывчивой. Я связывал это с тем, что поменялся южный мост, но, возможно дело не только в нем. Конечно, пиковая производительность у FX-4350 выше, чем у Phenom II, но вот насчет многозадачности я сейчас уже не уверен.

Ну и наконец-то собственно сама производительность. В качестве базы я, как обычно взял свой AMD FX-4350, поэтому его производительность везде принята за единицу.
Производительность сложения и сдвига я считаю в 1.5 раза более ценной, чем операции деления (так как встречаются в коде они гораздо чаще), как и производительность однопоточного выполнения.

Графики тоже удалил, так как они ошибочны.

Как можем видеть, в целочисленных регистровых операциях даже относительно новые, хоть и мобильные процессоры Intel с трудом тягаются со стареньким десктопным AMD. Ну а относительно новый Ryzen 5, теперь уже предыдущего поколения, вообще для них недостижим.
Так что и в самом деле архитектура Ryzen очень хороша. Хоть она, похоже, чуть-чуть хуже в операциях с плавающей точкой, но опережает в целочисленных, что для большинства приложений важнее. За исключением, может быть, трехмерных игр и приложений ИИ.

Посмотрим более детально. Вариант с использованием операции деления.

Если в функции встречается операция деления, то тут даже старенький процессор от AMD предпочтительней новых изделий от Intel. А Ryzen так вообще рвет все интеловские процессоры, как Тузик грелку.

Бинарный вариант на сложениях и сдвигах.

Тут Intel в последних поколениях в однопотоке немного обогнала PileDriver, но Ryzen 3000 это компенсировал и на обычных целочисленных регистровых операциях AMD и Intel сейчас на равных, но за счет большего количества ядер в общем зачете все же AMD впереди.