Формування Факторіалів


12

Цей гольф вимагає розбиття факторів на кілька потоків або процесів.

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

Мета конкурсу - побачити, хто може придумати найкоротший (у байтах, а не секундах) багатоядерний факторний алгоритм для обчислення N! вимірюється голосами, коли конкурс завершується. Має бути багатоядерна перевага, тому ми вимагатимемо, щоб вона працювала на N ~ 10 000. Виборці повинні голосувати проти, якщо автор не надає вагомих пояснень того, як він поширює роботу між процесорами / сердечниками та голосує на основі лаконічності гольфу.

Для цікавості, будь ласка, опублікуйте кілька номерів виступу. Можливо, в якийсь момент може відбутися компроміс з результатами порівняння з гольфом, займайтеся гольфом, якщо він відповідає вимогам. Мені буде цікаво дізнатися, коли це відбувається.

Ви можете використовувати звичайно доступні одноядерні великі цілі цілі бібліотеки. Наприклад, Perl зазвичай встановлюється з bigint. Однак зауважте, що просто виклик системи за умови функціональної функції зазвичай не розділяє роботу на декілька ядер.

Ви повинні прийняти від STDIN або ARGV вхід N та вихід на STDOUT значення N !. Ви можете додатково використовувати другий вхідний параметр, щоб також вказати кількість процесорів / ядер для програми, щоб вона не виконувала те, що ви побачите нижче :-) Або ви можете чітко спроектувати 2, 4, що б у вас було в наявності.

Я розміщую свій власний приклад дивного perl нижче, раніше поданий на стек переповнення у розділі Факторні алгоритми різними мовами . Це не гольф. Було подано чимало інших прикладів, багато з них - гольф, а багато - ні. Через ліцензування подібного доступу, сміливо використовуйте код у будь-яких прикладах у наведеному вище посиланні як вихідну точку.

Продуктивність у моєму прикладі є недостатньою з кількох причин: він використовує занадто багато процесів, занадто багато конвертації string / bigint. Як я вже сказав, це навмисне дивний приклад. Він підрахує 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) дізнатися щось нове. Для мене це не домашня робота чи дослідження.

Удачі!


10
Ви не можете порахувати найкоротший код за голосами, а вимога про гольф та багатопотоковість, здається, погано поєднуються.
aaaaaaaaaaaa

Мій старовинний одноядерний ноутбук може робити 10000! менше ніж 0,2 сек у Python.
гніблер

Процес, пов'язаний з багатопотоковою передачею процесора, майже завжди сповільнить його. Все, що ви робите, - це додавання накладних витрат з невеликим або відсутнім збільшенням продуктивності. Багатопоточна передача призначена для вводу-виводу.
mellamokb

2
@mellamokb: Я прошу відрізнятись від багатоядерних систем.
Joey

@Joey: Ага. Пропущено цю дрібницю: s Погоджено
mellamokb

Відповіді:


7

Математика

Функція, здатна паралельно:

 f[n_, g_] := g[Product[N@i, {i, 1, n, 2}] Product[N@i, {i, 2, n, 2}]]

Де g є Identityабо Parallelizeзалежно від типу необхідного процесу

Для тесту на встановлення часу, ми трохи змінимо функцію, щоб вона повертала реальний годинний час.

f[n_, g_] := First@AbsoluteTiming[g[Product[N@i,{i,1,n,2}] Product[N@i,{i,2,n,2}]]]

І ми тестуємо обидва режими (від 10 ^ 5 до 9 * 10 ^ 5): (тут лише два ядра)

ListLinePlot[{Table[f[i, Identity],    {i, 100000, 900000, 100000}], 
              Table[f[i, Parallelize], {i, 100000, 900000, 100000}]}]   

Результат: введіть тут опис зображення


Ви пропускаєте a] в першому рядку коду? Це виглядає неврівноваженим.
Пітер Тейлор

@Peter Спасибі, останній "]" не пробився через буфер копіювання. Виправлено.
Доктор Белісарій

1
Це здається найкоротшим. Це також виглядає як найшвидший, якщо я щось не перечитаю. Я більше не підписуюся на Mathematica, тому не можу перевірити. Дякуємо за участь
Пол,

7

Haskell: 209 200 198 177 символів

176 167 джерело + 33 10 прапор компілятора

Це рішення досить нерозумно. Він застосовує продукт паралельно значенню типу [[Integer]], де внутрішні списки мають не більше двох елементів. Після того, як зовнішній список зменшиться щонайбільше до 2 списків, ми його вирівнюємо та беремо виріб безпосередньо. І так, для перевірки типів потрібне щось, що анотовано з Integer, інакше воно не буде компілюватися.

import Control.Parallel.Strategies
s(x:y:z)=[[x,y::Integer]]++s z;s x=[x]
p=product
f n=p$concat$(until((<3).length)$s.parMap rseq p)$s[1..n]
main=interact$show.f.read

(Не соромтеся читати середню частину fміж concatі sяк "доки я не покладу серця")

Здавалося, що вони будуть дуже гарними, оскільки parMap від Control.Parallel.Strategies дозволяє досить легко обробляти це на декілька потоків. Однак, схоже, що GHC 7 вимагає колосальних 33 символів у параметрах командного рядка та vars середовища, щоб фактично дозволити потоковому виконанню потоку використовувати декілька ядер (які я включив до загальної кількості). Якщо я чогось не пропускаю, це, безумовно, можливо . ( Оновлення : в потоці виконання GHC, схоже, використовується потоки N-1, де N - кількість ядер, тому не потрібно змішуватися з параметрами часу виконання.)

Для складання:

ghc -threaded prog.hs

Однак, час виконання був досить гарним, враховуючи смішну кількість паралельних оцінок, що були спричинені, і що я не збирався з -O2. За 50000! на двоядерний MacBook я отримую:

SPARKS: 50020 (29020 converted, 1925 pruned)

INIT  time    0.00s  (  0.00s elapsed)
MUT   time    0.20s  (  0.19s elapsed)
GC    time    0.12s  (  0.07s elapsed)
EXIT  time    0.00s  (  0.00s elapsed)
Total time    0.31s  (  0.27s elapsed)

Всього разів для декількох різних значень, перша колонка - це паралельна гольф, друга - наївна послідовна версія:

          Parallel   Sequential
 10000!      0.03s        0.04s
 50000!      0.27s        0.78s
100000!      0.74s        3.08s
500000!      7.04s       86.51s

Для довідки, наївна послідовна версія така (яка була складена з -O2):

factorial :: Integer -> Integer
factorial n = product [1..n]
main = interact $ show.factorial.read

1
IMO, вам не доведеться рахувати аргументи для компілятора та інтерпретатора.
FUZxxl

@FUZxxl: Зазвичай я погодився б, але ця проблема спеціально вимагала, щоб вона працювала в декількох потоках або процесах, і ці прапори необхідні, щоб це відбулося (принаймні, з GHC 7.0.2, з останньої платформи Haskell).

6

Рубі - 111 + 56 = 167 символів

Це два файли, основний файл ( fact.rb):

c,n=*$*.map(&:to_i)
p=(0...c).map{|k|IO.popen("ruby f2.rb #{k} #{c} #{n}")}
p p.map{|l|l.read.to_i}.inject(:*)

додатковий файл ( f2.rb):

c,h,n=*$*.map(&:to_i)
p (c*n/h+1..(c+1)*n/h).inject(:*)

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

Це дійсно показує, наскільки повільніше Рубіній до YARV:

Рубіній:

time ruby fact.rb 5 5000 #=> 61.84s

Ruby1.9.2:

time ruby fact.rb 5 50000 #=> 3.09s

(Зверніть увагу на додаткове 0)


1
inject може взяти символ як аргумент, тому ви можете зберегти символ за допомогою inject(:+). Ось приклад з docs : (5..10).reduce(:+).
Майкл Коль

@Michael: Дякую :). Також щойно помітив, що у мене було місце, 8де мали бути, *якщо хтось мав проблеми з цим.
Nemo157

6

Ява, 523 519 434 430 429 символів

import java.math.*;public class G extends Thread{BigInteger o,i,r=BigInteger.ONE,h;G g;G(BigInteger O,int
I,int n){o=O;i=new BigInteger(""+I);if(n>1)g=new G(O.subtract(r),I,n-1);h=n==I?i:r;start();}public void
run(){while(o.signum()>0){r=r.multiply(o);o=o.subtract(i);}try{g.join();r=r.multiply(g.r);}catch(Exception
e){}if(h==i)System.out.println(r);}public static void main(String[] args){new G(new BigInteger(args[0]),4,4);}}

Дві 4 в заключному рядку - це кількість потоків, які потрібно використовувати.

50000! протестовано з наступною основою (неперероблена версія оригінальної версії та з кількома меншими шкідливими практиками - хоча її все ще багато) дає (на моїй 4-ядерній машині Linux) рази

7685ms
2338ms
1361ms
1093ms
7724ms

Зауважте, що я повторив тест однією ниткою для справедливості, тому що джит, можливо, прогрівся.

import java.math.*;

public class ForkingFactorials extends Thread { // Bad practice!
    private BigInteger off, inc;
    private volatile BigInteger res;

    private ForkingFactorials(int off, int inc) {
        this.off = new BigInteger(Integer.toString(off));
        this.inc = new BigInteger(Integer.toString(inc));
    }

    public void run() {
        BigInteger p = new BigInteger("1");
        while (off.signum() > 0) {
            p = p.multiply(off);
            off = off.subtract(inc);
        }
        res = p;
    }

    public static void main(String[] args) throws Exception {
        int n = Integer.parseInt(args[0]);
        System.out.println(f(n, 1));
        System.out.println(f(n, 2));
        System.out.println(f(n, 3));
        System.out.println(f(n, 4));
        System.out.println(f(n, 1));
    }

    private static BigInteger f(int n, int numThreads) throws Exception {
        long now = System.currentTimeMillis();
        ForkingFactorials[] th = new ForkingFactorials[numThreads];
        for (int i = 0; i < n && i < numThreads; i++) {
            th[i] = new ForkingFactorials(n-i, numThreads);
            th[i].start();
        }
        BigInteger f = new BigInteger("1");
        for (int i = 0; i < n && i < numThreads; i++) {
            th[i].join();
            f = f.multiply(th[i].res);
        }
        long t = System.currentTimeMillis() - now;
        System.err.println("Took " + t + "ms");
        return f;
    }
}

Java з біґінтами не є правильною мовою для гри в гольф (подивіться, що я повинен зробити, щоб сконструювати жахливі речі, тому що конструктор, який займає багато часу, є приватним), але ей.

З неопікуваного коду повинно бути абсолютно очевидно, як воно розбиває роботу: кожен потік множить клас еквівалентності по модулю на кількість потоків. Ключовим моментом є те, що кожна нитка виконує приблизно однакову кількість роботи.


5

CSharp - 206 215 символів

using System;using System.Numerics;using System.Threading.Tasks;class a{static void Main(){var n=int.Parse(Console.ReadLine());var r=new BigInteger(1);Parallel.For(1,n+1,i=>{lock(this)r*=i;});Console.WriteLine(r);}}

Розділяє обчислення за допомогою функціональності C # Parallel.For ().

Редагувати; Забули замок

Час виконання:

n = 10,000, time: 59ms.
n = 20,000, time: 50ms.
n = 30,000, time: 38ms.
n = 40,000, time: 100ms.
n = 50,000, time: 139ms.
n = 60,000, time: 164ms.
n = 70,000, time: 222ms.
n = 80,000, time: 266ms.
n = 90,000, time: 401ms.
n = 100,000, time: 424ms.
n = 110,000, time: 501ms.
n = 120,000, time: 583ms.
n = 130,000, time: 659ms.
n = 140,000, time: 832ms.
n = 150,000, time: 1143ms.
n = 160,000, time: 804ms.
n = 170,000, time: 653ms.
n = 180,000, time: 1031ms.
n = 190,000, time: 1034ms.
n = 200,000, time: 1765ms.
n = 210,000, time: 1059ms.
n = 220,000, time: 1214ms.
n = 230,000, time: 1362ms.
n = 240,000, time: 2737ms.
n = 250,000, time: 1761ms.
n = 260,000, time: 1823ms.
n = 270,000, time: 3357ms.
n = 280,000, time: 2110ms.

4

Перл, 140

Береться Nзі стандартного введення.

use bigint;$m=<>;open A,'>',
undef;$i=$p=fork&&1;$n=++$i;
{$i+=2;$n*=$i,redo if$i<=$m}
if($p){wait;seek A,0,0;$_=<A
>;print$n*$_}else{print A$n}

Особливості:

  • обчислювальний розкол: рівний на одній стороні, а шанси - на іншому (що-небудь складніше, ніж це, знадобиться багато символів для відповідного балансування навантаження на обчислення.
  • IPC, використовуючи спільний анонімний файл.

Орієнтир:

  • 10000! надруковано у роздрібненій 2.3s, 3.4s unked
  • 100000! надруковано в 5'08,8 роздвоєних, 7'07,9 відмежених

4

Scala ( 345 266 244 232 214 символів)

Використання акторів:

object F extends App{import actors.Actor._;var(t,c,r)=(args(1).toInt,self,1:BigInt);val n=args(0).toInt min t;for(i<-0 to n-1)actor{c!(i*t/n+1 to(i+1)*t/n).product};for(i<-1 to n)receive{case m:Int=>r*=m};print(r)}

Редагувати - видалено посилання на System.currentTimeMillis()фактично створені факти a(1).toInt, змінено з List.rangeнаx to y

Edit 2 - змінив whileцикл на a for, змінив ліву складку на функцію списку, яка робить те саме, спираючись на неявні перетворення типу, тому BigIntтип 6 знаків з’являється лише один раз, змінив println для друку

Edit 3 - дізнайтеся, як робити кілька декларацій у Scala

Редагувати 4 - різні оптимізації, які я навчився з першого разу

Негольована версія:

import actors.Actor._
object ForkingFactorials extends App
{
    var (target,caller,result)=(args(1).toInt,self,1:BigInt)
    val numthreads=args(0).toInt min target
    for(i<-0 to numthreads-1)
        actor
        {
            caller ! (i*target/numthreads+1 to(i+1)*target/numthreads+1).product
        }
    for(i<-1 to numthreads)
        receive
        {
            case m:Int=>result*=m
        }
    print(result)
}

3

Скала-2,9,0 170

object P extends App{
def d(n:Int,c:Int)=(for(i<-1 to c)yield(i to n by c)).par
println((BigInt(1)/: d(args(0).toInt,args(1).toInt).map(x=>(BigInt(1)/: x)(_*_)))(_*_))}

неозорений:

object ParallelFactorials extends App
{
  def distribute (n: Int, cores: Int) = {
    val factorgroup = for (i <- 1 to cores) 
      yield (i to n by cores)
    factorgroup.par
  }

  val parallellist = distribute (args(0).toInt, args(1).toInt)

  println ((BigInt (1) /: parallellist.map (x => (BigInt(1) /: x) (_ * _)))(_ * _))

}

Фабрика з 10 обчислюється на 4 ядрах, генеруючи 4 Списки:

  • 1 5 9
  • 2 6 10
  • 3 7
  • 4 8

які множать паралельно. Існував би простіший підхід до розподілу чисел:

 (1 to n).sliding ((n/cores), (n/cores) 
  • 1 2 3
  • 4 5 6
  • 7 8 9
  • 10

Але розподіл не був би таким хорошим - менші числа закінчуватимуться в одному списку, найвищі в іншому, що веде до більш тривалих обчислень в останньому списку (для високих N останній потік не був би майже порожнім , але принаймні містять (N / сердечники) -cores елементів.

Scala у версії 2.9 містить паралельні Збірники, які самі обробляють паралельне виклик.


2

Ерланг - 295 символів.

Перше, що я коли-небудь писав на Ерланге, тому я не здивуюсь, якщо хтось легко може вдвічі зменшити це:

-module(f).
-export([m/2,f/4]).
m(N,C)->g(N,C,C,[]).
r([],B)->B;
r(A,B)->receive{F,V}->r(lists:delete(F,A),V*B)end.
s(H,L)->spawn(f,f,[self(),H,L,1]).
g(N,1,H,A)->r([s(N div H,1)|A],1);
g(N,C,H,A)->g(N,C-1,H,[s(N*C div H,N*(C-1) div H)|A]).
f(P,H,H,A)->P!{self(),A};
f(P,H,L,A)->f(P,H-1,L,A*H).

Використовується та сама модель нитки, що і моя попередня запис Ruby: розбиває діапазон на піддіапазон і множує діапазони разом на окремі потоки, після чого множує результати назад в основній темі.

Я не зміг зрозуміти, як змусити працювати ескрипт, просто збережіть як f.erlі відкрийте erl та запустіть:

c(f).
f:m(NUM_TO_CALC, NUM_OF_PROCESSES).

з відповідними замінами.

На моєму MacBook Air (двоядерний) отримано близько 8 с для 50000 в 2 процесах і 10 с за 1 процес.

Примітка. Щойно помітив, що він застигає, якщо ви спробуєте факторизувати більше процесів, ніж число.

Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.