每行进行Base64编码的更快方法

3

我有一个相当大的文本文件(约10GB),它可以轻松地放入内存中。我的目标是将每一行转换为Base64字符串。当前我的方法需要花费很长时间,并且似乎无法完成,因为它是单线程的。

while read line; do echo -n -i $line | base64 >> outputfile.txt; done < inputfile.txt

能否给我一些提示如何更快地完成这个任务?目前每小时生成的数据约为100 MB(因此需要100个小时才能完成),CPU使用率为5%,磁盘使用率也非常低。

似乎我在控制字符方面被误解了... 所以我包含了一个样本文本文件以及输出应该是什么样子的(chepner对于chomp是正确的):

样本输入:

Банд`Эрос
testè!?£$
``
▒``▒`

样例输出:

user@monster ~ # head -n 5 bash-script-output.txt
0JHQsNC90LRg0K3RgNC+0YE=
dGVzdMOoIT/CoyQ=
YGA=
4paSYGDilpJg

user@monster ~ # head -n 5 perl-without-chomp.txt
0JHQsNC90LRg0K3RgNC+0YEK
dGVzdMOoIT/CoyQK
YGAK
4paSYGDilpJgCg==

user@monster ~ # head -n 5 perl-chomp.txt
0JHQsNC90LRg0K3RgNC+0YE=
dGVzdMOoIT/CoyQ=
YGA=
4paSYGDilpJg

因此,样本总是比人类声明更好;=)


1
echo 的哪个版本支持 -i 选项? - chepner
默认情况下,base64 会在长编码行中插入换行符;您可能希望通过使用选项 -w0 来避免这种情况。如果您的输入文件包含 NUL 字符(这意味着它实际上不是文本文件),那么将其读入 shell 变量时将无法保留它们。 - rici
非常不愉快的是你给了我一個負評,理由是我回答的是Windows的問題,而當時問題“在你的理解中”是關於Linux的。 你有在你的問題中看到Linux這個詞嗎? - Garric
2个回答

4

打开输出文件只有一次可能会有所帮助:

while IFS= read -r line; do echo -n $line | base64; done < inputfile.txt > outputfile.txt

bash 在这里不是一个好选择,有两个原因:首先对文件进行迭代本来就很慢,其次你需要为每一行启动一个新进程。更好的做法是使用具有计算 base64 值库的语言,以便一切都在一个进程中处理。下面是一个使用 Perl 的例子:

perl -MMIME::Base64 -ne 'print encode_base64($_)' inputfile.txt > outputfile.txt

我正在使用echo -n -i(i表示忽略/隐藏不可打印字符,n表示换行符)... 我无法将其适应于$ _,因此您的编码不考虑此问题。但它非常快,大约每5秒200MB。 - snapo
Perl行与运行base64 < inputfile.txt > outputfile.txt相同--但不会产生与逐行读取输入的输出相同的结果(区别在于慢版本中维护换行符)。 - gilez
@gilez 不是的,每一行输入文件都是单独编码和打印的。 - chepner
@snapo:我不知道你希望echo -i做什么——我熟悉的每个版本的echo都只会回显字符串“-i”——但是你可以通过将命令更改为'chomp;print encode_base64($_)'来在Perl中剪切尾随的换行符。(如果它是一个文本文件,为什么会有非打印字符呢?另外,为什么你觉得有必要对一个文本文件进行base64编码呢?) - rici
@chepner,非常感谢您。正是因为缺少了chomp函数,才无法去除那些不可打印的控制字符。 - snapo
@chepner,我承认我错了。诚然,我没有检查Perl版本,只是检查了Bash方面。 - gilez

0

不要使用Perl或任何其他动态类型语言来处理10G的文本,特别是如果:受限于串行处理,预计源有效载荷随时间增加和/或有一些处理时间的SLA。

如果顺序不重要,则绝对可以绕过高级语言方法,因为您可以免费并行处理,只需使用shell和posix组件即可。

$ printf "%s\n" one two three
one
two
three
$ printf "%s\n" one two three \
> | xargs \
>      -P3 `# three parallel processes` \
>      -L1 `# use one line from stdin` \
>     -- sh -c 'echo $@ | base64' _ 
b25lCg==
dHdvCg==
dGhyZWUK

即使顺序(按读取顺序、处理顺序、写入顺序)是一个约束,我仍然会利用可用的多个核心,并将工作分发到多个处理程序,然后再扇入一些类似于减速器的单个进程。
# add line number to each line
$ printf "%s\n" one two three | nl
     1  one
     2  two
     3  three
# base64 encode second column
$ printf "%s\n" one two three \
>     | nl \
>     | xargs -P3 -L1 sh -c \
>       'echo $2 | base64 | xargs printf "%s %s\n" "$1"' _
2 dHdvCg==
1 b25lCg==
3 dGhyZWUK
# sort based on numeric value of first col
$ printf "%s\n" one two three \
>     | nl \
>     | xargs -P3 -L1 sh -c \
>       'echo $2 | base64 | xargs printf "%s %s\n" "$1"' _ \
>     | sort -k1 -n
1 b25lCg==
2 dHdvCg==
3 dGhyZWUK

所有这些方法都可以扩展到可用核心的数量,所有与文本处理相关的繁重工作都由古老的C二进制文件完成,这将优于任何其他东西。

如果你是一个程序员,可以全部使用C语言完成,但我可以保证上述方法将优于任何用Perl、Python、Ruby等编写的代码。内核将在管道之间管理缓冲区,这意味着大部分奇怪而糟糕的工作已经完成。


呼哧呼哧.. 不要使用Perl或其他动态类型语言来处理10G 为什么不呢?所有这些方法都可以扩展,并且只是因为在Linux上正确管理了行缓冲,但不能保证来自并行进程的行不会互换。您的所有片段都将不正确地处理带有-e或空格的行。在扩展周围使用引号,并使用printf“%s \ n”“$ 1”而不是echo - KamilCuk
为什么不:GIL - christian calloway
1: "为什么不": GIL 2: "只是因为在Linux上适当管理了行缓冲,所以刚好能够工作": 哈哈,这可不是小事情 3: "并行进程将不会交替执行": 你的意思是交错执行,这只是一个有趣的例子 4: "使用引号.. "$1" 而不是 echo.": 我不知道你在说什么 - $1就是$1,不是一个参数 5: "pffft": 它是“pffft”,“puff”是同性恋的俚语。冷静点,伙计 - 我知道那个Perl注释让你受伤了,但没那么严重。 - christian calloway
对于单词拆分,我不知道你在说什么。将 printf "%s\n" -E -e -n "a b" | xargs -d'\n' -n1 -- sh -c -x 'echo $@ | base64' _printf "%s\n" -E -e -n "a b" | xargs -d'\n' -n1 -- sh -c -x 'printf "%s\n" "$@" | base64' _ 进行比较。请参阅 为什么 printf 比 echo 更好 - KamilCuk
这个示例的重点是阐明并行性如何使得 OP 受益,相对于单进程、串行迭代方法的算法复杂度为 o(n)。没有人在这里提供经过证明的工作,也不应该有人期望它。 - christian calloway

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接