bashスクリプトを使用して、次の有効で単純なIDを生成しようとしています。
{"name": "John", "surname": "Gates", "country": "Germany", "age": "20", "height": "180"}
{"name": "John1", "surname": "Gates", "country": "Germany", "age": "20", "height": "180"}
{"name": "John2", "surname": "Gates", "country": "Germany", "age": "20", "height": "180"}
{"name": "John3", "surname": "Gates", "country": "Germany", "age": "20", "height": "180"}
{"id": "XXX", "name": "John", "surname": "Gates", "country": "Germany", "age": "20", "height": "180"}
{"id": "XXX", "name": "John1", "surname": "Gates", "country": "Germany", "age": "20", "height": "180"}
{"id": "XXX", "name": "John2", "surname": "Gates", "country": "Germany", "age": "20", "height": "180"}
{"id": "XXX", "name": "John3", "surname": "Gates", "country": "Germany", "age": "20", "height": "180"}
約5,000,000件の類似レコードがあり、繰り返し可能で予測可能なIDを生成したいと思います。次のファイルの処理には時間が限られているため、Linuxシステムのsql liteデータベースから20分以内にこれを完了する必要があります。
MD5、SHA1は、AMD Ryzen 1900X CPUの16スレッドでGNU Parallelなどの操作を実行でき、数分で実行できない場合は使用するには高すぎます。
MD5を試してみましたが、1分45秒で28,000個のIDの計算が完了しました。 SHA1を使用すると2分3秒かかりました。
私は非常に簡単にIDを作成しようとしています。
JohnGatesGermany20180
John1GatesGermany20180
John2GatesGermany20180
John3GatesGermany20180
次の要件を満たす必要がある場合は、どの提案をしますか?
- 強く打つ
- Linux
- 5,000,000件のレコードを処理する必要があります。
- 20分以内
- 同じjson行の場合、IDは同じでなければなりません。
実行されたテスト:
#!/usr/local/bin/bash
while IFS= read -r line
do
uuid=$(uuidgen -s --namespace @dns --name "www.example.com" )
done < testfile1.txt
1,000,000行のmd5ハッシュ:
$time bash script.sh
real 13m6.914s
user 10m24.523s
sys 2m56.095s
cksum は 1,000,000 に対して crc チェックを実行します。
#!/usr/local/bin/bash
while IFS= read -r line
do
# uuid=$(uuidgen -s --namespace @dns --name "www.example.com" )
echo "$line $uuid"|cksum >> test3.txt
done < testfile1.txt
$time bash script.sh
real 12m49.396s
user 12m23.219s
sys 4m1.417s
答え1
スクリプトが長すぎるのは、uuidgen
各行で実行しているからです。cksum
各プロセスを開始するだけで多くの時間が無駄になります。
5Mライン形式を{"name": "John%d", "surname": "Gates", "country": "Germany", "age": "20", "height": "180"}
tmpfsファイルシステム上のファイルに配置するには、次のPythonスクリプトが数秒で完了します。
#! /usr/bin/env python3
import hashlib
import sys
for line in sys.stdin:
print(hashlib.md5(line.rstrip('\n').encode('utf-8')).hexdigest())
実装する:
$ time ./foo.py < input > output
./foo.py < input > output 6.00s user 0.13s system 99% cpu 6.135 total
% wc -l input output
5000000 input
5000000 output
10000000 total
Pythonなので、行をJSONにデコードして各行にIDを挿入することもできます。次のような非効率的なコードもあります。
#! /usr/bin/env python3
import hashlib
import json
import sys
for line in sys.stdin:
l = line.rstrip('\n').encode('utf-8')
o = json.loads(line)
o["id"] = hashlib.md5(l).hexdigest()
print(json.dumps(o))
1分で完了:
% time ./foo.py < input > output
./foo.py < input > output 42.11s user 0.42s system 99% cpu 42.600 total
% head output
{"name": "John1", "surname": "Gates", "country": "Germany", "age": "20", "height": "180", "id": "2dc573ccb15679f58abfc44ec8169e52"}
{"name": "John2", "surname": "Gates", "country": "Germany", "age": "20", "height": "180", "id": "ee0583acaf8ad0e502bf5abd29f37edb"}
{"name": "John3", "surname": "Gates", "country": "Germany", "age": "20", "height": "180", "id": "a7352ebb79db8c8fc2cc8758eadd9ea3"}
{"name": "John4", "surname": "Gates", "country": "Germany", "age": "20", "height": "180", "id": "2062ad1b67ccdce55663bfd523ce1dfb"}
{"name": "John5", "surname": "Gates", "country": "Germany", "age": "20", "height": "180", "id": "5f81325c104c01c3e82abd2190f14bcf"}
{"name": "John6", "surname": "Gates", "country": "Germany", "age": "20", "height": "180", "id": "493e0c9656f74ec3616e60886ee38e6a"}
{"name": "John7", "surname": "Gates", "country": "Germany", "age": "20", "height": "180", "id": "19af9ef2e20466d0fb0efcf03f56d3f6"}
{"name": "John8", "surname": "Gates", "country": "Germany", "age": "20", "height": "180", "id": "2348bd47b20ac6445213254c6a8aa80b"}
{"name": "John9", "surname": "Gates", "country": "Germany", "age": "20", "height": "180", "id": "090a521b4a858705dc69bf9c8dca6c19"}
{"name": "John10", "surname": "Gates", "country": "Germany", "age": "20", "height": "180", "id": "fc3c699323cbe399e210e4a191f04003"}
私の仕様:
- インテル®Core™i7-8700 CPU @ 3.20GHz×12
- 2666MHz DDR4メモリ
uuidgen
私はあなたのスクリプトに基づいて4分で500,000行をかろうじて管理しました。出力を保存するように変更します。
#!/usr/bin/bash
while IFS= read -r line
do
uuidgen -s --namespace @dns --name "$line"
done < input > uuid
実装する:
% timeout 240 ./foo.sh
% wc -l uuid
522160 uuid
答え2
JSON行が指示したとおりであると仮定し、awkで単純なIDアイデアを実装します。すべて1行にあります。
awk -F'"' 'BEGIN{OFS=FS} {$1=$1"\"id\": \""$4$8$12$16$20"\", "; }1' < input
私はあなたに似たシステムを持っていないので、時間が許されることを確認する必要があります。
答え3
事故実験として、私はこの種の問題を解決するためにCLIツールをどれだけ活用できるかを確認したかったのです。このために、高速ハッシュCLIツールを試してみたいと思います。xx ハッシュ値タスクを実行します。
xxHash は、RAM の制限に近い動作をする非常に高速な非暗号化ハッシュアルゴリズムです。 32ビットと64ビットの2つのバージョンがあります。
xxhsum
すべてのプログラミング言語で動作しますが、この実験ではCLIバージョン、特に32ビットモードを使用しているためxxhsum -H0
。
あなたが見つけて他の人が言ったように、ハッシュ関数CLIツールまたはツールを繰り返し呼び出すことは、通常、このタイプのアプローチが失敗した場合です。これを5M回呼び出すことは、xxhsum
それを使用するための次善策になります。利点はファイルI / Oですが、5M行を5Mファイルに変換するとどうなりますか?
これはLinuxで本当に簡単です。次のsplit
コマンドを使用します。
split -l 1 afile
各ファイルに1行ずつこれらのファイル(1Mなど)をハッシュするのはどのくらい高速ですか?
例1 ラインファイル$ cat datadir/xzeyw
{"name": "John4000", "surname": "Gates", "country": "Germany", "age": "20", "height": "180"}
1Mファイルを含むディレクトリ
$ ls -l datadir | wc -l
1000002
ハッシュする時間
$ { 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
はい、そうです。約7秒ほどかかりました!これはとても印象的だと思います。この方法では、xxhsum
1回だけ実行するコストしか発生せず、1Mファイルを繰り返すことができます。
この方法の欠点
もちろん、その欠点の1つは、split
あなたが想像できるように、これが最も高価な作業になるということです。これは、X行を含む単一のファイルをインポートして、単一の行を含むXファイルとしてHDDに分割する必要があるためです。
以下はいくつかのデータです。
./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
split
ここで作業が約2分かかったことがわかります。メモ:この出力の最初の行は、100万行のJSONを含むファイルをビルドするのにかかる時間を示しています。
もう1つの欠点は、コマンドラインによって処理されるファイルの数です。一部の場所で使用されているため、*
1Mまたは5Mファイル名に拡張され、これは危険と見なされる可能性があります。ファイル数を増やすと、コマンドラインパラメータに割り当てられたスペースを超える危険があることに注意してください。
コマンドラインの長さの詳細については、次のリンクを参照してください。
- 実際の最大パラメータリストの長さを見つけるための標準的な方法は何ですか?
- システム構成 "変数" ARG_MAXのLinux実装は他のシステム変数と異なりますか? POSIXと互換性がありますか?
- コマンドの単一パラメータの最大サイズを定義するものは何ですか?
結論として
想像できるように、1Mまたは5Mファイルでこれらの問題を解決するのはほとんど面白そうです。私も同意する必要があります。しかし、CLIツールを正しい方法で活用すると、優れたパフォーマンスが得られることが示されているため、まだ興味深い実験です。
hashy.bashコード
誰もがコードに興味がある場合:
$ 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 ""
引用する
答え4
まず、データをSQLiteデータベースにインポートします。ここでは、Miller(mlr
)を使用して提供されたJSONLデータをCSVに変換し、data
新しいデータベースのテーブルに読み込みます。
mlr --l2c cat file.json | sqlite3 database.db '.import --csv /dev/stdin data'
完了したら、UPDATEステートメントを使用して、提案されたスキームを使用して識別子を生成できます。
sqlite> .mode box
sqlite> SELECT * FROM data;
┌───────┬─────────┬─────────┬─────┬────────┐
│ name │ surname │ country │ age │ height │
├───────┼─────────┼─────────┼─────┼────────┤
│ John │ Gates │ Germany │ 20 │ 180 │
│ John1 │ Gates │ Germany │ 20 │ 180 │
│ John2 │ Gates │ Germany │ 20 │ 180 │
│ John3 │ Gates │ Germany │ 20 │ 180 │
└───────┴─────────┴─────────┴─────┴────────┘
sqlite> ALTER TABLE data ADD COLUMN id TEXT;
sqlite> UPDATE data SET id = concat(name,surname,country,age,height);
sqlite> SELECT * FROM data;
┌───────┬─────────┬─────────┬─────┬────────┬────────────────────────┐
│ name │ surname │ country │ age │ height │ id │
├───────┼─────────┼─────────┼─────┼────────┼────────────────────────┤
│ John │ Gates │ Germany │ 20 │ 180 │ JohnGatesGermany20180 │
│ John1 │ Gates │ Germany │ 20 │ 180 │ John1GatesGermany20180 │
│ John2 │ Gates │ Germany │ 20 │ 180 │ John2GatesGermany20180 │
│ John3 │ Gates │ Germany │ 20 │ 180 │ John3GatesGermany20180 │
└───────┴─────────┴─────────┴─────┴────────┴────────────────────────┘
id
明らかに、Millerに即座に列を作成するように依頼することができます。以下は、スペースで区切られた各レコードフィールドのMD5ハッシュを使用します。
mlr --l2c put '$id = md5(joinv($*," "))' file | sqlite3 database.db '.import --csv /dev/stdin data'
sqlite> .mode box
sqlite> SELECT * FROM data;
┌───────┬─────────┬─────────┬─────┬────────┬──────────────────────────────────┐
│ name │ surname │ country │ age │ height │ id │
├───────┼─────────┼─────────┼─────┼────────┼──────────────────────────────────┤
│ John │ Gates │ Germany │ 20 │ 180 │ 150c35e2efb7093e1c30a46a0226f82c │
│ John1 │ Gates │ Germany │ 20 │ 180 │ c58a8be627dc1d6c9da36dd6de9fa62d │
│ John2 │ Gates │ Germany │ 20 │ 180 │ e41b62a821f51c13eea2191ebcbb5837 │
│ John3 │ Gates │ Germany │ 20 │ 180 │ 8e1012a599356fee66727107b750ba1a │
└───────┴─────────┴─────────┴─────┴────────┴──────────────────────────────────┘
最近(2020年)、MacBook Air(M1)でこれをテストするためにMillerを使用してMD5ハッシュを計算し、500万レコードをデータベースにインポートするのに約42秒かかりました。