Nhân tố ngã ba


12

Golf này đòi hỏi một tính toán giai thừa được phân chia giữa nhiều luồng hoặc quá trình.

Một số ngôn ngữ làm cho điều này dễ phối hợp hơn những ngôn ngữ khác, vì vậy đó là thuyết bất khả tri. Mã ví dụ được cung cấp, nhưng bạn nên phát triển thuật toán của riêng mình.

Mục tiêu của cuộc thi là để xem ai có thể đưa ra thuật toán nhân tử đa lõi ngắn nhất (tính bằng byte, không phải giây) để tính N! được đo bằng số phiếu khi cuộc thi kết thúc. Cần có một lợi thế đa lõi, vì vậy chúng tôi sẽ yêu cầu nó phải hoạt động với giá trị N ~ 10.000. Các cử tri nên bỏ phiếu nếu tác giả không đưa ra lời giải thích hợp lệ về cách thức phân tán công việc giữa các bộ xử lý / lõi và bỏ phiếu dựa trên sự đồng nhất của golf.

Để tò mò, xin vui lòng gửi một số số hiệu suất. Có thể có một hiệu suất so với đánh đổi điểm golf tại một số điểm, đi với golf miễn là nó đáp ứng các yêu cầu. Tôi tò mò muốn biết khi nào điều này xảy ra.

Bạn có thể sử dụng các thư viện số nguyên lớn lõi đơn có sẵn thông thường. Ví dụ, perl thường được cài đặt với bigint. Tuy nhiên, lưu ý rằng chỉ cần gọi một hệ thống được cung cấp chức năng giai thừa thường sẽ không phân chia công việc trên nhiều lõi.

Bạn phải chấp nhận từ STDIN hoặc ARGV đầu vào N và đầu ra để STDOUT giá trị của N!. Bạn có thể tùy ý sử dụng tham số đầu vào thứ 2 để cung cấp số lượng bộ xử lý / lõi cho chương trình để nó không làm những gì bạn thấy dưới đây :-) Hoặc bạn có thể thiết kế rõ ràng cho 2, 4, bất cứ điều gì bạn có sẵn.

Tôi sẽ đăng ví dụ perl lẻ của riêng tôi bên dưới, trước đây đã được gửi trên Stack Overflow theo Thuật toán nhân tố trong các ngôn ngữ khác nhau . Nó không phải là golf. Vô số ví dụ khác đã được gửi, nhiều người trong số họ chơi golf nhưng nhiều người thì không. Do cấp phép chia sẻ giống nhau, vui lòng sử dụng mã trong bất kỳ ví dụ nào trong liên kết ở trên làm điểm bắt đầu.

Hiệu suất trong ví dụ của tôi là mờ nhạt vì một số lý do: nó sử dụng quá nhiều quá trình, quá nhiều chuyển đổi chuỗi / bigint. Như tôi đã nói đó là một ví dụ kỳ quặc có chủ ý. Nó sẽ tính 5000! trong dưới 10 giây trên máy 4 lõi ở đây. Tuy nhiên, hai lớp lót cho vòng lặp tiếp theo / rõ ràng hơn có thể thực hiện 5000! trên một trong bốn bộ xử lý trong 3.6s.

Bạn chắc chắn sẽ phải làm tốt hơn thế này:

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

Quan tâm của tôi về điều này chỉ đơn giản là (1) giảm bớt sự nhàm chán; và (2) học một cái gì đó mới. Đây không phải là một bài tập về nhà hoặc vấn đề nghiên cứu đối với tôi.

Chúc may mắn!


10
Bạn không thể đếm mã ngắn nhất bằng phiếu bầu, và yêu cầu chơi gôn và đa luồng dường như không tốt với nhau.
aaaaaaaaaaaa

Máy tính xách tay lõi đơn cổ của tôi có thể làm 10000! trong chưa đầy 0,2 giây trong Python.
gnibbler

Đa luồng một quy trình liên kết với CPU sẽ hầu như luôn làm chậm nó. Tất cả những gì bạn đang làm là thêm chi phí với ít hoặc không tăng hiệu suất. Đa luồng là dành cho I / O-Wait.
mellamokb

2
@mellamokb: Tôi xin khác với các hệ thống đa lõi.
Joey

@Jey: À. Bỏ lỡ chi tiết nhỏ đó: s Đồng ý
mellamokb

Câu trả lời:


7

Toán học

Một chức năng có khả năng song song:

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

Trường hợp g là Identityhoặc Parallelizetùy thuộc vào loại quy trình cần thiết

Đối với thử nghiệm thời gian, chúng tôi sẽ sửa đổi một chút chức năng, để nó trả về thời gian thực của đồng hồ.

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

Và chúng tôi kiểm tra cả hai chế độ (từ 10 ^ 5 đến 9 * 10 ^ 5): (chỉ có hai hạt nhân ở đây)

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

Kết quả: nhập mô tả hình ảnh ở đây


Bạn có thiếu a] trong dòng mã đầu tiên không? Có vẻ không cân đối.
Peter Taylor

@Peter Cảm ơn, "]" cuối cùng đã không đi qua bộ đệm sao chép. Đã sửa.
Tiến sĩ belisarius

1
Điều này dường như là ngắn nhất. Nó cũng giống như nhanh nhất, trừ khi tôi đang đọc sai một cái gì đó. Tôi không còn đăng ký Mathicala nữa nên không thể xác minh. Cảm ơn vì đã tham gia.
Paul

7

Haskell: 209 200 198 177 ký tự

176 167 nguồn + 33 10 cờ biên dịch

Giải pháp này khá ngớ ngẩn. Nó áp dụng sản phẩm song song với một giá trị của loại[[Integer]] , trong đó danh sách bên trong dài tối đa hai mục. Khi danh sách bên ngoài giảm tối đa 2 danh sách, chúng tôi sẽ làm phẳng nó và lấy sản phẩm trực tiếp. Và vâng, trình kiểm tra loại cần một cái gì đó được chú thích bằng Integer, nếu không nó sẽ không được biên dịch.

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

(Hãy đọc phần fgiữa của giữaconcatsdưới dạng "cho đến khi tôi cảm nhận được chiều dài")

Mọi thứ dường như sẽ khá tốt vì parMap từ Control.Parallel.Strargeties làm cho nó khá dễ dàng để đưa nó ra nhiều luồng. Tuy nhiên, có vẻ như GHC 7 yêu cầu 33 ký tự trong các tùy chọn dòng lệnh và vars môi trường để thực sự cho phép thời gian chạy luồng để sử dụng nhiều lõi (mà tôi đã bao gồm trong tổng số). Trừ khi tôi đang thiếu một cái gì đó, đó là điều chắc chắn có thể xảy ra . ( Cập nhật : thời gian chạy GHC có luồng xuất hiện để sử dụng các luồng N-1, trong đó N là số lõi, do đó không cần phải sử dụng các tùy chọn thời gian chạy.)

Để biên dịch:

ghc -threaded prog.hs

Tuy nhiên, thời gian chạy khá tốt khi xem xét số lượng các đánh giá song song lố bịch được đưa ra và tôi đã không biên dịch với -O2. Với giá 50000! trên MacBook lõi kép, tôi nhận được:

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)

Tổng số lần cho một vài giá trị khác nhau, cột đầu tiên là song song golf, cột thứ hai là phiên bản tuần tự ngây thơ:

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

Để tham khảo, phiên bản tuần tự ngây thơ là cái này (được biên dịch với -O2):

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

1
IMO, bạn không phải đếm các đối số cho trình biên dịch và trình thông dịch.
FUZxxl

@FUZxxl: Thông thường tôi sẽ đồng ý, nhưng vấn đề này đặc biệt yêu cầu nó chạy trong nhiều luồng hoặc quy trình, và những cờ đó được yêu cầu để thực hiện điều đó (với GHC 7.0.2, từ Nền tảng Haskell mới nhất, ít nhất).

6

Ruby - 111 + 56 = 167 ký tự

Đây là tập lệnh hai tập tin, tập tin chính ( 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(:*)

tệp phụ ( f2.rb):

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

Đơn giản chỉ cần lấy số lượng các quy trình và số lượng để tính toán như các đối số và phân chia công việc thành các phạm vi mà mỗi quy trình có thể tính toán riêng lẻ. Sau đó nhân kết quả vào cuối.

Điều này thực sự cho thấy Rubinius chậm hơn bao nhiêu đối với YARV:

Rubinius:

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

Ruby1.9.2:

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

(Lưu ý thêm 0)


1
chích có thể lấy một biểu tượng làm đối số, vì vậy bạn có thể lưu một ký tự bằng cách sử dụng inject(:+). Đây là ví dụ từ các tài liệu : (5..10).reduce(:+).
Michael Kohl

@Michael: Cảm ơn :). Cũng chỉ cần lưu ý rằng tôi đã có một 8nơi đáng lẽ phải có *nếu có ai gặp vấn đề khi chạy cái này.
Nemo157

6

Java, 523 519 434 430 429 ký tự

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

Hai 4s trong dòng cuối cùng là số lượng chủ đề để sử dụng.

50000! đã được thử nghiệm với khung sau (phiên bản chưa được chỉnh sửa của phiên bản gốc và với một số thực tiễn xấu ít hơn - mặc dù nó vẫn còn nhiều) cho (trên máy Linux 4 nhân của tôi) lần

7685ms
2338ms
1361ms
1093ms
7724ms

Lưu ý rằng tôi đã lặp lại bài kiểm tra với một luồng cho công bằng vì jit có thể đã ấm lên.

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 với các gợi ý không phải là ngôn ngữ phù hợp để chơi gôn (hãy nhìn vào những gì tôi phải làm chỉ để xây dựng những thứ tồi tệ, bởi vì hàm tạo mất nhiều thời gian là riêng tư), nhưng hey.

Nó phải hoàn toàn rõ ràng từ mã không mã hóa làm thế nào nó phá vỡ công việc: mỗi luồng nhân một lớp tương đương modulo số lượng luồng. Điểm mấu chốt là mỗi luồng thực hiện gần như cùng một lượng công việc.


5

CSharp - 206 215 ký tự

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

Chia nhỏ tính toán với chức năng C # Parallel.For ().

Biên tập; Quên khóa

Thời gian thực hiện:

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

Lấy Ntừ đầu vào tiêu chuẩ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}

Đặc trưng:

  • phân chia tính toán: phát triển ở một bên và tỷ lệ cược ở bên kia (bất cứ điều gì phức tạp hơn thế sẽ cần rất nhiều ký tự để cân bằng tải tính toán một cách thích hợp.
  • IPC sử dụng một tệp ẩn danh được chia sẻ.

Điểm chuẩn:

  • 10000! được in trong 2.3s ngã ba, 3,4 giây
  • 100000! được in trong 5'08.8 ngã ba, 7'07.9 không mở

4

Scala ( 345 266 244 232 214 ký tự)

Sử dụng diễn viên:

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

Chỉnh sửa - xóa tham chiếu đến System.currentTimeMillis(), bao thanh toán a(1).toInt, thay đổi từ List.rangethànhx to y

Chỉnh sửa 2 - thay đổi whilevòng lặp thành a for, thay đổi nếp gấp bên trái thành chức năng danh sách thực hiện điều tương tự, dựa trên chuyển đổi loại ẩn để 6 charBigInt chỉ xuất hiện một lần, thay đổi println thành in

Chỉnh sửa 3 - tìm ra cách thực hiện nhiều khai báo trong Scala

Chỉnh sửa 4 - tối ưu hóa khác nhau tôi đã học được từ khi tôi mới làm điều này

Phiên bản bị đánh cắp:

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

vô dụng:

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

}

Giai thừa của 10 được tính trên 4 lõi bằng cách tạo 4 Danh sách:

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

được nhân lên song song. Sẽ có một cách tiếp cận đơn giản hơn để phân phối các số:

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

Nhưng phân phối sẽ không tốt như vậy - tất cả các số nhỏ hơn sẽ kết thúc trong cùng một danh sách, cao nhất trong một danh sách khác, dẫn đến tính toán dài hơn trong danh sách cuối cùng (đối với Ns cao, chuỗi cuối cùng sẽ gần như trống rỗng , nhưng ít nhất chứa các phần tử lõi (N / lõi).

Scala trong phiên bản 2.9 chứa Bộ sưu tập song song, tự xử lý việc gọi song song.


2

Erlang - 295 ký tự.

Điều đầu tiên tôi từng viết bằng Erlang vì vậy tôi sẽ không ngạc nhiên nếu ai đó có thể dễ dàng giảm một nửa điều này:

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

Sử dụng cùng một mô hình luồng như mục Ruby trước đây của tôi: chia phạm vi thành phạm vi phụ và nhân các phạm vi lại với nhau trong các luồng riêng biệt sau đó nhân kết quả trở lại trong luồng chính.

Tôi không thể tìm ra làm thế nào để escript hoạt động để chỉ cần lưu f.erlvà mở erl và chạy:

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

với sự thay thế phù hợp.

Có khoảng 8 giây cho 50000 trong 2 quy trình và 10 giây cho 1 quy trình, trên MacBook Air (lõi kép) của tôi.

Lưu ý: Chỉ cần lưu ý rằng nó đóng băng nếu bạn cố gắng nhiều quá trình hơn số lượng để giai thừa.

Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.