Раку параллельные / функциональные методы - PullRequest
5 голосов
/ 23 апреля 2020

Я довольно новичок в Raku, и у меня есть вопросы к функциональным методам, в частности, к Reduce. У меня изначально был метод:

sub standardab{
  my $mittel = mittel(@_);
  my $foo = 0;
  for @_ {
    $foo += ($_ - $mittel)**2;
  }
  $foo = sqrt($foo/(@_.elems));
}

, и он работал нормально. Затем я начал использовать уменьшение:

sub standardab{
    my $mittel = mittel(@_);
    my $foo = 0;
    $foo = @_.reduce({$^a + ($^b-$mittel)**2});
    $foo = sqrt($foo/(@_.elems));
}

мое время выполнения удвоилось (я применяю это примерно к 1000 элементам), и решение отличалось на 0,004 (я думаю, ошибка округления). Если я использую

.race.reduce(...)

, мое время выполнения в 4 раза выше, чем с исходным последовательным кодом. Может кто-нибудь сказать мне причину этого? Я думал о времени инициализации параллелизма, но - как я уже сказал - я применяю это к 1000 элементам, и если я изменяю другие циклы for в своем коде, чтобы уменьшить его, становится еще медленнее!

Спасибо за вашу помощь

1 Ответ

6 голосов
/ 23 апреля 2020

Резюме

  • В общем, reduce и for делают разные вещи, и они делают разные вещи в вашем коде. Например, по сравнению с вашим for кодом ваш reduce код содержит вдвое больше передаваемых аргументов и выполняет на одну итерацию меньше. Я думаю, что это вероятно при root разности 0.004.

  • Даже если ваш код for и reduce сделал то же самое, оптимизированная версия такого * Код 1016 * никогда не будет быстрее, чем оптимизированная версия эквивалентного for кода.

  • Я думал, что race не будет автоматически распараллеливать reduce из-за reduce натура. (Хотя я вижу в вашем комментарии и комментарии @ user0721090601, что я неправ.) Но это будет накладываться на накладные расходы - в настоящее время много .

  • Вы могли бы использовать race для распараллеливания вашего for l oop, если он слегка переписан. Это может ускорить его.

О разнице между вашим for и reduce кодом

Вот разница, которую я имел в виду:

say do for    <a b c d>  { $^a }       # (a b c d)      (4 iterations)

say do reduce <a b c d>: { $^a, $^b }  # (((a b) c) d)  (3 iterations)

Подробнее об их работе см. В соответствующих документах c (for, reduce).

Вы не передали свои данные, но я предполагаю, что вычисления for и / или reduce включают Num с (с плавающей точкой). Добавление чисел с плавающей запятой не является коммутативным, поэтому вы можете получить (как правило, небольшие) расхождения, если дополнения произойдут в другом порядке.

Полагаю, это объясняет разницу 0.004.

На вашем последовательном reduce в 2 раза медленнее, чем на for

мое время выполнения удвоилось (я применяю это примерно к 1000 элементам)

Во-первых, ваш reduce код отличается, как описано выше. Существуют общие абстрактные различия (например, принимая два аргумента за вызов вместо одного блока for) и, возможно, ваши специфические данные c приводят к принципиальным различиям в вычислениях чисел c (возможно, ваши вычисления for l oop в основном целочисленная или плавающая математика, в то время как ваша reduce в основном рациональна?) То, что может объяснить разницу во времени выполнения, или какую-то ее часть.

Другой частью может быть разница между, с одной стороны, reduce, который по умолчанию компилируется на вызовы замыкания, с накладными расходами на вызовы и двумя аргументами на вызов, а также во временную память, хранящую промежуточные результаты, и, с другой стороны, for, который по умолчанию будет компилироваться в прямую итерацию, с {...} просто встроенным код, а не вызов закрытия. (Тем не менее, возможно, что reduce иногда будет компилироваться во встроенный код; и это может даже быть так для вашего кода.)

В целом, усилия по оптимизации Rakudo все еще находятся на относительно ранних этапах. Большинство из них были обобщены c, ускоряя весь код. Там, где усилия были приложены к конкретным конструкциям, наиболее широко используемые конструкции до сих пор привлекали внимание, и for широко используется, а reduce меньше. Таким образом, некоторые или все различия могут быть просто в том, что reduce плохо оптимизирован.

Вкл. reduce с race

мое время выполнения [для .race.reduce(...)] равно В 4 раза выше, чем с исходным последовательным кодом

Я не думал, что reduce будет автоматически с возможностью параллелизации race. В соответствии с его do c, reduce работает путем «итеративного применения функции, которая знает, как объединить два значения», и один аргумент в каждой итерации является результатом предыдущей итерации. Так что мне показалось, что это должно быть сделано последовательно.

(я вижу в комментариях, что я неправильно понимаю, что может сделать компилятор с сокращением. Возможно, это если это коммутативная операция?)

Таким образом, ваш код несет race накладные расходы без каких-либо преимуществ.

Вкл. race в целом

Допустим, вы используете какую-то операцию, можно распараллелить с race.

Во-первых, как вы заметили, race связано с накладными расходами. Будут затраты на инициализацию и разбор, по крайней мере, некоторые из которых будут выплачиваться неоднократно за каждую оценку общего утверждения / выражения, которое составляет race d.

Второе, по крайней мере, на данный момент, race означает использование потоков, работающих на ядрах процессора. Для некоторых полезных нагрузок, которые могут принести полезную выгоду, несмотря на любые затраты на инициализацию и разборку. Но в лучшем случае это будет увеличение скорости, равное количеству ядер.

(Однажды разработчики компиляторов смогут заметить, что race d for l oop достаточно простой для запуска на GPU, а не на CPU, и на go вперед и отправки его на GPU для достижения впечатляющего ускорения.)

В-третьих, если вы буквально напишите .race.foo..., вы будете получить настройки по умолчанию для некоторых настраиваемых аспектов гонок. Значения по умолчанию почти наверняка не оптимальны и могут быть недопустимыми.

В настоящее время настраиваются следующие параметры: :batch и :degree. См. их do c для получения более подробной информации.

В более общем смысле, ускорение распараллеливания кода зависит от деталей конкретного c варианта использования, такого как данные и используемое оборудование. .

При использовании race с for

Если вы немного переписали свой код, вы можете race свой for:

$foo = sum do race for @_ { ($_ - $mittel)**2 } 

Подать заявку настройки вы должны повторить race как метод, например:

$foo = sum do race for @_.race(:degree(8)) { ($_ - $mittel)**2 } 
...