Резюме
В общем, 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 }