為 json 內容索引生成唯一 ID
我正在尋找使用 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。由於處理以下文件的時間會受到限制,因此我需要在 20 分鐘的視窗內對 Linux 機器上的 sql lite 數據庫執行此操作。
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
作為一個思想實驗,我想看看我們可以將 CLI 工具推到多遠來解決這類問題。為此,我想嘗試使用快速散列 CLI 工具xxHash來完成這項工作。
xxHash 是一種速度極快的非加密雜湊算法,其工作速度接近 RAM 限制。它以兩種形式提出,32 位和 64 位。
它在每種程式語言中都可用,但對於這個實驗,我將使用 CLI 風格
xxhsum
,特別是 32 位模式,所以xxhsum -H0
.正如您所發現的和其他人所說的那樣,一遍又一遍地呼叫散列函式 CLI 工具或任何工具通常是這些類型的方法失敗的地方。在這裡呼叫
xxhsum
5M 次將是使用它的次優方式。它的強項在於文件 I/O,那麼如果我們把 5M 的行,轉換成 5M 的文件呢?
split
使用以下命令,該任務在 Linux 中實際上是微不足道的:split -l 1 afile
以及像這樣散列這些文件的速度有多快,比如 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
這種方式使用,我們只產生了執行一次的成本,並允許它循環遍歷 1M 文件。這種方法的缺點
因此,這樣做的缺點之一當然是
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 分鐘。**注意:**此輸出中的第一行顯示了建構包含 1M 行 JSON 的文件的時間。另一個缺點是我們在命令行上處理的文件數量。我
*
在地方使用,所以這將擴展到 1M 或 5M 文件名,這可能被認為是危險的,它是。請記住,當您增加文件數量時,您會面臨超出分配給命令行參數的空間量的風險。請參閱有關命令行長度的這些連結:
結論
所以你可以想像,使用 1M 文件或 5M 文件解決這樣的問題看起來幾乎是荒謬的。我不得不同意。但這仍然是一個有趣的實驗,因為它表明如果您以適當的方式利用 CLI 工具,您可以從中獲得出色的性能。
hash.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 ""
參考
我敢打賭,你的腳本需要這麼長時間的原因是你每行都在執行
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))
不到一分鐘完成:
% 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"}
我的規格:
- Intel® Core™ i7-8700 CPU @ 3.20GHz × 12
- 2666MHz DDR4 記憶體
基於你
uuidgen
的腳本在 4 分鐘內勉強完成了 500k 行。修改為保存輸出:#!/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