Esse golfe exige que um cálculo fatorial seja dividido entre vários segmentos ou processos.
Alguns idiomas facilitam a coordenação do que outros, por isso é agnóstico. Um código de exemplo não-fornecido é fornecido, mas você deve desenvolver seu próprio algoritmo.
O objetivo do concurso é ver quem consegue criar o algoritmo fatorial multicore mais curto (em bytes, não em segundos) para calcular o N! medido pelos votos quando o concurso é encerrado. Deveria haver uma vantagem multicore, portanto, exigiremos que funcione para N ~ 10.000. Os eleitores devem votar se o autor não fornecer uma explicação válida de como ele distribui o trabalho entre processadores / núcleos e votar com base na concisão do golfe.
Por curiosidade, publique alguns números de desempenho. Em algum momento, pode haver uma troca entre desempenho e pontuação do golfe, vá com o golfe desde que atenda aos requisitos. Eu ficaria curioso para saber quando isso ocorre.
Você pode usar bibliotecas inteiras grandes de núcleo único normalmente disponíveis. Por exemplo, o perl geralmente é instalado com o bigint. No entanto, observe que simplesmente chamar uma função fatorial fornecida pelo sistema normalmente não dividirá o trabalho em vários núcleos.
Você deve aceitar de STDIN ou ARGV a entrada N e a saída para STDOUT o valor de N !. Opcionalmente, você pode usar um segundo parâmetro de entrada para também fornecer o número de processadores / núcleos para o programa, para que ele não faça o que você verá abaixo :-) Ou você pode criar explicitamente para 2, 4, o que você tiver disponível.
Vou postar meu próprio exemplo de oddball perl abaixo, enviado anteriormente no Stack Overflow em Algoritmos fatoriais em diferentes idiomas . Não é golfe. Vários outros exemplos foram apresentados, muitos deles golfe, mas muitos não. Por causa do licenciamento compartilhado, fique à vontade para usar o código em qualquer exemplo no link acima como ponto de partida.
O desempenho no meu exemplo é pouco claro por várias razões: ele usa muitos processos, muita conversão de string / bigint. Como eu disse, é um exemplo intencionalmente estranho. Ele calculará 5000! em menos de 10 segundos em uma máquina de 4 núcleos aqui. No entanto, um forro mais óbvio de dois for / next loop pode fazer 5000! em um dos quatro processadores no 3.6s.
Você definitivamente terá que fazer melhor do que isso:
#!/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);
}
}
Meu interesse nisso é simplesmente (1) aliviar o tédio; e (2) aprender algo novo. Este não é um problema de lição de casa ou de pesquisa para mim.
Boa sorte!
Respostas:
Mathematica
Uma função com capacidade paralela:
Onde g é
Identity
ouParallelize
depende do tipo de processo necessárioPara o teste de temporização, modificaremos ligeiramente a função, para que ela retorne a hora do relógio real.
E testamos os dois modos (de 10 ^ 5 a 9 * 10 ^ 5): (apenas dois kernels aqui)
Resultado:
fonte
Haskell:
209200198177 chars176167 fonte +3310 sinalizador do compiladorEssa solução é bem boba. Aplica o produto em paralelo a um valor do tipo
[[Integer]]
, onde as listas internas têm no máximo dois itens. Quando a lista externa estiver reduzida a, no máximo, 2 listas, a aplainamos e levamos o produto diretamente. E sim, o verificador de tipos precisa de algo anotado com Inteiro, caso contrário, não será compilado.(Sinta-se à vontade para ler a parte do meio
f
entreconcat
es
como "até que eu tenha o comprimento")As coisas pareciam estar indo muito bem, já que o parMap, do Control.Parallel.Strategies, facilita bastante o farm para vários segmentos. No entanto, parece que o GHC 7 requer 33 caracteres impressionantes em opções de linha de comando e variáveis de ambiente para realmente permitir que o tempo de execução encadeado use vários núcleos (que eu incluí no total).A menos que esteja faltando alguma coisa, o que épossíveldefinitivamente é o caso . ( Atualização : o tempo de execução do GHC encadeado parece usar encadeamentos N-1, onde N é o número de núcleos, portanto, não é necessário mexer nas opções de tempo de execução.)Compilar:
O tempo de execução, no entanto, foi muito bom, considerando o número ridículo de avaliações paralelas sendo desencadeadas e que eu não compilei com -O2. Para 50000! em um MacBook dual-core, recebo:
Tempo total para alguns valores diferentes, a primeira coluna é o paralelo de golfe, a segunda é a versão seqüencial ingênua:
Para referência, a versão seqüencial ingênua é esta (que foi compilada com -O2):
fonte
Ruby - 111 + 56 = 167 caracteres
Este é um script de dois arquivos, o arquivo principal (
fact.rb
):o arquivo extra (
f2.rb
):Simplesmente pega o número de processos e o número a serem calculados como args e divide o trabalho em intervalos que cada processo pode calcular individualmente. Em seguida, multiplica os resultados no final.
Isso realmente mostra o quanto o Rubinius é mais lento para o YARV:
Rubinius:
Ruby1.9.2:
(Observe o extra
0
)fonte
inject(:+)
. Aqui está o exemplo de docs:(5..10).reduce(:+)
.8
onde deveria haver um*
se alguém tivesse problemas para executar isso.Java,
52351944430309caracteresOs dois 4s na linha final são o número de threads a serem usados.
50000! testado com a seguinte estrutura (versão simples da versão original e com poucas práticas ruins - embora ainda haja bastante) dá (na minha máquina Linux de quatro núcleos) vezes
Observe que eu repeti o teste com uma única thread para garantir a justificativa, porque o jit pode ter esquentado.
Java com bigints não é a linguagem certa para o golfe (veja o que tenho que fazer apenas para construir coisas ruins, porque o construtor que demora muito é privado), mas ei.
Deveria ser completamente óbvio, a partir do código não-destruído, como ele divide o trabalho: cada thread multiplica uma classe de equivalência modulo pelo número de threads. O ponto principal é que cada thread realiza aproximadamente a mesma quantidade de trabalho.
fonte
CSharp -
206215 caracteresDivide o cálculo com a funcionalidade C # Parallel.For ().
Editar; Esqueceu o bloqueio
Tempos de execução:
fonte
Perl, 140
Toma
N
da entrada padrão.Recursos:
Referência:
fonte
Scala (
345266244232214 caracteres)Usando atores:
Editar - referências removidas
System.currentTimeMillis()
, fatoradasa(1).toInt
, alteradas deList.range
parax to y
Editar 2 - alterou o
while
loop para afor
, alterou a dobra esquerda para uma função de lista que faz a mesma coisa, contando com conversões implícitas de tipo, para que oBigInt
tipo de 6 caracteres apareça apenas uma vez, alterou println para imprimirEdit 3 - descobriu como fazer múltiplas declarações no Scala
Edição 4 - várias otimizações que aprendi desde que fiz isso pela primeira vez
Versão não destruída:
fonte
Scala-2.9.0 170
ungolfed:
O fatorial de 10 é calculado em 4 núcleos, gerando 4 listas:
que são multiplicados em paralelo. Teria havido uma abordagem mais simples para distribuir os números:
Mas a distribuição não seria tão boa - os números menores terminariam na mesma lista, os mais altos em outro, levando ao cálculo mais longo da última lista (para Ns altos, o último encadeamento não estaria quase vazio , mas pelo menos contém elementos (N / núcleos) -cores.
O Scala na versão 2.9 contém coleções paralelas, que tratam da chamada paralela.
fonte
Erlang - 295 caracteres.
A primeira coisa que escrevi em Erlang para não me surpreender se alguém pudesse facilmente reduzir pela metade:
Usa o mesmo modelo de encadeamento que minha entrada anterior do Ruby: divide o intervalo em sub-intervalo e multiplica os intervalos juntos em threads separados e multiplica os resultados novamente no thread principal.
Eu não consegui descobrir como fazer o escript funcionar, então salve como
f.erl
e abra erl e execute:com substituições apropriadas.
Consegui cerca de 8s para 50000 em 2 processos e 10s para 1 processo, no meu MacBook Air (núcleo duplo).
Nota: Apenas observe que ele congela se você tentar mais processos do que o número para fatorar.
fonte