Chaves de ordenação Unix causam problemas de desempenho

3

Meus dados:

  • É um arquivo de 71 MB com 1,5 milhões de linhas.
  • Tem 6 campos
  • Todos os seis campos se combinam para formar uma chave exclusiva - e é isso que eu preciso classificar.

Instrução de classificação:

sort -t ',' -k1,1 -k2,2 -k3,3 -k4,4 -k5,5 -k6,6 -o output.csv input.csv

O problema:

  • Se eu ordenar sem chaves, demora 30 segundos.
  • Se eu ordenar com chaves, demora 660 segundos.
  • Preciso classificar com chaves para manter isso genérico e útil para outros arquivos que também têm campos sem chave. O tempo de 30 segundos é bom, mas o 660 é um assassino.

Mais detalhes usando o tempo unix:

  • classifique input.csv -o output.csv = 28 segundos
  • classifique -t ',' -k1 input.csv -o output.csv = 28 segundos
  • classifique -t ',' -k1,1 input.csv -o output.csv = 64 segundos
  • classifique -t ',' -k1,1 -k2,2 input.csv -o output.csv = 194 segundos
  • classifique -t ',' -k1,1 -k2,2 -k3,3 input.csv -o output.csv = 328 segundos
  • classifique -t ',' -k1,1 -k2,2 -k3,3 -k4,4 input.csv -o output.csv = 483 segundos
  • classifique -t ',' -k1,1 -k2,2 -k3,3 -k4,4 -k5,5 input.csv -o output.csv = 561 segundos
  • classifique -t ',' -k1,1 -k2,2 -k3,3 -k4,4 -k5,5 -k6,6 input.csv -o output.csv = 660 segundos

Eu poderia, teoricamente, mover o diretório temporário para SSD e / ou dividir o arquivo em 4 partes, classificá-las separadamente (em paralelo) e depois mesclar os resultados, etc. Mas espero algo mais simples, pois parece que o tipo é apenas escolhendo um algoritmo ruim.

Alguma sugestão?

Testando melhorias usando o tamanho do buffer:

  • Com 2 teclas, obtive uma melhoria de 5% com 8, 20, 24 MB e melhor desempenho de 8% de melhoria com 16 MB, mas 6% pior com 128 MB
  • Com 6 teclas, obtive uma melhoria de 5% com 8, 20, 24 MB e melhor desempenho de 9% de melhoria com 16 MB.

Teste de melhorias usando a ordem do dicionário (apenas 1 execute cada):

  • classifique -d --buffer-size = 8M -t ',' -k1,1 -k2,2 input.csv -o output.csv = 235 segundos (21% pior)
  • classifique -d --buffer-size = 8M -t ',' -k1,1 -k2,2 input.csv -o ouput.csv = 232 segundos (21% pior)
  • conclusão: faz sentido que isso atrase o processo, não seja útil

Teste com sistema de arquivos diferente no SSD - não consigo fazer isso neste servidor agora.

Teste com código para consolidar chaves adjacentes:

def consolidate_keys(key_fields, key_types):
""" Inputs:
         - key_fields - a list of numbers in quotes: ['1','2','3']
         - key_types - a list of types of the key_fields: ['integer','string','integer']
    Outputs:
         - key_fields - a consolidated list:  ['1,2','3']
         - key_types - a list of types of the consolidated list: ['string','integer']
"""
assert(len(key_fields) == len(key_types))

def get_min(val):
    vals = val.split(',')
    assert(len(vals) <= 2)
    return vals[0]

def get_max(val):
    vals = val.split(',')
    assert(len(vals) <= 2)
    return vals[len(vals)-1]

i = 0
while True:
    try:
        if ( (int(get_max(key_fields[i])) + 1) == int(key_fields[i+1])
        and  key_types[i] == key_types[i+1]):
                key_fields[i] = '%s,%s' % (get_min(key_fields[i]), key_fields[i+1])
                key_types[i]  = key_types[i]
                key_fields.pop(i+1)
                key_types.pop(i+1)
                continue
        i = i+1
    except IndexError:
        break  # last entry

return key_fields, key_types

Embora esse código seja apenas uma solução alternativa, ele só se aplicará a casos em que eu tenha um conjunto contíguo de chaves. Isso acelera o código em 95% no pior cenário possível.

    
por KenFar 14.06.2012 / 21:49

3 respostas

1

Eu não tenho ideia de como sort funciona internamente e não há 71 MB .csv de arquivo disponível para testá-lo, mas aqui estão algumas coisas que você pode tentar:

  • Defina --buffer-size ( -S ) como algo grande o suficiente para evitar a leitura do disco rígido mais de uma vez.

    Comece com -S=1G e trabalhe até o fim.

  • Elimine as chaves, uma a uma, para ver se há uma específica que causa problemas (por exemplo, os inteiros).

    Exemplos:

    • -k1,1 -k2,2 -k3,3 -k4,4 -k5,5

    • -k1,1 -k2,2 -k3,3 -k4,4 -k6,6

  • A menos que isso seja inaceitável para os inteiros, defina a opção --dictionary-order ( -d ).

por 14.06.2012 / 22:20
1

A especificação de várias chaves requer que os dados sejam classificados primeiro pela primeira chave, depois os itens com as primeiras chaves iguais são classificados pela segunda chave, etc. Isso é um monte de dados circulando na RAM. Se algum deles for paginado, o algoritmo passa de ser limitado a meus tempos de acesso à memória (medidos em nanossegundos) a ser limitado pelos tempos de acesso ao disco (medidos em milissegundos).

    
por 15.06.2012 / 17:04
1

Eu enfrentei precisamente esse problema, e depois de dar uma rápida olhada no código-fonte sort.c, notei que a parte que procura por uma string por chaves se as chaves não estão consecutivamente no começo da string, é uma pesquisa de string simples (até o delimitador). E considerando que a classificação é uma operação n (log n), esse tipo de busca por chaves dentro de uma linha pode ser repetido várias vezes ao comparar duas linhas, cada vez que uma linha é comparada a outra.

Então, eu usei uma combinação de awk (prefixar chaves consecutivamente), classificar (nos primeiros x campos) e cortar (cortar as teclas prefixadas) para preceder chaves de ordenação consecutivamente e removê-las depois que o trabalho é concluído. Obtive uma melhoria de 3x para o meu caso de uso.

    
por 25.02.2015 / 09:32

Tags