Этот гольф требует, чтобы факторный расчет был разделен между несколькими потоками или процессами.
В некоторых языках это легче координировать, чем в других, поэтому это не зависит от языка. Приведен пример кода без правил, но вы должны разработать свой собственный алгоритм.
Цель конкурса - выяснить, кто может предложить самый короткий (в байтах, а не секундах) многоядерный факториальный алгоритм для вычисления N! по результатам голосования после закрытия конкурса. Должно быть многоядерное преимущество, поэтому нам потребуется, чтобы оно работало за N ~ 10000. Избиратели должны проголосовать, если автор не предоставит достоверного объяснения того, как это распределяет работу между процессорами / ядрами, и голосовать на основе лаконичности гольфа.
Для любопытства, пожалуйста, оставьте некоторые номера производительности. В какой-то момент может быть компромисс между игрой в гольф и игрой в гольф, если она соответствует требованиям. Мне было бы интересно узнать, когда это произойдет.
Вы можете использовать обычно доступные одноядерные библиотеки больших целых чисел. Например, perl обычно устанавливается вместе с bigint. Однако обратите внимание, что простой вызов предоставляемой системой факториальной функции обычно не распределяет работу между несколькими ядрами.
Вы должны принять от STDIN или ARGV вход N и вывести STDOUT значение N !. При желании вы можете использовать второй входной параметр, чтобы также указать количество процессоров / ядер для программы, чтобы она не выполняла то, что вы увидите ниже :-) Или вы можете явно указать 2, 4, что у вас есть.
Ниже я опубликую свой собственный пример Perl, который ранее был представлен в Переполнении стека в разделе Факторные алгоритмы на разных языках . Это не гольф. Было представлено множество других примеров, многие из них - гольф, но многие нет. Из-за общего лицензирования, не стесняйтесь использовать код в любых примерах по ссылке выше в качестве отправной точки.
Производительность в моем примере невелика по ряду причин: она использует слишком много процессов, слишком много преобразования строк / больших чисел. Как я уже сказал, это намеренно странный пример. Это будет рассчитывать 5000! менее чем за 10 секунд на 4-ядерном компьютере здесь. Тем не менее, более очевидные два цикла для / следующего цикла могут сделать 5000! на одном из четырех процессоров в 3.6s.
Вам определенно придется сделать лучше, чем это:
#!/usr/bin/perl -w
use strict;
use bigint;
die "usage: f.perl N (outputs N!)" unless ($ARGV[0] > 1);
print STDOUT &main::rangeProduct(1,$ARGV[0])."\n";
sub main::rangeProduct {
my($l, $h) = @_;
return $l if ($l==$h);
return $l*$h if ($l==($h-1));
# arghhh - multiplying more than 2 numbers at a time is too much work
# find the midpoint and split the work up :-)
my $m = int(($h+$l)/2);
my $pid = open(my $KID, "-|");
if ($pid){ # parent
my $X = &main::rangeProduct($l,$m);
my $Y = <$KID>;
chomp($Y);
close($KID);
die "kid failed" unless defined $Y;
return $X*$Y;
} else {
# kid
print STDOUT &main::rangeProduct($m+1,$h)."\n";
exit(0);
}
}
Мой интерес к этому просто (1) облегчение скуки; и (2) узнать что-то новое. Для меня это не домашняя работа или исследование.
Удачи!
Ответы:
Mathematica
Параллельно-способная функция:
Где г
Identity
или вParallelize
зависимости от типа требуемого процессаДля теста синхронизации мы немного изменим функцию, чтобы она возвращала реальное время.
И мы тестируем оба режима (от 10 ^ 5 до 9 * 10 ^ 5): (здесь только два ядра)
Результат:
источник
Haskell:
209200198177 символов176167 источник +3310 флаг компилятораЭто решение довольно глупо. Продукт применяется параллельно со значением типа
[[Integer]]
, где внутренние списки имеют длину не более двух элементов. Как только внешний список становится максимум 2 списками, мы выравниваем его и получаем продукт напрямую. И да, для проверки типов нужно что-то, помеченное Integer, иначе оно не скомпилируется.(Не стесняйтесь читать среднюю часть
f
междуconcat
иs
как "до тех пор, пока я сердце длиной")Казалось, что все будет довольно хорошо, так как parMap из Control.Parallel.Strategies позволяет довольно легко распределить это по нескольким потокам. Тем не менее, похоже, что GHC 7 требует колоссальных 33 символов в параметрах командной строки и переменных среды, чтобы фактически позволить многопоточному времени выполнения использовать несколько ядер (которые я включил в общую сумму).Если я что-то упустил, что, безусловно,возможно. ( Обновление : среда выполнения GHC с резьбой, по-видимому, использует потоки N-1, где N - количество ядер, поэтому не нужно возиться с параметрами времени выполнения.)Компилировать:
Время выполнения, однако, было довольно хорошим, учитывая, что было запущено нелепое количество параллельных оценок, и что я не компилировал с -O2. За 50000! на двухъядерном MacBook я получаю:
Общее количество раз для нескольких различных значений: первый столбец - параллель гольфа, второй - наивный последовательный вариант:
Для справки, наивная последовательная версия - это (которая была скомпилирована с -O2):
источник
Рубин - 111 + 56 = 167 символов
Это двухфайловый скрипт, основной файл (
fact.rb
):дополнительный файл (
f2.rb
):Просто берет число процессов и число для вычисления в виде аргументов и разбивает работу на диапазоны, которые каждый процесс может рассчитать индивидуально. Затем умножает результаты в конце.
Это действительно показывает, насколько медленнее Рубиниус для YARV:
Rubinius:
Ruby1.9.2:
(Обратите внимание на дополнительное
0
)источник
inject(:+)
. Вот пример из документации:(5..10).reduce(:+)
.8
где должно было быть,*
если у кого-то возникли проблемы с этим.Ява,
523 519 434 430429 символовДве 4 в последней строке - количество потоков, которые нужно использовать.
50000! протестировано на следующей платформе (без первоначальной версии исходной версии и с небольшим количеством плохих практик - хотя ее еще много) дает (на моей 4-ядерной машине с Linux) время
Обратите внимание, что я повторил тест с одним потоком для справедливости, потому что джит, возможно, нагрелся.
Java с bigints - это не тот язык, на котором можно играть в гольф (посмотрите, что я должен сделать, просто чтобы создать жалкие вещи, потому что конструктор, который занимает много времени, является приватным), но эй.
Из неопрятного кода должно быть совершенно очевидно, как он разбивает работу: каждый поток умножает класс эквивалентности по модулю количества потоков. Ключевым моментом является то, что каждый поток выполняет примерно одинаковый объем работы.
источник
CSharp -
206215 символовДелит вычисления с помощью функции C # Parallel.For ().
Редактировать; Забыл замок
Время выполнения:
источник
Perl, 140
Берет
N
из стандартного ввода.Функции:
Ориентир:
источник
Scala (
345266244232214 символов)Используя Актеров:
Изменить - удалены ссылки на
System.currentTimeMillis()
, вычеркнутыa(1).toInt
, изменены сList.range
наx to y
Редактировать 2 - изменил
while
цикл на afor
, изменил левый сгиб на функцию списка, которая делает то же самое, полагаясь на неявные преобразования типов, так чтоBigInt
тип 6 символов появляется только один раз, изменил println для печатиEdit 3 - узнал, как сделать несколько объявлений в Scala
Редактировать 4 - различные оптимизации, которые я изучил с тех пор, как впервые сделал это
Безголовая версия:
источник
Scala-2.9.0 170
ungolfed:
Факториал 10 вычисляется на 4 ядрах путем создания 4 списков:
которые умножаются параллельно. Был бы более простой подход к распределению чисел:
Но распределение не будет таким хорошим - все меньшие числа будут заканчиваться в одном и том же списке, а самые высокие - в другом, что приведет к более длинным вычислениям в последнем списке (для больших N последний поток не будет почти пустым). , но, по крайней мере, содержат (N / core) -ядерные элементы.
Scala в версии 2.9 содержит параллельные коллекции, которые сами обрабатывают параллельный вызов.
источник
Эрланг - 295 символов.
Первое, что я когда-либо писал на Эрланге, поэтому я не удивлюсь, если кто-то сможет вдвое сократить это:
Использует ту же модель потоков, что и в моей предыдущей записи Ruby: разбивает диапазон на поддиапазоны и умножает диапазоны вместе в отдельных потоках, а затем умножает результаты обратно в основной поток.
Я не смог понять, как заставить работать скрипт, поэтому просто сохраните как,
f.erl
откройте erl и запустите:с соответствующими заменами.
На моем MacBook Air (двухъядерный) я получил около 8 секунд за 50000 за 2 процесса и 10 секунд за 1 процесс.
Примечание: только что заметил, что он зависает, если вы пытаетесь факторизовать больше процессов, чем число.
источник