【问题标题】:Bash: uniq count large datasetBash:uniq count 大数据集
【发布时间】:2017-04-15 11:01:10
【问题描述】:

我有一组超过 70GB 的 CVS 文件,其中 35GB 是关于我感兴趣的字段(每行中该字段大约 100 字节)

数据高度重复(抽样显示前 1000 名覆盖了 50% 以上的行),我有兴趣获取 uniq 总数

如果数据集不是那么大,我会这样做

cat my.csv | cut -f 5 | sort | uniq -c | sort --numeric 工作正常

但是我遇到的问题是(据我了解)由于中间 sort ,此命令需要保存在 RAM 中(然后保存在磁盘上,因为它不适合我的 16Go RAM)数据,流到uniq -c

我想知道是否有一个命令 /script awk/python 可以一步完成sort | uniq -c,以便 RAM 消耗应该低得多?

【问题讨论】:

  • 没有测试,但this blog 假装awk 可以。
  • @jm666 据我了解,它拥有整行的 hash,这应该会降低内存消耗。如果即使是唯一行的哈希数组也无法保存在您的记忆中,您将不得不使用另一种方法。您是否有足够的存储空间来创建数据集的副本?
  • @jm666 我的坏应该阅读更广泛。作者确实使用hash 作为我所知道的associative array
  • @jm666,我偶尔运行一次,所以速度值得赞赏但不是必需的,更重要的是我有一个 100Go 磁盘和 16RAM 目前该命令正在完成,因为排序在两者中都空间不足RAM 和磁盘。
  • @silel 我目前正在运行您的解决方案,我应该很快就能告诉您它是否有效以及花费了多少次

标签: bash uniq


【解决方案1】:

你可以试试这个:

perl -F, -MDigest::MD5=md5 -lanE 'say unless $seen{ md5($F[4]) }++' < file.csv >unique_field5.txt

它将为每个唯一的field-5(例如$F[4])在内存中保存 16 字节长的 md5-digest。或者你可以使用

cut -d, -f5 csv | perl -MDigest::MD5=md5 -lnE 'say unless $seen{md5($_)}++'

同样的结果。

当然,现在 md5 在密码学上并不安全,但可能足以用于排序...当然,可以使用 sha1sha256,只需使用 -MDigest::SHA=sha255。当然,sha-digests 更长 - 例如需要更多内存。

它与 cmets 中链接的 awk 类似,不同之处在于,这里用作哈希键而不是整个输入行,而只是 16byte 长 MD5 摘要。

编辑

因为我想知道性能,所以创建了这个测试用例:

# this perl create 400,000,000 records
# each 100 bytes + attached random number,
# total size of data 40GB.
# each invocation generates same data (srand(1))
# because the random number is between 0 - 50_000_000
#    here is approx. 25% unique records.
gendata() {
perl -E '
    BEGIN{ srand(1) }
    say "x"x100, int(rand()*50_000_000) for 1..400_000_000
'
}

# the unique sorting - by digest
# also using Devel::Size perl module to get the final size of the data hold in the memory

# using md5
domd5() {
    perl -MDigest::MD5=md5 -MDevel::Size=total_size -lnE '
        say unless $seen{md5($_)}++;
        END {
            warn"total: " . total_size(\%seen);
        }'
}
#using sha256
dosha256() {
    perl -MDigest::SHA=sha256 -MDevel::Size=total_size -lnE '
        say unless $seen{sha256($_)}++;
        END {
            warn"total: " . total_size(\%seen);
        }'
}

#MAIN
time gendata | domd5    | wc -l 
time gendata | dosha256 | wc -l 

结果:

total: 5435239618 at -e line 4, <> line 400000000.
 49983353

real    10m12,689s
user    12m43,714s
sys 0m29,069s
total: 6234973266 at -e line 4, <> line 400000000.
 49983353

real    15m51,884s
user    18m23,900s
sys 0m29,485s

例如:

对于 md5

  • 内存使用量:5,435,239,618 字节 - 例如大约 5.4 GB
  • 唯一记录:49,983,353
  • 运行时间:10 分钟

对于 sha256

  • 内存使用量:6,234,973,266 字节 - 例如大约 6.2 GB
  • 唯一记录:49,983,353
  • 运行时间:16 分钟

相比之下,使用“常规”方法进行纯文本唯一搜索:

doplain() {
        perl -MDevel::Size=total_size -lnE '
                say unless $seen{$_}++;
                END {
                        warn"total: " . total_size(\%seen);
                }'
}

例如跑步:

time gendata | doplain | wc -l

结果:

  • 内存使用量要大得多:10,022,600,682 - 我的 16GB RAM 笔记本开始大量交换(因为有 SSD,所以没什么大不了的 - 但仍然......)
  • 唯一记录:49,983,353
  • 运行时间:8:30 分钟

结果?

只需使用

cut -d, -f5 csv | perl -MDigest::MD5=md5 -lnE 'say unless $seen{md5($_)}++'

你应该足够快地获得独特的线条。

【讨论】:

  • 我认为为了性能,你甚至可以使用像 murmur 这样的非加密哈希
【解决方案2】:

你可以试试这个:

split --filter='sort | uniq -c | sed "s/^\s*//" > $FILE' -b 15G -d "dataset" "dataset-"

此时您应该有大约 5 个dataset-&lt;i&gt;,每个都应该比15G 少得多。

要合并文件,您可以将以下 bash 脚本保存为 merge.bash

#! /bin/bash
#

read prev_line
prev_count=${prev_line%% *}

while read line; do
    count="${line%% *}"
    line="${line#* }" # This line does not handle blank lines correctly
    if [ "$line" != "$prev_line" ]; then
        echo "$prev_count $prev_line"
        prev_count=$count
        prev_line=$line
    else
        prev_count=$((prev_count + count))
    fi
done

echo "$prev_count $prev_line"

然后运行命令:

sort -m -k 2 dataset-* | bash merge.sh > final_dataset.

注意:空行处理不正确,如果它适合您的需要,您可以将它们从数据集中删除或更正merge.bash

【讨论】:

  • 它是并行运行还是顺序运行?
  • 您必须查看split 的文档,但我不认为合并部分是可并行化的。这很像 map/reduce 范例。
猜你喜欢
  • 1970-01-01
  • 2010-12-24
  • 2016-06-28
  • 1970-01-01
  • 2012-07-31
  • 1970-01-01
  • 1970-01-01
  • 2020-01-11
相关资源
最近更新 更多