我有一个带有以下行的 shell 脚本,用于从文本文件中删除双引号 "。
sed 's/\"//g' old_file.txt > new_file.txt
还有一个awk语句仅从 ^ 分隔的文本文件中选择特定列。
这两个语句都按预期工作。但是当输入文件的大小超过几 GB 时,服务器会挂起。我想知道python是否可以更有效地做同样的事情。
更新:
它没有停止服务器,但是当我运行 shell 脚本时,托管在同一台服务器上的 mysql 很慢。
我有一个带有以下行的 shell 脚本,用于从文本文件中删除双引号 "。
sed 's/\"//g' old_file.txt > new_file.txt
还有一个awk语句仅从 ^ 分隔的文本文件中选择特定列。
这两个语句都按预期工作。但是当输入文件的大小超过几 GB 时,服务器会挂起。我想知道python是否可以更有效地做同样的事情。
更新:
它没有停止服务器,但是当我运行 shell 脚本时,托管在同一台服务器上的 mysql 很慢。
Python 不可能更快地做到这一点。稍加工作,它就可以以 +/- 相同的效率做同样的事情。除非你试图做错;因为那样它会更慢。
sed 和 awk 都在线路模式下运行。它们是相当 I/O 优化的,我认为你无法改进。如果涉及到执行操作,Python 脚本可能会更快,但在这种情况下,它不太可能是相关的。
就像@paxdiablo建议的那样通过管道传输它们:
sed 's/"//g' old_file.txt | awk '...' > new_file.txt
或者,如果列格式足够简单,您可以awk
用更简单的替换,cut
这样会更快:
sed 's/"//g' old_file.txt | cut -d' ' -f1-2,4 > new_file.txt
(例如第 1、2 和 4 列,以空格分隔)
并且如果需要中间输出,可以同时放入tee
管道中编写:
sed 's/"//g' old_file.txt | tee inter_file.txt | cut -d' ' -f1-2,4 > new_file.txt
但它实际上可能效率较低,因为两者都inter_file.txt
将new_file.txt
同时编写。
好的,现在我想我明白问题所在了。您的问题不在于脚本不够快,因为它变得尽可能快。是您的硬盘驱动器达到了吞吐量限制,因此使用它的其他应用程序会延迟。你可以说它对你的硬盘来说太快了。
一种解决方案是尝试使用ionice
给它较低的优先级。它可能会有所帮助,它可能根本没有任何区别。
ionice -c3 -p$$
为当前 shell 或脚本提供最低(空闲)I/O 优先级。同样,您可以使用以下命令以给定的优先级启动脚本:
ionice -c3 ./yourscript.sh
结果可能因使用的 I/O 调度程序而异。有些调度程序会忽略这一点,有些可能实际上会使脚本变慢(每当 mysql 请求 I/O 时)。
或者,您可以使用一个额外的程序来限制 sed 的吞吐量,并有效地使其变慢,并为 mysql 提供一些可用空间来适应。但是,您需要衡量哪种吞吐量对您来说是最佳的。
最后,如果以上都不是一个选项,你可以跳到 Python,time.sleep()
每隔几百或几千行添加一次以停止脚本一段时间,让 mysql 完成它的工作。
这可能是对您当前方法的改进,因为您可以同时执行双引号删除和列选择并写出最终结果。通过这种方式,您可以避免将非常大的中间文件写入磁盘。它也可能比将 sed 和 awk 放在管道中更快,因为这需要一个额外的进程和一些同步开销。
这不太可能。两者都不会一次将整个文件放入内存中,因此您的限制基本上是磁盘 I/O 速率。
我认为它实际上并没有挂起(完全停止),更有可能的是它只是需要一些时间来处理更大的文件。
您可以尝试几件事。
首先,如果您将该new_file.txt
文件发送到awk
流程中,则根本不要创建中间文件。你应该能够做到:
sed 's/\"//g' old_file.txt | awk 'some commands' >next_file.txt
其次,由于它是一个相当简单的替换,您可能会发现编写自己的固定过滤器比依赖运行速度较慢的脚本更快,sed
因为python
它必须满足一般情况。
换句话说,类似:
create 1M buffer
read up to 1M from input
while not EOF:
go through data removing `"` characters.
write changed buffer to output.
read up to 1M from input
正确的 C 代码是:
#include <stdio.h>
#include <errno.h>
static char buff[1000000];
int main (void) {
int sz;
char *src, *dst;
while ((sz = fread (buff, 1, sizeof(buff), stdin)) > 0) {
src = dst = buff;
while (sz-- >= 0) {
if (*src == '"') {
src++;
continue;
}
*dst++ = *src++;
}
sz = dst - buff;
if (fwrite (buff, sz, 1, stdout) != 1) {
fprintf (stderr, "Error %d writing data\n", errno);
return -1;
}
}
return 0;
}
这可能会运行得更快,因为它是针对特定情况的,但是与所有优化一样,测量,不要猜测!
在我的系统上,编译该代码gcc -O3
给出了以下结果,首先是sed
:
pax> time ( cat Photo_* | sed 's/"//g' >/dev/null )
real 0m0.094s
user 0m0.080s
sys 0m0.024s
pax> time ( cat Photo_* | sed 's/"//g' >/dev/null )
real 0m0.097s
user 0m0.076s
sys 0m0.032s
pax> time ( cat Photo_* | sed 's/"//g' >/dev/null )
real 0m0.095s
user 0m0.092s
sys 0m0.012s
pax> time ( cat Photo_* | sed 's/"//g' >/dev/null )
real 0m0.096s
user 0m0.060s
sys 0m0.048s
pax> time ( cat Photo_* | sed 's/"//g' >/dev/null )
real 0m0.095s
user 0m0.088s
sys 0m0.016s
然后对于自定义过滤器:
pax> time ( cat Photo_* | ./qq >/dev/null )
real 0m0.030s
user 0m0.012s
sys 0m0.028s
pax> time ( cat Photo_* | ./qq >/dev/null )
real 0m0.032s
user 0m0.008s
sys 0m0.032s
pax> time ( cat Photo_* | ./qq >/dev/null )
real 0m0.030s
user 0m0.012s
sys 0m0.028s
pax> time ( cat Photo_* | ./qq >/dev/null )
real 0m0.030s
user 0m0.012s
sys 0m0.028s
pax> time ( cat Photo_* | ./qq >/dev/null )
real 0m0.030s
user 0m0.012s
sys 0m0.028s
所以平均值system+user
和wallclock
时间是:
system+user wallclock
sed 0.1056 0.0954
custom 0.0400 0.0304
因此,与一般情况相比,自定义过滤器似乎很可能会给您带来性能优势。对于测试数据,有 3 个文件与该通配符匹配,分别位于 sizes和3427158
,总共大约 14M。5462472
5921534
对于更大的输入大小,使用 3,791,657,984 字节(约 4G)的单个文件,sed
(用户 + 系统)时间为 27.037 秒,而自定义时间为 9.020 秒(均取 5 个样本并取平均值)。
而且,关于你的问题编辑,如果这个过程正在减慢另一个更重要的过程,你不应该在那里运行它。您应该同时nice
使用 ionice
一个copy/ftp/rcp
命令,将文件转移到一个完全独立的盒子中,您可以在其中运行脚本到您的心脏内容,而不会损害性能。
如果我在我们的生产机器上运行脚本,降低了它们的性能,我会让一半的网站维护者要求我的头脑:-)
您不需要同时使用sed
and awk
; 只需使用awk
.
假设您想要第三列:
awk -F'^' '{gsub(/"/, "", $3); print $3;}' huge_data_file
这有许多优点,例如不需要 sed、管道、中间文件等。此外,替换"
仅针对所需列中包含的数据,而不是整行。
我在一个包含 64,400,000 行的 2.1G 文件上对此进行了测试,这些行包含重复的数据(不确定数据重复是否会影响我的测试):
"blah"^"blah blah"^"test1 ""^stuff
"blah"^"blah blah"^"test2 ""^stuff
"blah"^"blah blah"^"test3"^stuff
"blah"^"blah blah"^"test4^stuff^
$ time awk -F'^' '{gsub(/"/, "", $3); print $3;}' huge_data_file >/dev/null
real 2m31.706s
user 2m29.745s
sys 0m0.953s
没有“挂起”。虚拟内存使用量不超过 105MB。当然,实际性能可能因您的服务器而异。
[编辑]
为了比较:
$ time sed -e 's/"//g' huge_data_file | awk -F'^' '{ print $3;}' >/dev/null
real 2m9.723s
user 3m34.355s
sys 0m3.585s
这有效地破坏了我的“非 sed”解决方案,尽管它确实需要两倍的内存。