我正在寻找使用 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))
不到一分钟就完成了:
% 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"}
我的规格:
- 英特尔® 酷睿™ i7-8700 CPU @ 3.20GHz × 12
- 2666MHz DDR4内存
基于你uuidgen
的脚本在 4 分钟内勉强完成了 50 万行。修改为保存输出:
#!/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
在 awk 中实现简单的 ID 想法,假设 JSON 行如您所指示的那样——所有行都在一行上:
awk -F'"' 'BEGIN{OFS=FS} {$1=$1"\"id\": \""$4$8$12$16$20"\", "; }1' < input
我没有与你的类似的系统,所以你必须看看时间是否可以接受。
答案3
作为一个思想实验,我想看看我们可以在多大程度上推动 CLI 工具解决此类问题。为此,我想尝试使用快速哈希 CLI 工具xx哈希值来做这项工作。
xxHash 是一种速度极快的非加密哈希算法,其工作速度接近 RAM 限制。它有两种版本:32 位和 64 位。
它适用于每种编程语言,但对于本实验,我将使用 CLI 风格,xxhsum
特别是 32 位模式,因此xxhsum -H0
.
正如您所发现的以及其他人所说的那样,一遍又一遍地调用哈希函数 CLI 工具或任何工具通常是这些类型的方法失败的地方。调用xxhsum
此处 5M 次将是使用它的次优方式。它的优势在于文件 I/O,那么如果我们将 5M 行转换为 5M 文件呢?
该任务在 Linux 中实际上很简单,使用以下split
命令:
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 工具,您可以从中获得出色的性能。
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
即时创建您的专栏。以下使用每个记录的空格分隔字段的 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 秒。