Forking Faktörleri


12

Bu golf faktöriyel bir hesaplamanın birden çok iş parçacığı veya süreç arasında bölünmesini gerektirir.

Bazı diller bunu koordine etmeyi diğerlerinden daha kolaylaştırır, bu yüzden lang agnostiktir. Ungolfed örnek kodu sağlanır, ancak kendi algoritmanızı geliştirmelisiniz.

Yarışmanın amacı N'yi hesaplamak için en kısa (saniyede değil bayt cinsinden) çok çekirdekli faktöriyel algoritmayı kimin bulabileceğini görmektir. yarışma sona erdiğinde oylarla ölçülür. Çok çekirdekli bir avantaj olmalı, bu yüzden N ~ 10.000 için çalışmasını gerektireceğiz. Yazarlar, işçiyi işlemciler / çekirdekler arasında nasıl yaydığına dair geçerli bir açıklama sağlayamazsa ve golf kısaltmasına dayanarak oy kullanırlar.

Merak için lütfen bazı performans numaraları gönderin. Bir noktada golf skoru dengesine karşı bir performans olabilir, gereksinimleri karşıladığı sürece golf ile devam edin. Bunun ne zaman gerçekleştiğini bilmek isterdim.

Normalde kullanılabilir tek çekirdekli büyük tamsayı kitaplıkları kullanabilirsiniz. Örneğin, perl genellikle bigint ile kurulur. Bununla birlikte, sadece bir sistem tarafından sağlanan faktöriyel fonksiyonu çağırmanın, işi normalde birden fazla çekirdek arasında bölmeyeceğini unutmayın.

STDIN veya ARGV'den N girişini kabul etmeli ve STDOUT'a N! İsteğe bağlı olarak programa işlemci / çekirdek sayısını sağlamak için 2. giriş parametresini kullanabilirsiniz, böylece aşağıda göreceğiniz şeyi yapmaz :-) Veya mevcut olan her ne olursa olsun 2, 4 için açıkça tasarlayabilirsiniz.

Daha önce Farklı Dillerdeki Faktöriyel Algoritmalar altında Stack Overflow'da gönderilen kendi oddball perl örneğimi yayınlayacağım . Golf değil. Birçoğu golf ama birçoğu olmayan çok sayıda başka örnek sunuldu. Benzer şekilde lisanslama nedeniyle, yukarıdaki bağlantıdaki örneklerde yer alan kodu başlangıç ​​noktası olarak kullanmaktan çekinmeyin.

Örneğimdeki performans birkaç nedenden dolayı yetersiz: çok fazla işlem, çok fazla dize / bigint dönüşümü kullanıyor. Dediğim gibi bu kasten tuhaf bir örnek. 5000 hesaplayacak! 4 çekirdekli bir makinede 10 saniyenin altında. Ancak, daha açık bir iki astar / sonraki döngü 5000 yapabilir! 3.6'larda dört işlemciden birinde.

Kesinlikle bundan daha iyisini yapmanız gerekecek:

#!/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);
    }
}

Buna olan ilgim basitçe (1) can sıkıntısını hafifletmek; ve (2) yeni bir şey öğrenmek. Bu benim için bir ödev ya da araştırma sorunu değil.

İyi şanslar!


10
Oylara göre en kısa kodu sayamazsınız ve golf ve çoklu iş parçacığı gereksinimi birlikte kötü gidiyor gibi görünüyor.
aaaaaaaaaaaa

Eski tek çekirdekli dizüstü bilgisayar 10000 yapabilir! Python'da 0.2 saniyeden daha kısa sürede.
gnibbler

İşlemciye bağlı bir işlemin çok iş parçacıklı işlenmesi neredeyse her zaman yavaşlatır. Yaptığınız tek şey, performans artışı az olan veya hiç olmayan ek yük eklemek. Çoklu iş parçacığı G / Ç beklemesi içindir.
mellamokb

2
@mellamokb: Çok çekirdekli sistemler için farklı olmaya yalvarıyorum.
Joey

@Joey: Ah. Kaçırılan bu küçük detay: s Kabul
Edildi

Yanıtlar:


7

Mathematica

Paralel özellikli bir işlev:

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

G olduğu Identityveya Parallelizegereken işlem türüne bağlı olarak

Zamanlama testi için işlevi biraz değiştiririz, böylece gerçek saat zamanını döndürür.

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

Ve her iki modu da test ediyoruz (10 ^ 5 ila 9 * 10 ^ 5): (burada sadece iki çekirdek)

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

Sonuç: resim açıklamasını buraya girin


İlk kod satırında a] eksik mi? Dengesiz görünüyor.
Peter Taylor

@Peter Teşekkürler, son "]" kopyalama arabelleğinden geçemedi. Düzeltildi.
Dr. belisarius

1
Bu en kısa gibi görünüyor. Bir şeyi yanlış okuduğum sürece aynı zamanda en hızlı gibi görünüyor. Artık Mathematica'ya abone değilim, bu yüzden doğrulayamıyorum. Katıldığınız için teşekkürler.
Paul

7

Haskell: 209 200 198 177 karakter

176166 kaynak + 33 10 derleyici bayrağı

Bu çözüm oldukça aptalca. Ürünü [[Integer]], iç listelerin en fazla iki öğe uzunluğunda olduğu bir tür değere paralel olarak uygular . Dış liste en fazla 2 listeye indiğinde, düzleştirir ve ürünü doğrudan alırız. Ve evet, tür denetleyicisinin Integer ile açıklamalı bir şeye ihtiyacı vardır, aksi takdirde derlenmez.

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

(Orta bölümünü okumak için çekinmeyin farasında concatve solarak "kalp ben uzunluğu kadar")

Kontrol.Parallel.Strategies gelen parMap beri bu çok iyi iş parçacıkları için çiftlik oldukça kolay yapar gibi şeyler oldukça iyi olacak gibi görünüyordu. Bununla birlikte, GHC 7'nin komut satırı seçeneklerinde okkalı bir 33 karakter gerektirdiği ve ortam değişkeninin aslında iş parçacığının (toplamda dahil olduğum) birden fazla çekirdek kullanmasını sağlamak için değiştiği görülüyor. Bir şeyi kaçırmadıkça, bu kesinlikle mümkündür . ( Güncelleme : dişli GHC çalışma zamanı N-1 iş parçacıklarını kullanıyor gibi görünüyor, burada N çekirdek sayısıdır, bu nedenle çalışma süresi seçenekleriyle uğraşmanıza gerek yoktur.)

Derlemek için:

ghc -threaded prog.hs

Ancak çalışma zamanı, çok sayıda paralel değerlendirmenin kıvılcım çıkardığı ve -O2 ile derlemediğim göz önüne alındığında oldukça iyiydi. 50000 için! çift ​​çekirdekli bir MacBook'da:

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)

Birkaç farklı değer için toplam süreler, ilk sütun golflü paralel, ikincisi saf sıralı versiyon:

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

Referans için, saf sıralı sürüm budur (-O2 ile derlenmiştir):

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

1
IMO, derleyici ve yorumlayıcı için bağımsız değişkenleri saymanız gerekmez.
FUZxxl

@FUZxxl: Normalde kabul ediyorum, ancak bu sorun özellikle birden çok iş parçacığında veya işlemde çalışmasını istedi ve bu bayrakların bunu gerçekleştirmesi gerekiyor (en azından en son Haskell Platformundan GHC 7.0.2 ile).

6

Ruby - 111 + 56 = 167 karakter

Bu iki dosya komut dosyasıdır, ana dosya ( 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(:*)

ekstra dosya ( f2.rb):

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

Basitçe, bağımsız değişken olarak hesaplanacak işlemlerin sayısını ve sayısını alır ve işi her işlemin ayrı ayrı hesaplayabileceği aralıklara böler. Sonunda sonuçları çarpar.

Bu, Rubinius'un YARV için ne kadar yavaş olduğunu gerçekten gösteriyor:

Rubinius:

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

Ruby1.9.2:

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

(Ekstra not alın 0)


1
inject, sembolü bağımsız değişken olarak alabilir, böylece kullanarak bir karakteri kaydedebilirsiniz inject(:+). İşte dokümanlardan örnek: (5..10).reduce(:+).
Michael Kohl

@Michael: Teşekkürler :). Ayrıca sadece bir 8yerde olması gerektiğini fark ettim *eğer kimse bu çalışan sorunları vardı.
Nemo157

6

Java, 523519444430429 karakter

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);}}

Son satırdaki iki 4s, kullanılacak iplik sayısıdır.

50000! Aşağıdaki çerçeve ile test edilmiştir (orijinal sürümün ungolfed sürümü ve birkaç daha az kötü uygulama ile - hala bol miktarda olmasına rağmen) (4 çekirdekli Linux makinemde) kez verir

7685ms
2338ms
1361ms
1093ms
7724ms

Jit ısınmış olabilir, çünkü testi adil olmak için bir iplikle tekrarladım.

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;
    }
}

Bigints ile Java golf için doğru dil değildir (sadece sefil şeyleri inşa etmek için ne yapmam gerektiğini bak, çünkü uzun süren yapıcı özeldir), ama hey.

Çözümsüz koddan işi nasıl parçaladığı tamamen açık olmalıdır: her bir iş parçacığı, bir eşdeğerlik sınıfı modulo iş parçacığı sayısını çarpar. Kilit nokta, her iş parçacığının kabaca aynı miktarda iş yapmasıdır.


5

CSharp - 206 215 karakter

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);}}

Hesaplamayı C # Parallel.For () işleviyle böler.

Düzenle; Kilidi unuttum

Yürütme süreleri:

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

Perl, 140

Alır Nstandart girişten.

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}

Özellikleri:

  • hesaplama bölünmesi: bir tarafta eşitlenir ve diğer tarafta olasılıklar (bundan daha karmaşık bir şey, hesaplama yükünü uygun şekilde dengelemek için çok fazla karaktere ihtiyaç duyar.
  • IPC, paylaşılan bir anonim dosya kullanarak.

Karşılaştırma:

  • 10000! 2.3s çatallı, 3.4s çatalsız
  • 100000! 5'08.8 çatallı, 7'07.9 baskısız olarak basılmıştır

4

Scala ( 345 266 244 232 214 karakter)

Aktörleri Kullanma:

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)}

Düzenleme - kaldırıldı referanslar için System.currentTimeMillis()dışarı çarpanlarına, a(1).toInt, değiştirilen List.rangeiçinx to y

Düzenle 2 - whiledöngüyü a forolarak değiştirdi, sol katlamayı aynı şeyi yapan bir liste işleviyle değiştirdi, 6 karakterlik BigInttürün yalnızca bir kez görünmesi için örtük tür dönüşümlerine dayanarak yazdırmak için değiştirdi

Edit 3 - Scala'da birden çok bildirimin nasıl yapılacağını öğrendim

Edit 4 - Bunu ilk yaptığımdan beri öğrendiğim çeşitli optimizasyonlar

Ungolfed sürümü:

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

Scala-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)(_*_)))(_*_))}

ungolfed:

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 faktöriyel 4 Liste oluşturularak 4 çekirdek üzerinde hesaplanır:

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

paralel olarak çarpılır. Sayıları dağıtmak için daha basit bir yaklaşım olurdu:

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

Ancak dağıtım o kadar iyi olmazdı - daha küçük sayılar aynı listede biter, bir diğerinde en yüksek olur, son listede daha uzun hesaplamaya yol açar (yüksek N'ler için son iş parçacığı neredeyse boş olmaz ancak en azından (N / çekirdek) çekirdek çekirdekleri içerir.

2.9 sürümündeki Scala, paralel çağrıyı kendileri ele alan paralel Koleksiyonlar içerir.


2

Erlang - 295 karakter.

Erlang'da yazdığım ilk şey, bu yüzden birisi bunu kolayca yarıya indirebilirse şaşırmazdım:

-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).

Önceki Ruby girişimle aynı iş parçacığı modelini kullanır: aralığı alt aralığa böler ve aralıkları ayrı iş parçacıklarıyla birlikte çarpar, ardından sonuçları ana iş parçacığında geri çarpar.

Ben escript çalışma almak nasıl bu yüzden sadece kaydetmek f.erlve erl açmak ve çalıştırmak anlayamadı:

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

uygun ikamelerle.

MacBook Air (çift çekirdekli) üzerinde 2 işlemde 50000 için 8s ve 1 işlem için 10s var.

Not: Faktöriyel sayıdan daha fazla işlem yapmaya çalışırsanız donar.

Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.