Houve um bilhão de iterações de desafios de Fibonacci neste site, então vamos apimentar as coisas com um desafio de Fibonacci de um bilhão de iterações!
Seu desafio é gerar os primeiros 1000 dígitos decimais do número de 1.000.000.000.000 de Fibonacci com o menor programa possível. Opcionalmente, isso pode ser seguido por qualquer saída adicional de sua escolha, incluindo mas não se limitando ao restante dos dígitos.
Eu estou usando a convenção de que fib 0 = 0
, fib 1 = 1
.
Seu programa deve ser rápido o suficiente para você executá-lo e verificar sua correção. Para esse fim, aqui estão os primeiros 1000 dígitos:
7952317874554683467829385196197148189255542185234398913453039937343246686182519370050999626136556779332482035723222451226291714456275648259499530612111301255499879639516053459789018700567439946844843034599802419924043753401950114830107234265037841426980398387360784284231996457340782784200767760907777703183185744656536253511502851715963351023990699232595471322670365506482435966586886048627159716916351448788527427435508113909167963907380398242848033980110276370544264285032744364781198451825462130529529633339813483105771370128111851128247136311414208318983802526907917787094802217750859685116363883374847428036737147882079956688807509158372249451437519320162582002000530798309887261257028201907509370554232931107084976854715833585623910450679449120011564762925649144509531904684984417002512086504020779012501356177874199605085558317190905395134468919443313026824813363234190494375599262553025466528838122639433600483849535070647711986769279568548796855207684897741771784375859496425384355879105799
code-golf
kolmogorov-complexity
fibonacci
restricted-time
user1502040
fonte
fonte
Your program must be fast enough for you to run it and verify its correctness.
e a memória?a+=b;b+=a;
loop (talvez com o Java BigInteger) é a escolha óbvia, pelo menos se você estiver pensando em desempenho. Uma implementação recursiva sempre me pareceu terrivelmente ineficiente.write()
chamada de sistema). Eu gosto dos requisitos de desempenho, que tornaram muito mais divertido para mim.Respostas:
Python 2 + sympy, 72 bytes
Experimente online!
-10 bytes removendo o termo praticamente 0, graças a Jeff Dege
-1 byte (1000 -> 1e3 graças a Zacharý)
-2 bytes removendo a variável desnecessária graças a Erik the Outgolfer
-2 bytes movendo-se para Python 2 graças a Zacharý
-3 bytes por 11'ing os
-11
agradecimentos a ThePirateBay -3 bytes trocandostr
por backticks graças a notjaganagora supera a solução haskell não publicada do OP!
fonte
from sympy import*;sqrt
não poupa bytes sobreimport sympy;sympy.sqrt
:)sympy
é um pacote simbólico de matemática para o Python, portanto não há problemas com erros de arredondamento, pelo menos até números muito grandes (esse número não é grande o suficiente). Então eu apenas calculo para me fornecer os primeiros dígitos 1e3, porque, caso contrário, se você remover a.evalf(1e3)
peça, ela me dará uma representação de notação científica muito curta.Python 2 , 106 bytes
Experimente online!
Sem bibliotecas, apenas aritmética inteira. É executado quase instantaneamente.
O núcleo é a identidade de dividir e conquistar:
Isso nos permite atualizar
(a,b) = (f(n),f(n+1))
para o dobron -> 2*n
. Como queremos obtern=10**9
, são necessárias apenaslog_2(10**9)=30
iterações. Nós construímosn
até10**9
por repetidamente fazern->2*n+c
para cada dígitoc
de sua expansão binário. Quandoc==1
, o valor dobrado é aumentado2*n -> 2*n+1
com um deslocamento de Fibonacci em uma etapa(a,b)=(b+a,b)
Para manter os valores
a,b
gerenciáveis, armazenamos apenas os primeiros1006
dígitos dividindo o piso10
até que eles estejam abaixo2**3340 ~ 1e1006
.fonte
a,b,c=a*a+b*b,a*a-c*c,b*b+c*c
.código de máquina x86 de 32 bits (com chamadas do sistema Linux):
106105 byteschangelog: salvou um byte na versão rápida porque uma constante off-by-one não altera o resultado para Fib (1G).
Ou 102 bytes para uma versão 18% mais lenta (no Skylake) (usando
mov
/sub
/ emcmc
vez delea
/cmp
no loop interno, para gerar execução e empacotamento em10**9
vez de2**32
). Ou 101 bytes para uma versão mais lenta ~ 5.3x com uma ramificação no processamento de transporte no loop mais interno. (Avaliei uma taxa de desvio de agência de 25,4%!)Ou 104/101 bytes, se um zero inicial for permitido. (É necessário 1 byte extra para o código físico pular 1 dígito da saída, que é o que é necessário para a Fib (10 ** 9)).
Infelizmente, o modo NASM do TIO parece ignorar
-felf32
nos sinalizadores do compilador. Aqui está um link de qualquer maneira com meu código fonte completo, com toda a bagunça de idéias experimentais nos comentários.Este é um programa completo . Ele imprime os primeiros 1000 dígitos de Fib (10 ** 9), seguidos de alguns dígitos extras (os últimos estão errados), seguidos de alguns bytes de lixo (sem incluir uma nova linha). A maior parte do lixo não é ASCII, portanto, você pode querer passar por ele
cat -v
.konsole
Porém, não quebra meu emulador de terminal (KDE ). Os "bytes de lixo" estão armazenando Fib (999999999). Eu já tinha-1024
um registro, por isso era mais barato imprimir 1024 bytes do que o tamanho adequado.Estou contando apenas o código da máquina (tamanho do segmento de texto do meu executável estático), não o buço que o torna um executável ELF. ( Executáveis ELF muito pequenos são possíveis , mas eu não queria me preocupar com isso). Acabou sendo mais curto usar a pilha de memória em vez do BSS, para que eu possa justificar não contar mais nada no binário, pois não dependo de nenhum metadado. (Produzir um binário estático sem strip da maneira normal torna um ELF de 340 bytes executável.)
Você poderia criar uma função desse código que poderia chamar de C. Custaria alguns bytes para salvar / restaurar o ponteiro da pilha (talvez em um registro MMX) e outras sobrecargas, mas também salvar bytes retornando com a string na memória, em vez de fazer uma
write(1,buf,len)
chamada do sistema. Eu acho que jogar golfe em código de máquina deve me dar alguma folga aqui, já que ninguém mais postou uma resposta em qualquer idioma sem precisão estendida nativa, mas acho que uma versão funcional disso ainda deve ter menos de 120 bytes sem jogar novamente todo o golfe coisa.Algoritmo:
força bruta
a+=b; swap(a,b)
, truncando conforme necessário para manter apenas os dígitos decimais iniciais> = 1017. Ele roda em 1min13s no meu computador (ou 322,47 bilhões de ciclos de clock + - 0,05%) (e pode ser um pouco mais rápido com alguns bytes extras de tamanho de código ou até 62s com tamanho de código muito maior desde o desenrolamento do loop. matemática inteligente, apenas fazendo o mesmo trabalho com menos sobrecarga). É baseado na implementação Python do @ AndersKaseorg , que é executada em 12min35s no meu computador (Skylake 4.4GHz i7-6700k). Nenhuma versão tem nenhum cache L1D em falta, portanto meu DDR4-2666 não importa.Diferentemente do Python, eu armazeno os números de precisão estendida em um formato que libera truncado os dígitos decimais . Eu armazeno grupos de 9 dígitos decimais por número inteiro de 32 bits, para que um deslocamento de ponteiro descarte os 9 dígitos baixos. Isso é efetivamente base de 1 bilhão, o que é uma potência de 10. (É pura coincidência que esse desafio precise do número de bilionésimo de Fibonacci, mas me poupa alguns bytes versus duas constantes separadas).
Seguindo a terminologia GMP , cada parte de 32 bits de um número de precisão estendida é chamada de "membro". A execução durante a adição deve ser gerada manualmente com uma comparação com 1e9, mas é usada normalmente como uma entrada para as
ADC
instruções usuais do próximo membro. (Também tenho que quebrar manualmente para o[0..999999999]
intervalo, em vez de 2 ^ 32 ~ = 4.295e9. Faço isso sem ramificações comlea
+cmov
, usando o resultado de execução da comparação.)Quando o último membro produz uma execução diferente de zero, as duas próximas iterações do loop externo são lidas a partir de 1 membro mais alto que o normal, mas ainda gravam no mesmo local. É como fazer um
memcpy(a, a+4, 114*4)
deslocamento à direita de 1 membro, mas feito como parte dos próximos dois loops de adição. Isso acontece a cada ~ 18 iterações.Hacks para economia de tamanho e desempenho:
As coisas de sempre, como em
lea ebx, [eax-4 + 1]
vez demov ebx, 1
, quando eu sei dissoeax=4
. E usarloop
em lugares ondeLOOP
a lentidão tem apenas um pequeno impacto.Trunque por 1 membro gratuitamente, deslocando os ponteiros dos quais lemos, enquanto ainda escreve no início do buffer no
adc
loop interno. Lemos[edi+edx]
e escrevemos para[edi]
. Para que possamos obteredx=0
ou4
obter um deslocamento de leitura e gravação para o destino. Precisamos fazer isso por 2 iterações sucessivas, primeiro compensando as duas e depois apenas compensando o dst. Detectamos o segundo caso examinandoesp&4
antes de redefinir os ponteiros para a frente dos buffers (usando&= -1024
porque os buffers estão alinhados). Veja os comentários no código.O ambiente de inicialização do processo Linux (para um executável estático) zera a maioria dos registros e a memória da pilha abaixo
esp
/rsp
é zerada. Meu programa tira proveito disso. Em uma versão com função de chamada disso (onde a pilha não alocada pode estar suja), eu poderia usar o BSS para memória zerada (ao custo de talvez mais 4 bytes para configurar os ponteiros). A zeragemedx
levaria 2 bytes. A ABI do System V x86-64 não garante nenhum deles, mas a implementação do Linux é zero (para evitar vazamentos de informações do kernel). Em um processo vinculado dinamicamente,/lib/ld.so
é executado antes_start
e deixa os registros diferentes de zero (e provavelmente lixo na memória abaixo do ponteiro da pilha).Eu continuo
-1024
emebx
para uso fora de loops. Usebl
como um contador para loops internos, terminando em zero (que é o byte baixo de-1024
, restaurando assim a constante para uso fora do loop). A Intel Haswell e mais tarde não têm multas parciais de mesclagem de registros para registros low8 (e, na verdade, nem os renomeiam separadamente) ; portanto, há uma dependência no registro completo, como na AMD (não é um problema aqui). Isso seria horrível para Nehalem e versões anteriores, porém, que têm paradas parciais de registro durante a fusão. Em outros lugares, escrevo regs parciais e leio o reg completo semxor
-zero ou ummovzx
, geralmente porque sei que algum código anterior zerou os bytes superiores e, novamente, isso é bom na família AMD e Intel SnB, mas lento na Intel pré-Sandybridge.Como uso
1024
o número de bytes para gravar em stdout (sub edx, ebx
), meu programa imprime alguns bytes de lixo após os dígitos de Fibonacci, porquemov edx, 1000
custa mais bytes.(não utilizado)
adc ebx,ebx
com EBX = 0 para obter EBX = CF, economizando um byte vssetc bl
.dec
/jnz
dentro de umadc
loop preserva o CF sem causar uma parada parcial do sinalizador quandoadc
lê sinalizadores no Intel Sandybridge e posterior. É ruim em CPUs anteriores , mas o AFAIK é gratuito no Skylake. Ou, na pior das hipóteses, um golpe extra.Use a memória abaixo
esp
como uma zona vermelha gigante . Como esse é um programa completo do Linux, sei que não instalei nenhum manipulador de sinal e que nada mais assobiará de forma assíncrona a memória da pilha de espaço do usuário. Pode não ser o caso em outros sistemas operacionais.Tire proveito do mecanismo de pilha para economizar largura de banda de problemas de uop usando
pop eax
(1 uop + ocasional de sincronização de pilha) em vez delodsd
(2 uops em Haswell / Skylake, 3 em IvB e versões anteriores de acordo com as tabelas de instruções de Agner Fog )). IIRC, isso diminuiu o tempo de execução de 83 segundos para 73. Provavelmente, eu poderia obter a mesma velocidade usando ummov
com um modo de endereçamento indexado, comomov eax, [edi+ebp]
ondeebp
mantém o deslocamento entre os buffers src e dst. (Isso tornaria o código fora do loop interno mais complexo, tendo que negar o registro de deslocamento como parte da troca de src e dst pelas iterações de Fibonacci.) Consulte a seção "desempenho" abaixo para obter mais informações.inicie a sequência fornecendo à primeira iteração uma carga (um byte
stc
), em vez de armazenar uma1
na memória em qualquer lugar. Muitas outras coisas específicas do problema estão documentadas nos comentários.Listagem NASM (código de máquina + fonte) , gerada com
nasm -felf32 fibonacci-1G.asm -l /dev/stdout | cut -b -28,$((28+12))- | sed 's/^/ /'
. (Em seguida, removi manualmente alguns blocos de material comentado, para que a numeração das linhas tenha lacunas.) Para remover as colunas principais para que você possa alimentá-lo no YASM ou NASM, usecut -b 27- <fibonacci-1G.lst > fibonacci-1G.asm
.Provavelmente há espaço para jogar mais alguns bytes com isso, mas eu já passei pelo menos 12 horas nisso em 2 dias. Eu não quero sacrificar a velocidade, mesmo que seja muito mais do que rápido o suficiente e haja espaço para diminuí-la de maneiras que custam velocidade . Parte do meu motivo para publicar está mostrando a rapidez com que posso criar uma versão asm de força bruta. Se alguém quiser realmente optar pelo tamanho mínimo, mas talvez 10 vezes mais lento (por exemplo, 1 dígito por byte), fique à vontade para copiar isso como ponto de partida.
O executável resultante (de
yasm -felf32 -Worphan-labels -gdwarf2 fibonacci-1G.asm && ld -melf_i386 -o fibonacci-1G fibonacci-1G.o
) é 340B (despojado):atuação
O
adc
loop interno é de 10 uops de domínio fundido no Skylake (+1 de sincronização de pilha a cada ~ 128 bytes), para que ele possa emitir um por ~ 2,5 ciclos no Skylake com taxa de transferência de front-end ideal (ignorando os uops de sincronização de pilha) . A latência-caminho crítico é de 2 ciclos, por oadc
->cmp
-> de iteração seguinteadc
cadeia de dependência realizadas em malha, de modo que o gargalo deve ser o limite de emissão de front-end ~ 2,5 ciclos por iteração.adc eax, [edi + edx]
são 2 uops de domínio não utilizado para as portas de execução: load + ALU. Ele se funde nos decodificadores (1 uop de domínio fundido), mas não é laminado no estágio de edição para 2 uops de domínio fundido, devido ao modo de endereçamento indexado, mesmo em Haswell / Skylake . Eu pensei que ele ficaria micro-fundido, comoadd eax, [edi + edx]
acontece, mas talvez manter os modos de endereçamento indexado micro-fundidos não funcione para uops que já possuem 3 entradas (sinalizadores, memória e destino). Quando escrevi, estava pensando que não teria uma desvantagem no desempenho, mas estava errado. Essa maneira de lidar com o truncamento diminui o loop interno todas as vezes, sejaedx
0 ou 4.Seria mais rápido lidar com o deslocamento de leitura e gravação para o dst, deslocando
edi
e usandoedx
para ajustar o armazenamento. Entãoadc eax, [edi]
/ ... /mov [edi+edx], eax
/ emlea edi, [edi+4]
vez destosd
. Haswell e mais tarde podem manter uma loja indexada micro-fundida. (Sandybridge / IvB também a lamina.)No Intel Haswell e anteriores,
adc
ecmovc
são 2 UOPs cada, com latência 2c . (adc eax, [edi+edx]
ainda não é laminado em Haswell e é emitido como três uops de domínio fundido). Broadwell e mais tarde permitem uops de 3 entradas para mais do que apenas FMA (Haswell), fazendoadc
ecmovc
(e algumas outras coisas) instruções de uop, como se estivessem na AMD há muito tempo. (Essa é uma das razões pelas quais a AMD se sai bem nos benchmarks GMP de precisão estendida há muito tempo.) De qualquer forma, o loop interno de Haswell deve ser de 12 uops (ocasionalmente, com +1 de sincronização de pilha), com um gargalo de front-end de ~ 3 c por melhor exemplo, ignorando os uops de sincronização de pilha.Usar
pop
sem balancearpush
dentro de um loop significa que o loop não pode ser executado a partir do LSD (detector de fluxo de loop) e deve ser relido do cache uop para o IDQ todas as vezes. Se alguma coisa, é uma coisa boa no Skylake, já que um loop de 9 ou 10 uop não emite de maneira ideal a 4 uops a cada ciclo . Isso provavelmente é parte do motivo pelo qual a substituiçãolodsd
porpop
ajudou tanto. (O LSD não pode bloquear os uops porque isso não deixaria espaço para inserir um uop de sincronização de pilha .) (BTW, uma atualização de microcódigo desativa o LSD inteiramente no Skylake e Skylake-X para corrigir uma errata. acima antes de obter essa atualização.)Eu criei um perfil no Haswell e descobri que ele roda 381,31 bilhões de ciclos de clock (independentemente da frequência da CPU, pois ele usa apenas o cache L1D, não a memória). A taxa de transferência do problema de front-end foi de 3,72 ups de domínio fundido por relógio, contra 3,70 no Skylake. (Mas é claro instruções por ciclo foi reduzido para 2,42 de 2,87, porque
adc
ecmov
são 2 UOPs sobre Haswell.)push
substituirstosd
provavelmente não ajudaria tanto, porqueadc [esp + edx]
provocaria uma sincronização de pilha sempre. E custaria um byte, porstd
issolodsd
vai na outra direção. (mov [edi], eax
/lea edi, [edi+4]
substituirstosd
é uma vitória, passando de 32.909Motos para iteradores de 100M para 31.954Motos para iteradores de 100M. Parece questosd
decodifica como 3 uops, com os uops de endereço da loja / dados da loja não microfundidos, entãopush
+ sincronização de pilha uops ainda pode ser mais rápido questosd
)O desempenho real de ~ 322,47 bilhões de ciclos para iterações 1G de 114 membros funciona para 2.824 ciclos por iteração do loop interno , para a versão rápida 105B no Skylake. (Veja a
ocperf.py
saída abaixo). Isso é mais lento do que eu previa na análise estática, mas estava ignorando a sobrecarga do loop externo e quaisquer uops de sincronização de pilha.Os contadores de perf
branches
ebranch-misses
mostram que o loop interno é imprevisível uma vez por loop externo (na última iteração, quando não é utilizado). Isso também é responsável por parte do tempo extra.Eu poderia salvar o tamanho do código fazendo com que o loop mais interno tivesse latência de 3 ciclos para o caminho crítico, usando
mov esi,eax
/sub eax,ebp
/cmovc eax, esi
/cmc
(2 + 2 + 3 + 1 = 8B) em vez delea esi, [eax - 1000000000]
/cmp ebp,eax
/cmovc
(6 + 2 + 3 = 11B ) Ocmov
/stosd
está fora do caminho crítico. (A edição de incrementostosd
pode ser executada separadamente do armazenamento, de modo que cada iteração cria uma cadeia de dependência curta.) Ele costumava salvar outro 1B alterando a instrução init do ebp delea ebp, [ecx-1]
paramov ebp,eax
, mas descobri que ter o erro erradoebp
não mudou o resultado. Isso permitiria que um membro fosse exatamente == 1000000000 em vez de agrupar e produzir um carry, mas esse erro se propaga mais lentamente do que o crescimento de Fib (), portanto, isso não altera os dígitos de 1k iniciais do resultado final. Além disso, acho que esse erro pode se corrigir quando estamos apenas adicionando, já que há espaço em um membro para mantê-lo sem excesso. Mesmo 1G + 1G não transborda um número inteiro de 32 bits; portanto, ele eventualmente percorre para cima ou é truncado.A versão de latência 3c é 1 uop extra, portanto, o front-end pode emiti-lo em um por 2,75 c ciclos no Skylake, apenas um pouco mais rápido que o back-end pode executá-lo. (No Haswell, serão 13 uops no total, pois ainda usa
adc
andcmov
, e gargalo no front-end a 3,25 c por iter).Na prática, ele executa um fator 1,18 mais lento no Skylake (3,34 ciclos por membro), em vez de 3 / 2,5 = 1,2 que eu previ para substituir o gargalo do front-end pelo gargalo de latência, apenas olhando para o loop interno sem a sincronização de pilha uops. Como os uops de sincronização de pilha prejudicam apenas a versão rápida (gargalo no front-end em vez de latência), não é preciso muito para explicar. por exemplo, 3 / 2,54 = 1,18.
Outro fator é que a versão de latência 3c pode detectar a imprevisibilidade ao deixar o loop interno enquanto o caminho crítico ainda está em execução (porque o front-end pode ficar à frente do back-end, permitindo que a execução fora de ordem execute o loop- contra-ataques), portanto, a penalidade efetiva de imprevisibilidade é menor. Perder esses ciclos de front-end permite que o back-end o atualize.
Se não fosse por isso, talvez pudéssemos acelerar a
cmc
versão 3c usando uma ramificação no loop externo em vez de manipular sem ramificações as compensações carry_out -> edx e esp. Previsão de ramificação + execução especulativa para uma dependência de controle em vez de uma dependência de dados pode permitir que a próxima iteração comece a executar oadc
loop enquanto os uops do loop interno anterior ainda estavam em andamento. Na versão sem ramificação, os endereços de carregamento no loop interno têm uma dependência de dados do CF doadc
último membro da última.A versão de loop interno de latência 2c afunila no front-end, portanto o back-end praticamente se mantém. Se o código do loop externo tiver alta latência, o front-end poderá avançar emitindo uops a partir da próxima iteração do loop interno. (Mas, neste caso, o material do loop externo possui bastante ILP e não possui alta latência, portanto o back-end não tem muito o que fazer quando começa a mastigar os uops no planejador fora de ordem, como suas entradas ficam prontas).
( +- x %)
é o desvio padrão nas 4 execuções para essa contagem. Interessante que ele execute um número tão redondo de instruções. Esses 924 bilhões não são uma coincidência. Eu acho que o loop externo executa um total de 924 instruções.uops_issued
é uma contagem de domínio fundido (relevante para a largura de banda do problema de front-end), enquantouops_executed
é uma contagem de domínio não fundido (número de uops enviados para portas de execução). A microfusão une 2 uops de domínio não fundido em um uop de domínio fundido, mas a eliminação de movimentos significa que alguns uops de domínio fundido não precisam de nenhuma porta de execução. Consulte a pergunta vinculada para obter mais informações sobre a contagem de uops e domínio fundido versus domínio não fundido. (Consulte também as tabelas de instruções e o guia do uarch do Agner Fog e outros links úteis no wiki de tags do SO x86 ).De outra execução, medindo coisas diferentes: as falhas no cache L1D são totalmente insignificantes, conforme o esperado para a leitura / gravação dos mesmos dois buffers 456B. A ramificação do loop interno é imprevisível uma vez por loop externo (quando não é necessário sair do loop). (O tempo total é maior porque o computador não estava totalmente ocioso. Provavelmente o outro núcleo lógico estava ativo algumas vezes e passava mais tempo em interrupções (uma vez que a frequência medida pelo espaço do usuário estava abaixo de 4.400 GHz). Ou vários núcleos estavam ativos a maior parte do tempo, diminuindo o máximo de turbo. Não rastreei
cpu_clk_unhalted.one_thread_active
para ver se a competição por HT era um problema.)Meu código pode rodar em menos ciclos no Ryzen, que pode emitir 5 uops por ciclo (ou 6 quando alguns deles são instruções de 2 uop, como o AVX 256b no Ryzen). Não tenho certeza do que o seu front-end faria
stosd
, que são 3 uops na Ryzen (o mesmo que a Intel). Eu acho que as outras instruções no loop interno são a mesma latência que a Skylake e todas são únicas. (Incluindoadc eax, [edi+edx]
, o que é uma vantagem sobre a Skylake).Provavelmente isso poderia ser significativamente menor, mas talvez 9x mais lento, se eu armazenasse os números como 1 dígito decimal por byte . Gerar execução
cmp
e ajuste comcmov
funcionaria da mesma forma, mas executaria 1/9 da obra. 2 dígitos decimais por byte (base-100, não BCD de 4 bits com lentidãoDAA
) também funcionariam ediv r8
/add ax, 0x3030
transforma um byte de 0-99 em dois dígitos ASCII na ordem de impressão. Mas um dígito por byte não precisadiv
, basta repetir e adicionar 0x30. Se eu armazenar os bytes na ordem de impressão, isso tornaria o segundo loop realmente simples.Usar 18 ou 19 dígitos decimais por número inteiro de 64 bits (no modo de 64 bits) faria com que fosse executado duas vezes mais rápido, mas custaria um tamanho de código significativo para todos os prefixos REX e constantes de 64 bits. Membros de 32 bits no modo de 64 bits evitam o uso em
pop eax
vez delodsd
. Ainda pude evitar os prefixos REX usandoesp
como um registro de rascunho que não é um ponteiro (trocando o uso deesi
eesp
), em vez de usarr8d
como um oitavo registro.Se estiver criando uma versão de função que pode chamar, converter para 64 bits e usá-lo
r8d
pode ser mais barato do que salvar / restaurarrsp
. 64 bits também não podem usar adec r32
codificação de um byte (já que é um prefixo REX). Mas na maioria das vezes acabei usandodec bl
2 bytes. (Porque eu tenho uma constante nos bytes superiores deebx
e a uso apenas fora dos loops internos, o que funciona porque o byte baixo da constante é0x00
.)Versão de alto desempenho
Para obter o desempenho máximo (não código-golfe), você deseja desenrolar o loop interno para que ele execute no máximo 22 iterações, que é um padrão de tomada / não-tomada suficientemente curto para que os preditores de ramificações funcionem bem. Nas minhas experiências,
mov cl, 22
antes de um.inner: dec cl/jnz .inner
loop ter muito poucos erros de previsão (como 0,05%, muito menos de um por execução completa do loop interno), masmov cl,23
erros de previsão de 0,35 a 0,6 vezes por loop interno.46
é particularmente ruim, imprevisível ~ 1,28 vezes por loop interno (128M vezes para iterações de loop externo 100M).114
imprevisível exatamente uma vez por loop interno, o mesmo que encontrei como parte do loop de Fibonacci.Fiquei curioso e tentei, desenrolando o loop interno por 6 com um
%rep 6
(porque isso divide 114 uniformemente). Isso eliminou principalmente erros de ramificação. Fizedx
negativo e usei-o como compensação para asmov
lojas, para queadc eax,[edi]
pudesse ficar micro-fundido. (E assim eu poderia evitarstosd
). Puxei olea
para atualizaredi
fora do%rep
bloco, então ele faz apenas uma atualização de ponteiro por 6 lojas.Também me livrei de todas as coisas de registro parcial no loop externo, embora não ache isso significativo. Pode ter ajudado um pouco a CF no final do loop externo, não dependente do ADC final; portanto, alguns dos uops do loop interno podem começar. O código do loop externo provavelmente poderia ser otimizado um pouco mais, já que
neg edx
foi a última coisa que fiz, depois de substituirxchg
por apenas 2mov
instruções (já que eu ainda tinha 1) e reorganizar as cadeias de dep e eliminar os 8 bits registrar coisas.Esta é a fonte NASM apenas do loop Fibonacci. É um substituto para essa seção da versão original.
Atuação:
Isso é para o mesmo Fib (1G), produzindo a mesma saída em 62,3 segundos em vez de 73 segundos. (Ciclos 273.146G, vs. 322.467G. Como tudo ocorre no cache L1, os ciclos de clock do núcleo são realmente tudo o que precisamos examinar.)
Observe a
uops_issued
contagem total muito mais baixa , bem abaixo dauops_executed
contagem. Isso significa que muitos deles foram micro-fundidos: 1 uop no domínio fundido (problema / ROB), mas 2 uops no domínio não fundido (unidades de agendador / execução)). E que poucos foram eliminados no estágio de emissão / renomeação (comomov
cópia de registro ouxor
zeros, que precisam ser emitidos, mas não precisam de uma unidade de execução). Uops eliminados desequilibrariam a contagem para o outro lado.branch-misses
caiu para ~ 400k, de 1G, então desenrolar funcionou.resource_stalls.any
é significativo agora, o que significa que o front-end não é mais o gargalo: em vez disso, o back-end está ficando para trás e limitando o front-end.idq_uops_not_delivered.core
conta apenas ciclos em que o front-end não deu uops, mas o back-end não foi interrompido. Isso é bom e baixo, indicando alguns gargalos no front-end.Curiosidade: a versão python gasta mais da metade do tempo dividindo por 10 em vez de adicionar. (Substituir o
a/=10
coma>>=64
acelera mais de um fator de 2, mas altera o resultado porque truncamento binário! = Truncamento decimal.)É claro que minha versão asm é otimizada especificamente para esse tamanho de problema, com a iteração de loop - contagens codificadas. Mesmo mudar um número de precisão arbitrária o copiará, mas minha versão pode apenas ler de um deslocamento para as próximas duas iterações para pular até isso.
Eu criei um perfil da versão python (python2.7 de 64 bits no Arch Linux):
Os números em (parens) são quanto tempo esse contador de amostras estava sendo amostrado. Ao olhar para mais contadores do que o HW suporta, o perf gira entre diferentes contadores e extrapola. Isso é ótimo para uma longa execução da mesma tarefa.
Se eu corresse
perf
depois de definir o sysctlkernel.perf_event_paranoid = 0
(ou executandoperf
como root), ele seria medido4.400GHz
.cycles:u
não conta o tempo gasto em interrupções (ou chamadas do sistema), apenas ciclos de espaço do usuário. Minha área de trabalho estava quase totalmente ociosa, mas isso é típico.fonte
Haskell,
8361 bytesSaídas ( F 1000000000 , F 1000000001 ). No meu laptop, ele imprime corretamente o ponto esquerdo e os primeiros 1000 dígitos em 133 segundos, usando 1,35 GiB de memória.
Como funciona
A recorrência de Fibonacci pode ser resolvida usando exponenciação de matriz:
[ F i - 1 , F I ; F i , F i + 1 ] = [0, 1; 1, 1] i ,
a partir do qual derivamos essas identidades:
[ M i + j - 1 , M i + j ; M i + j , M i + j + 1 ] = [ F i - 1 , F I ; F i , F i + 1 ] ⋅ [ F j - 1 , F j ; F j , F j + 1 ],
F i + j = F i+ 1 F j + 1 - F i - 1 F j - 1 = F i + 1 F j + 1 - ( F i + 1 - F i ) ( F j + 1 - F j ),
F i + j + 1 = F i F j + F i + 1 F j + 1 .
A
p
função calcula ( F i + j , F i + j + 1 ) dados ( F i , F i + 1 ) e ( F j , F j + 1 ). Escrevendof n
para ( F i , F i + 1 ), temosp (f i) (f j)
=f (i + j)
.Então,
(t=<<t.p) (f i)
=
t ((t.p) (f i)) (f i)
=
t (p (f i).p (f i).p (f i)) (f i)
=
(p (f i).p (f i).p (f i).p (f i).p (f i).p (f i).p (f i).p (f i).p (f i)) (f i)
=
f (10 * i)
,(t$t=<<t.p) (f i)
=
((t=<<t.p).(t=<<t.p).(t=<<t.p)) (f i)
=
f (10^3 * i)
,t(t$t=<<t.p) (f i)
=
((t$t=<<t.p).(t$t=<<t.p).(t$t=<<t.p)) (f i)
=
f (10^9 * i)
,e nós conectamos
f 1
=(1,1)
.fonte
Mathematica, 15
34bytesFibonacci
em si leva ~ 6s no meu computador. E 95 (+/- 5) s para o frontend exibi-lo.Os primeiros 1000 dígitos (34 bytes):
⌊Fibonacci@1*^9/1*^208986640⌋&
Mais longo, mas mais rápido
ToString@Fibonacci@1*^9~StringTake~1000&
:fonte
div
). Parei, já que as pessoas provavelmente terminariam de olhar para essa pergunta quando eu tivesse uma função bem treinada que fizesse todo esse trabalho. Mas aparentemente a força bruta pode funcionar, como mostram algumas respostas.Python 2, 70 bytes
Isso funcionou em 18 minutos e 31 segundos no meu laptop, produzindo os 1000 dígitos corretos seguidos por
74100118580
(os seguintes dígitos corretos são74248787892
).fonte
div
loop para fazer 9 dígitos decimais por bloco. Leve durante as adições com cmp / cmov e 2xADD em vez de ADC.Haskell , 78 bytes
Experimente online!
Levou 48 segundos no TIO. A mesma fórmula recursiva da minha resposta em Python , mas sem truncar.
A constante
2143923439
é10**9-1
, invertida em binário e com 1 extra no final. Iterar através de seus dígitos binários no sentido inverso simula a iteração através dos dígitos binários de10**9-1
. Parece mais curto codificar isso do que calculá-lo.fonte
Haskell ,
202184174173170168164162 bytesExperimente online!
Explicação
Isso usa uma maneira bastante rápida de calcular os números de fibonacci. A função
l
leva dois números de Fibonacci e calcula os números de Fibonacci 10 posterior, enquanto quef
leva a n th e n + 1 th números de Fibonacci e calcula a 2n + 20 ° e 2n + 21 números th Fibonacci. Eu os encadeio aleatoriamente para obter 1 bilhão e pegar os primeiros 1000 dígitos.fonte
Haskell, 81 bytes
Explicação
f n
calcula recursivamente on
número de fibonacci usando a recorrência da resposta do xnor com a eliminação da subexpressão comum. Diferentemente das outras soluções postadas, que usam multiplicações de O (log (n)), temos uma recursão de profundidade de O (log (n)) com um fator de ramificação de 2, para uma complexidade de multiplicações de O (n).No entanto, nem tudo está perdido! Como quase todas as chamadas estarão próximas à parte inferior da árvore de recursão, podemos usar aritmética nativa rápida sempre que possível e evitar muita manipulação de bignums enormes. Ele cospe uma resposta em alguns minutos na minha caixa.
fonte
T-SQL,
422 414453 bytes (Verificado, agora competindo!)EDIT 2 : Alterado para , ganhou alguns bytes, mas aumentou a velocidade suficiente para completar para 1 bilhão! Concluído em 45 horas e 29 minutos , verifica a sequência especificada e exibe 8 caracteres adicionais (que podem ou não estar corretos devido a erros de arredondamento).
INT BIGINT
DECIMAL(37,0)
O T-SQL não tem suporte nativo para "grande número", então tive que rolar meu próprio somador de grande número baseado em texto usando cadeias de 1008 caracteres:
Aqui está a versão formatada com comentários:
Basicamente, estou manipulando manualmente seqüências de 1008 caracteres preenchidas com zero, representando minhas duas variáveis Fibonacci,
@a
e@
.Eu os adiciono
8 1836 dígitos por vez, removendo os últimos 36 dígitos, convertendo para um tipo numérico gerenciável (DECIMAL(37,0)
), somando-os e depois esmagando-o novamente em outra sequência longa@c
. Eu então "giro"@a
e@
movendo os últimos 36 dígitos para a frente e repetindo o processo. 28 rotações * 36 dígitos cobrem todos os 1008. Eu tenho que "carregar esse" manualmente.Quando nosso número começa a exceder o comprimento da minha string, eu "desligo para a esquerda" e começamos a perder alguma precisão, mas o erro está dentro dos meus caracteres extras.
Tentei usar uma tabela SQL cheia de INTs e BIGINTs, com lógica semelhante, e foi muito mais lenta. Esquisito.
fonte
PARI / GP, 45 bytes
De alguma forma,
\p1000
não é suficiente. Isso não funciona com sistemas de 32 bits. A divisão final é evitar o ponto decimal na notação científica.fonte
Pari / GP , 15 + 5 = 20 bytes
Execute com a opção de linha de comando
-s1g
para alocar 1 Gbytes de memória.fonte
Ruby, 63 bytes
cara, sou ruim em jogar rubi; mas a classe BigInt faz maravilhas para esse tipo de coisa. Usamos o mesmo algoritmo que Anders Kaseorg.
fonte