Quanto mais um experimento de pensamento eu queria ver até onde poderíamos usar as ferramentas CLI para resolver esse tipo de problema. Para esse fim, eu queria tentar usar a ferramenta de CLI hash rápido xxHash para fazer este trabalho.
xxHash is an extremely fast non-cryptographic hash algorithm, working at speeds close to RAM limits. It is proposed in two flavors, 32 and 64 bits.
Ele está disponível em todas as linguagens de programação, mas para este experimento, vou usar o sabor do CLI, xxhsum
, especificamente o modo de 32 bits, então xxhsum -H0
.
Como você descobriu e como os outros afirmaram, chamar a ferramenta CLI da função hash, ou qualquer ferramenta, é geralmente onde esses tipos de abordagens caem. Chamar o xxhsum
aqui 5M vezes seria uma maneira insuficiente de usá-lo. Sua força está no arquivo I / O, então e se fôssemos pegar as linhas de 5M e convertê-las em arquivos de 5M?
Essa tarefa é realmente trivial no Linux, usando o comando split
:
split -l 1 afile
E quão rápido seria o hash, digamos 1M, desses arquivos, cada um com uma linha neles assim.
exemplo 1 arquivo de linha$ cat datadir/xzeyw
{"name": "John4000", "surname": "Gates", "country": "Germany", "age": "20", "height": "180"}
diretório com arquivos 1M
$ ls -l datadir | wc -l
1000002
tempo para hash eles
$ { time xxhsum -H0 * > ../nfile 2>&1 ;} |& awk '/real|user|sys/ {print $1": "$2"\t"}' | tr -d '\n'
real: 0m6.998s user: 0m5.007s sys: 0m1.569s
Sim, está correto, demorou ~ 7 segundos! Eu acho isso bastante impressionante. Usando xxhsum
dessa maneira, só incorremos no custo de executá-lo uma vez e permitimos que ele passasse por arquivos 1M.
Desvantagens para este método
Então, uma das desvantagens de fazer isso dessa maneira é, claro, o split
. Isso se torna nossas operações mais caras, como você pode imaginar. Já que estamos tendo que pegar um único arquivo com linhas X e explodi-lo no disco rígido como arquivos X com uma única linha.
Veja alguns desses dados:
./hashy.bash
make data
---------
real: 0m17.492s user: 0m12.434s sys: 0m4.788s
split data
----------
real: 2m15.180s user: 0m0.700s sys: 2m4.443s
hash data
---------
real: 0m6.487s user: 0m5.798s sys: 0m0.459s
Aqui podemos ver que nossa operação split
demorou ~ 2 minutos. NOTA: A primeira linha desta saída mostra o tempo para construir um arquivo com 1M linhas de JSON nele.
Outra desvantagem é o número de arquivos com os quais estamos lidando na linha de comando. Estou usando *
em lugares e isso vai se expandir para nomes de arquivos de 1M ou 5M, o que pode ser considerado perigoso, é. Lembre-se de que, ao aumentar o número de arquivos, você corre o risco de exceder a quantidade de espaço alocado para os argumentos da linha de comando.
Refira estes links a respeito do comprimento da linha de comando:
- O que é uma maneira canônica de encontrar o comprimento real da lista máxima de argumentos?
- É o Linux implementação da configuração do sistema "variável" ARG_MAX diferente de outras variáveis do sistema e é compatível com POSIX?
- O que define o tamanho máximo para um argumento único de comando?
Conclusão
Então, como você pode imaginar, resolver um problema como este usando arquivos de 1M ou arquivos de 5M parece quase ridículo. E eu teria que concordar. Mas ainda é uma experiência interessante, pois mostra que, se você alavancar as ferramentas CLI de maneira apropriada, poderá obter um excelente desempenho delas.
Código para hashy.bash
Se alguém estiver interessado no código:
$ cat hashy.bash
#!/bin/bash
echo ""
echo "make data"
echo "---------"
rm -f afile
{ time for i in {0..1000000};do echo "{\"name\": \"John${i}\", \"surname\": \"Gates\", \"country\": \"Germany\", \"age\": \"20\", \"height\": \"180\"}">> afile ;done ;} \
|& awk '/real|user|sys/ {print $1": "$2"\t"}' | tr -d '\n'
echo ""
echo ""
rm -fr datadir && mkdir datadir && cd datadir
echo "split data"
echo "----------"
{ time split -l 1 ../afile ;} |& awk '/real|user|sys/ {print $1": "$2"\t"}' | tr -d '\n'
echo ""
echo ""
echo "hash data"
echo "---------"
{ time xxhsum -H0 * > ../nfile 2>&1 ;} |& awk '/real|user|sys/ {print $1": "$2"\t"}' | tr -d '\n'
cd - > /dev/null 2>&1
echo ""
echo ""