계승


12

이 골프는 여러 스레드 또는 프로세스로 팩토리얼 계산을 분할해야합니다.

일부 언어는이를 다른 언어보다 쉽게 ​​조정할 수 있으므로 언어에 구애받지 않습니다. Ungolfed 예제 코드가 제공되지만 자체 알고리즘을 개발해야합니다.

콘테스트의 목표는 누가 N을 계산하기 위해 가장 짧은 (초가 아닌 바이트 단위) 멀티 코어 팩토리얼 알고리즘을 만들 수 있는지 확인하는 것입니다! 컨테스트가 종료 될 때 투표로 측정됩니다. 멀티 코어 이점이 있어야하므로 N ~ 10,000에서 작동해야합니다. 저자가 작업을 프로세서 / 코어에 분산시키는 방법에 대한 유효한 설명을 제공하지 못하고 골프 간결성을 기반으로 투표하는 경우 유권자는 투표를해야합니다.

호기심을 위해 몇 가지 성능 수치를 게시하십시오. 어떤 시점에서는 성능 대 골프 점수 트레이드 오프가있을 수 있습니다. 골프가 요구 사항을 충족하는 한 골프와 함께하십시오. 언제 이런 일이 발생했는지 궁금합니다.

일반적으로 사용 가능한 단일 코어 큰 정수 라이브러리를 사용할 수 있습니다. 예를 들어, perl은 일반적으로 bigint와 함께 설치됩니다. 그러나 단순히 시스템에서 제공하는 계승 함수를 호출한다고해서 작업이 여러 코어로 분할되지는 않습니다.

STDIN 또는 ARGV에서 입력 N을 승인하고 N! 값을 STDOUT에 출력해야합니다. 선택적으로 두 번째 입력 매개 변수를 사용하여 프로그램에 프로세서 / 코어 수를 제공하여 아래에 표시되는 작업을 수행하지 않도록 할 수 있습니다.

이전 에 다른 언어의 요인 알고리즘 아래 에서 스택 오버플로에 제출 된 내 홀수 펄 예제를 게시 할 것 입니다. 골프가 아닙니다. 많은 다른 예들이 제출되었는데, 그 중 많은 것은 골프이지만 많은 것은 아닙니다. 공유와 유사한 라이센스로 인해 위 링크의 모든 예제 코드를 출발점으로 자유롭게 사용하십시오.

내 예제의 성능은 여러 가지 이유로 인해 부족합니다. 너무 많은 프로세스, 너무 많은 문자열 / 큰 변환을 사용합니다. 내가 말했듯이 그것은 의도적으로 이상한 예입니다. 5000을 계산합니다! 여기서 4 초 머신에서 10 초 안에 그러나보다 확실한 두 개의 라이너 for / next 루프는 5000을 수행 할 수 있습니다! 3.6 년대에 4 개의 프로세서 중 하나에서

이보다 확실히 더 잘해야합니다.

#!/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 초 안에
gnibbler

CPU 바인딩 프로세스를 멀티 스레딩하면 거의 항상 속도가 느려집니다. 성능 향상이 거의 없거나 전혀없이 오버 헤드를 추가하기 만하면됩니다. 멀티 스레딩은 I / O 대기 용입니다.
mellamokb

2
@mellamokb : 멀티 코어 시스템에 따라달라고 간청합니다.
Joey

@Joey : 아. 작은 세부 사항 놓친 :이야 합의
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}]}]   

결과: 여기에 이미지 설명을 입력하십시오


첫 번째 코드 줄에]가 누락되어 있습니까? 불균형으로 보입니다.
피터 테일러

@Peter Thanks, 마지막 "]"은 (는) 복사 버퍼를 통과하지 못했습니다. 수정했습니다.
Dr. belisarius

1
가장 짧은 것 같습니다. 내가 무언가를 잘못 읽지 않는 한 그것은 가장 빠른 것처럼 보입니다. 더 이상 Mathematica에 가입하지 않으므로 확인할 수 없습니다. 참여해 주셔서 감사합니다.
Paul

7

하스켈 : 209 개 200 198 177 문자

176 167 원 + 33 10 컴파일러 플래그

이 솔루션은 꽤 바보입니다. [[Integer]]내부 목록이 최대 2 개의 항목 길이 인 type 값에 병렬로 product를 적용 합니다. 외부 목록이 최대 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사이 concats같은을 "마음 I 길이까지")

Control.Parallel.Strate의 parMap 이후로 상황이 꽤 좋을 것 같았습니다.이를 여러 스레드로 쉽게 만들 수 있습니다. 그러나 GHC 7은 실제로 스레드 런타임이 여러 코어 (전체에 포함)를 사용할 수 있도록 명령 줄 옵션과 환경 변수에 무려 33 문자가 필요합니다. 내가 뭔가를 놓치지 않는 한, 분명히 가능 합니다 . ( 업데이트 : 스레드 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 : 일반적으로 동의하지만이 문제는 특히 여러 스레드 또는 프로세스에서 실행되도록 요청했으며 그 플래그를 적용하려면 (최소 Haskell 플랫폼의 GHC 7.0.2 이상) 필요합니다.

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(:*)

단순히 프로세스 수와 수를 취하여 인수로 계산하고 각 프로세스를 개별적으로 계산할 수있는 범위로 작업을 분할합니다. 그런 다음 끝에 결과를 곱합니다.

이것은 실제로 Rubinius가 YARV보다 얼마나 느린 지 보여줍니다.

루비니 우스 :

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

루비 1.9.2 :

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

(추가 참고 0)


1
inject는 인수로 기호를 사용할 수 있으므로을 사용하여 문자를 저장할 수 있습니다 inject(:+). 문서의 예는 다음과 같습니다 (5..10).reduce(:+)..
Michael Kohl

@ 마이클 : 감사합니다 :). 또한 누군가가 이것을 실행하는 데 문제 8*있다면 내가 있어야 할 곳이 있다는 것을 알았습니다 .
Nemo157

6

Java, 523519434430 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

jit이 예열되었을 수 있으므로 공정성을 위해 하나의 스레드로 테스트를 반복했습니다.

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가있는 Java는 골프에 적합한 언어가 아닙니다 (오래 걸리는 생성자가 비공개이기 때문에 비참한 것을 구성하기 위해해야 ​​할 일을보십시오).

ungolfed 코드에서 작업을 분리하는 방법이 완전히 분명해야합니다. 각 스레드는 스레드 수에 등가 클래스 모듈로를 곱합니다. 핵심은 각 스레드가 대략 같은 양의 작업을 수행한다는 것입니다.


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.3 초 분기, 3.4 초 분기 인쇄
  • 100000! 5'08.8 분기, 7'07.9 분기에 인쇄됩니다

4

스칼라 ( 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

편집 2- while루프를 a for로 변경하고 왼쪽 폴드를 동일한 기능을 수행하는 목록 함수로 변경하고 암시 적 유형 변환에 의존하여 6 문자 BigInt유형이 한 번만 나타나 도록 println을 인쇄로 변경

편집 3-스칼라에서 여러 선언을 수행하는 방법을 발견했습니다.

편집 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) (_ * _)))(_ * _))

}

4의 목록을 생성하여 4의 코어에서 10의 계승을 계산합니다.

  • 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)-코어 요소를 포함해야합니다.

버전 2.9의 Scala에는 병렬 호출 자체를 처리하는 병렬 콜렉션이 포함되어 있습니다.


2

얼랭-295 자

내가 Erlang으로 쓴 첫 번째 내용은 누군가 이것을 쉽게 반으로 줄일 수 있다면 놀라지 않을 것입니다.

-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 항목과 동일한 스레딩 모델을 사용합니다. 범위를 하위 범위로 나누고 별도의 스레드로 범위를 곱한 다음 결과를 기본 스레드로 다시 곱합니다.

나는 escript를 작동시키는 방법을 알 수 없었으므로 f.erlerl 로 저장 하고 열고 실행하십시오.

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

적절한 대체로.

MacBook Air (이중 코어)에서 2 개의 프로세스에서 50000에 대해 8 초, 1 개의 프로세스에 대해 10s를 얻었습니다.

참고 : 계승 화하는 수보다 많은 프로세스를 시도하면 작동이 멈추는 것을 알았습니다.

당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.