O desafio é filtrar um arquivo grande rapidamente.
- Entrada: Cada linha possui três números inteiros positivos separados por espaço.
Saída: Todas as linhas de entrada
A
B
,T
que satisfazem tanto do critério seguinte.- Existe uma outra linha de entrada
C
,D
,U
ondeD = A
e0 <= T - U < 100
. - Existe uma outra linha de entrada
C
,D
,U
ondeB = C
e0 <= U - T < 100
.
- Existe uma outra linha de entrada
Para criar um arquivo de teste, use o seguinte script python, que também será usado para o teste. Ele criará um arquivo 1.3G. Obviamente, você pode reduzir nolines para testes.
import random
nolines = 50000000 # 50 million
for i in xrange(nolines):
print random.randint(0,nolines-1), random.randint(0,nolines-1), random.randint(0,nolines-1)
Regras. O código mais rápido quando testado em um arquivo de entrada que cria usando o script acima no meu computador vence. O prazo é de uma semana a partir da primeira entrada correta.
Minha máquina Os horários serão executados na minha máquina. Esta é uma instalação padrão do ubuntu de 8GB de RAM em um processador AMD FX-8350 Eight-Core. Isso também significa que eu preciso poder executar seu código.
Algumas informações relevantes de tempo
Horários atualizados para executar o seguinte antes de cada teste.
sync && sudo bash -c 'echo 3 > /proc/sys/vm/drop_caches'
time wc test.file
real 0m26.835s
user 0m18.363s
sys 0m0.495s
time sort -n largefile.file > /dev/null
real 1m32.344s
user 2m9.530s
sys 0m6.543s
Status das entradas
Eu corro a seguinte linha antes de cada teste.
sync && sudo bash -c 'echo 3 > /proc/sys/vm/drop_caches'
- Perl (aguardando correção de bug.)
- Scala 1 minutos 37 segundos por @James_pic. (Usando o scala -J-Xmx6g Filterer largefile.file output.txt)
- Java . 1 minuto e 23 segundos por @Geobits. (Usando java -Xmx6g Filter_26643)
- C . 2 minutos e 21 segundos por @ScottLeadley.
- C . 28 segundos por @James_pic.
- Python + pandas . Talvez exista uma solução simples "agrupada"?
- C . 28 segundos por @KeithRandall.
Os vencedores são Keith Randall e James_pic.
Eu não sabia diferenciar o tempo de execução e ambos são quase tão rápidos quanto o wc!
1 < n < 2147483647
?Respostas:
C, ~
74,1 segundosRadix ordena em T, depois percorre a matriz procurando por correspondências.
É rápido porque é compatível com cache. A raiz classifica razoavelmente, e a caminhada final muito. Eu tenho que verificar cada linha contra cerca de 100 outras, mas todas elas são adjacentes no cache.
Adicionado: não preciso mais verificar cada linha em uma verificação de 100 outras linhas. Uma pequena tabela de contagens de bits de ordem baixa de b's na janela é suficiente para eliminar a maioria das instâncias dessa verificação.
Agora, cerca de metade da análise de tempo, classificação de 1/3, 1/6 de tempo fazendo a correspondência real.
fonte
filter.c
para fazer a mesma coisa, cheguei à pergunta e encontrei isso. +1Scala 2.10 - 0:41
O problema é basicamente:
A maioria dos RDBMSs notaria que a junção de
x.a
paray.b
tem a mais alta especificidade e planejaria isso como uma junção de hash.Então é isso que vamos fazer. Criamos uma hashtable dos dados
a
, juntamos hash à mesma tabelab
e filtramos a diferença det
.Ajuntar com:
E corra com:
Na minha máquina, isso é executado em 2 minutos 27.
Ainda assim, pode ser interessante tentar a abordagem da resposta de @ Lembik, mas em um idioma mais rápido. Corresponde a algo como uma junção de mesclagem
t
. No papel, deve ser mais lento, mas possui uma melhor localização de cache, o que pode levar adiante.Atualizar
Consegui passar uma grande parte do tempo com uma mudança surpreendentemente pequena - um melhor misturador de hash. O mapa de hash é muito sensível ao aglomerado de hash; portanto, essa alteração reduz para 1:45 na minha máquina.
A maior parte desse tempo é gasta lendo os dados em uma matriz.
Estou curioso para saber por que meu código de leitura de dados é muito mais lento que o @Geobits. Leva meu código 70 segundos para ler os dados - mais do que o programa inteiro @Geobits, depois que o
Thread.start
bug é corrigido. Estou tentado a roubar a abordagem do @Geobits para ler dados, mas não tenho certeza de como os deuses do Stack Exchange se sentiriam sobre isso.Atualização 2
Fiz melhorias adicionais, desta vez para o leitor de dados. O uso de correspondência de padrões e operações de mônada dentro do loop estava prejudicando o desempenho, então eu o simplifiquei. Eu acho que
scala.io.Source
é o próximo gargalo a enfrentar.Agora é 1:26 na minha máquina.
Atualização 3
Got livrar
probe
do OpenHashMultiMap. O código agora é mais java-ish e é executado em 1:15.Atualização 4
Agora estou usando um FSM para analisar a entrada. O tempo de execução caiu para 0:41
fonte
StringTokenizer
, mas quando uso , analiso milhões de strings.String.split
atualmente é um gargalo, masStringTokenizer
não está muito melhor no momento - alocar um loop interno apertado está prejudicando meu GC, que já está sobrecarregado. Eu estou trabalhando em um FSM que parece ter promessa (embora seja um exagero completo)Java: 1m54s
(No meu i7)
Como cada partida terá menos de 100
t
do seu companheiro, decidi agrupar as entradas port
. Há um balde para cada 100, portanto, para verificar um número, ele só precisa verificar com +/- 1 baldes.Em média, cada depósito contém apenas 100 entradas, portanto, não demora muito para digitalizar alguns depósitos para cada um. Mais da metade do tempo é gasto lendo e compactando, a correspondência leva apenas 40 segundos ou mais.
Nota: Dependendo da configuração da JVM, pode ser necessário aumentar o tamanho do heap. Isso também assume o nome do arquivo
test.file
. Basta alterá-lo na linha 24, se não for esse o caso.fonte
Thread::run
, nãoThread.start
, então está tudo rodando nomain
encadeamento. ComThread::start
, o tempo de execução cai de 1:38 para 0:46 na minha máquina.sort
tempo. Aumentei o monte até 6G, o mesmo que o meu (você disse que tinha 8G, então parecia um palpite sensato).C - 12 segundos
Decidi portar minha resposta do Scala para C, para ver quanto mais desempenho eu poderia obter.
É mais ou menos a mesma abordagem (criar uma tabela de hash aberta
a
), exceto que pulo a etapa em que construo a matriz inicial e itero diretamente da tabela de hash (por algum motivo, nunca consegui que essa abordagem fosse executada no Scala - Suspeito que a culpa da JVM foi a inlining).Eu não me incomodei com tópicos, pois é uma dor de se portar.
O código é:
Ajuntar com:
E corra com:
A localização do arquivo de teste é codificada como "test.file".
Mais uma vez, a leitura dos dados ocupa a maior parte do tempo (pouco menos de 9 segundos). A correspondência leva o resto do tempo.
Mais uma vez, será interessante ver como isso se compara à resposta de Scott Leadley, que usa o mesmo idioma, mas com uma estratégia diferente. Scott está ingressando no T, o que, em princípio, significa que ele terá mais com o que se juntar, mas, novamente, ingressar no T fornece uma melhor localidade do cache.
fonte
diff <(sort -n James_pic-c.out) <(sort -n James_pic-scala.out)
a
valor ocorren
tempos onden >= BUFFER_SIZE + 2
perl, 17m46s em um core i7 com 8GB de memória
Primeiro, usamos sort -n -k3 para obter o campo mais importante em ordem, aproveitando o paralelismo interno nas versões modernas do sort (1). Então, como o perl é muito prejudicado pelo fato de um escalar simples assumir a ordem de 80 bytes cada (50 milhões * 3 * 80 é demais - pelo menos 12 GB), reduzimos a saída para 50 milhões * 12 bytes matriz (12 bytes por linha, cada linha contém 3 números inteiros que podem ser representados como um número inteiro de 32 bits). Em seguida, disparamos 8 threads, cobrindo (aproximadamente) 1/8 dos dados cada (+ algumas sobreposições).
Saída não classificada:
Tenho certeza de que seria uma ordem de magnitude mais rápida em C, mas provavelmente não levarei tempo para fazê-lo.
fonte
A = D = 8455767
, masU = 50175
,T = 50130
e assimT - U = -45
C # - 30 segundos
Uma abordagem diferente da maioria, se eu ler direito - não uso nenhuma estrutura baseada em hash.
Tendo a não obter resultados, não tenho certeza se isso é uma anomalia estatística ou erro no meu raciocínio.Corrigida, a comparação para classificação binária era falha.fonte
x.A
isso virásortedA
ex.B
virásortedB
, enquanto na verdade ambos virãosortedB
, e issoComparer
produzirá resultados sem sentido.A
eB
, existe um algoritmo mais rápido do que a iteraçãoA
e a pesquisa binária naB
qual estáO(n log(n))
(e é efetivamente uma tabela de hash de pobre). Em vez disso, você pode mesclar as duas listas, o que éO(n)
.B
serão distribuídos uniformemente em um intervalo específico, seria trocar a pesquisa binária pela pesquisa de interpolação, o que reduz o tempo de pesquisa deO(log(n))
paraO(log(log(n))
.C
Força brutal, bruta, feio na cara do cara. Em uma renovação, eu escolheria qualquer outra linguagem compilada.
Compile com "gcc -m64 -pthreads -O". Espera entrada no stdin. Executa multithread por padrão. Use a opção "-s" para usar apenas um thread.
fonte
Finalmente tive a chance de construir um sistema físico Ubuntu 14.04 semelhante ao Lembik e fazer um post-mortem na minha solução para esse quebra-cabeça. Na minha escolha de importância:
sugadofoi não-performance:Em vez de chateá-lo com outro analisador FSM, a solução abaixo usa fgets () e uma substituição local strtol () [procure por s2i ()].
Uma implementação de referência em Ruby:
É um cachorro, ~ 50x mais lento que uma solução C, mas o perl é igualmente lento e menos conciso.
A solução C:
Compile com "gcc -O3 -std = c99 -Wall -m64".
fonte