shell脚本内存耗尽问题

5
我已经编写了以下随机数生成器shell脚本:
for i in $(seq 1 $1) #for as many times, as the first argument ($1) defines...
do 
echo "$i $((RANDOM%$2))" #print the current iteration number and a random number in [0, $2)
done

我会这样运行它:
./generator.sh 1000000000 101 > data.txt

生成1B行id和一个在[0,100]范围内的随机数,并将这些数据存储在文件data.txt中。

我想要的输出结果是:

1 39
2 95
3 61
4 27
5 85
6 44
7 49
8 75
9 52
10 66
...

对于少量的行,它运行得很好,但是对于10亿行数据,我遇到了以下OOM错误:

./generator.sh: xrealloc: ../bash/subst.c:5179: cannot allocate 18446744071562067968 bytes (4299137024 bytes allocated)

我的程序哪部分造成了这个错误?如何逐行写入 data.txt 文件呢? 我尝试用以下代码替换了 echo 行:
echo "$i $((RANDOM%$2))" >> $3

$3是data.txt,但我看不出有什么区别。


3
seq 1000000000 表示 10e9,意思是很大。我执行了 seq 1000000000 > file 命令,在我停止之前文件已经达到 5G 大小。 - fedorqui
@fedorqui 我知道,但这正是我需要的。我想要大数据来进行实验,以证明我的算法的可扩展性。我对文件大小没有问题,只是想知道我能做些什么来避免OOM错误。 - vefthym
@fedorqui,这个问题显然是由评论和答案中已经提到的问题引起的。我的问题是,为什么它没有首先达到MAX_ARG_STRLEN限制?或者它确实达到了那个限制?但如果是这样的话,故障不应该是bash中的OOM...对吧? - anishsane
@anishsane 我不知道,我也很好奇。也许你可以把它变成一个问题,我相信一些Bash/Linux/c大师可以回答它! - fedorqui
2
可能是因为bash首先计算$(...)并将其保存在内存中。在评估完成之后,它才形成实际命令for ...部分的命令行。但在第一步完成之前,它收到了OOM错误。 - anishsane
显示剩余3条评论
3个回答

10
问题在于你的 for 循环:
for i in $(seq 1 $1) 

这将首先扩展$(seq 1 $1),创建一个非常大的列表,然后将其传递给for

然而,使用while,我们可以逐行读取seq的输出,这将占用很少的内存:

seq 1 1000000000 | while read i; do
        echo $i
done

2
太棒了!实际上,你可以通过使用while read -r i; do ... done < <(seq ...)来避免使用管道。 - fedorqui
1
谢谢@fedorqui;那样更快,但在我看来也更难看 :-) 我会选择可读性,除非有充分的(性能)理由不这样做(通常一个管道性能影响并不大,尽管也有例外!)。 - Martin Tournoij
2
有一个陷阱是使用 something | while:如果你想在 while 循环中更改变量,它们不会被更改。这是因为原始 shell 中的变量被复制到新 shell 中,但更改不会影响原始 shell。请参考这篇好文章:我在管道中设置了循环中的变量。为什么循环结束后它们会消失?或者为什么我不能将数据传输到 read? - fedorqui
2
@vefthym 这不是“更好”,而只是针对同一问题的另一种解决方案。我认为这将增加您对为什么会失败以及脚本编写的理解的价值。Hari的答案仅适用于数字,对于此特定问题可能最好,但我尚未测试过。它还是bash特定的非可移植语法,如果您关心的话… 这个解决方案也适用于其他输入,例如一行文本;它也是可移植的(顺便说一下,fedorqui的建议不是可移植的)。 - Martin Tournoij
1
@vefthym 瓶颈在于针对每个数字都调用 echo 函数。 - user3442743
显示剩余4条评论

4

$(seq 1 $1)计算整个列表然后再进行迭代,因此需要存储完整的10 ^ 9 数字列表,这是很多的。

我不确定是否可以使seq懒执行,即只在需要时获取下一个数字。您可以使用简单的for循环:

for ((i=0; i<$1;++i))
do
  echo "$i $((RANDOM%$2))"
done

4
不确定您是否可以使序列懒惰运行”:这是一段shell脚本,当然可以!您只需要将输出导入到while中,它会逐行读取(请参见我的答案)。 - Martin Tournoij

4
如果您想让它运行得更快,可以尝试以下方法。您需要使用g++编译器以以下形式进行编译:
g++ -o <executable> <C++file>

例如,我是这样做的。
g++ -o inseq.exe CTest.cpp

CTest.cpp

#include <iostream>
#include <string>
#include <fstream>
#include <iomanip>
#include <cstdlib>
#include <sstream>

int main (int argc,char *argv[])
{
    std::stringstream ss;
    int x = atoi(argv[1]);
        for(int i=1;i<=x;i++)
        {
                ss << i << "\n";
                if(i%10000==0)
                {
                        std::cout << ss.rdbuf();
                        ss.clear();
                        ss.str(std::string());

                }
        }
std::cout << ss.rdbuf();
ss.clear();
ss.str(std::string());
}

速度比较

对于一个1000000行的文件,每种方法进行3次测试,得到最低速度。

Jidder

$ time ./inseq 1000000 > file

real    0m0.143s
user    0m0.131s
sys     0m0.011s

地毯吸尘器

$ cat Carpet.sh

#!/bin/bash

seq 1 $1 | while read i; do
    echo $i
done

.

$ time ./Carpet.sh 1000000 > file

 real    0m12.223s
 user    0m9.753s
 sys     0m2.140s

Hari Shankar

$ cat Hari.sh

#!/bin/bash

for ((i=0; i<$1;++i))
do
  echo "$i $((RANDOM%$2))"
done

.

$ time ./Hari.sh 1000000 > file
real    0m9.729s
user    0m8.084s
sys     0m1.064s

从这些结果可以看出,我的方法要稍微快一些,大约快60-70*。

编辑

因为Python很棒

$ cat Py.sh

#!/usr/bin/python

for x in xrange(1, 1000000):
print (x)

'

$ time ./Py.sh >file

real    0m0.543s
user    0m0.499s
sys     0m0.016s

相比于c++,它的速度要慢4倍,因此如果文件需要1小时完成,使用这两行代码则需要4小时。

编辑2

尝试在一个10亿行的文件上使用Python和c++。

对于非CPU密集型任务,这似乎会使用大量的CPU。

PID USER  %CPU   TIME+  COMMAND
56056 me  96     2:51.43 Py.sh

Python的结果

real    9m37.133s
user    8m53.550s
sys     0m8.348s

C++ 的结果

 real    3m9.047s
 user    2m53.400s
 sys     0m2.842s

1
@Carpetsmoker 可能吧。如果你将它存储在数组中并在每n个数字后一次性输出整个数组,而不是每个数字都输出的话,那么你也可以很快地在bash中完成它。我不明白你的评论“这不是CPU密集型”的意思,因为其他答案显然是这样的。 - user3442743
1
@Carpetsmoker,你让我迷惑了。这有什么关联吗?我不知道你想表达什么观点?此外,大多数*nix系统默认都提供g++,而Python则不是。 - user3442743
这是相关的,因为等效的Python脚本只有两行简单代码,而不是24行不太容易的代码;它几乎和C++代码一样快,因为C++代码不需要CPU密集型(所以性能收益很小);你可以用Ruby、Perl等语言做同样的事情。许多系统没有安装C++编译器,我认为安装了Python的系统会更多。 - Martin Tournoij
@Carpetsmoker 在 Python 中写一个答案,然后呢?我还是不明白问题是什么? - user3442743
没有问题,这只是一个评论,针对未来的读者,提到相对于其他编程语言,C++ 在性能方面的优势并不明显。 - Martin Tournoij

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