bash:循环超过 20000 个文件很慢 - 为什么?

bash:循环超过 20000 个文件很慢 - 为什么?

在一个系统上对大量文件进行简单循环的速度是在另一个系统上的一半。

使用 bash,我做了类似的事情

for * in ./
do
   something here
done

使用“时间”,我能够确认,在系统 2 上,“此处某事”部分比在系统 1 上运行得更快。然而,系统 2 上的整个循环所花的时间是系统 1 上的两倍。为什么?...我如何才能加快速度?

目录中有大约 20000 个(文本)文件。将文件数量减少到大约 6000 个可以显著加快速度。无论使用哪种循环方法(用 find 命令替换“for * in”,甚至先将文件名放入数组中),这些结果都保持不变。

系统 1:Debian(在 openvz-vm 中,使用 reiserfs)
系统 2:Ubuntu(本机,比系统 1 更快的处理器,也更快的 Raid5,使用 ext3 和 ext4 - 结果保持不变)

到目前为止我应该排除:硬件(System2 应该更快)、用户空间软件(bash、grep、awk、find 是相同的版本)和 .bashrc(那里没有出色的配置)。

那么这是文件系统的问题吗?我可以调整 ext3/4 使其与 reiserfs 一样快吗?

感谢您的建议!

编辑: 好的,你说得对,我应该提供更多信息。现在我必须透露我初学者的 bash 口误,但我们开始吧:

 declare -a UIDS NAMES TEMPS ANGLEAS ANGLEBS
 ELEM=0
 for i in *html
    do
            #get UID
            UID=${i%-*html}
            UIDS[$ELEM]=$UID

            # get Name
            NAME=`awk -F, '/"name":"/ { lines[last] = $0 } END { print lines[last] }' ${i} | awk '{ print $2 }'`
            NAME=${NAME##\[*\"}
            NAMES[$ELEM]=$NAME

            echo "getting values for ["$UID"]" "("$ELEM "of" $ELEMS")"

            TEMPS[$ELEM]=`awk -F, '/Temperature/ { lines[last] = $0 } END { print lines[last] }' ${i} | sed 's/<[^>]*>//g' | tr -d [:punct:] | awk '{ print $3 }'`
            ANGLEAS[$ELEM]=`awk -F, '/Angle A/ { lines[last] = $0 } END { print lines[last] }' ${i} | sed 's/<[^>]*>//g' | tr -d [:punct:] | awk '{ print $3 }'`
            ANGLEBS[$ELEM]=`awk -F, '/Angle B/ { lines[last] = $0 } END { print lines[last] }' ${i} | sed 's/<[^>]*>//g' | tr -d [:punct:] | awk '{ print $3 }'`
            ### about 20 more lines like these ^^^ 
             ((ELEM++))
 done

是的,问题是,我必须读取文件 20 次,但将文件内容放入变量 (FILE=( cat $i)) 会删除换行符,我不能再使用 awk 了……?也许我尝试错了,所以如果您能给我一个建议,我将不胜感激。

尽管如此,问题仍然存在,读取该目录中的文件需要太长时间......

对于硬件问题:好吧,系统 1 运行在 5 年多前的硬件上,系统 2 运行在 2 个月前。是的,规格完全不同(其他主板、处理器等),但系统 2 在其他所有方面都更快,文件系统的原始写入/读取速率也更快。

答案1

取决于你具体在做什么,但是是的,当你在一个目录中有很多文件时,ext 文件系统会变慢。将文件拆分成例如编号的子目录是一种常见的解决方法。

答案2

对于您正在做的事情,没有必要使用数组awk。由于您正在打印,因此您似乎没有使用逗号作为字段分隔符$0

AWK 可以做您所拥有sedtr正在做的事情。

了解您的数据是什么样子的将会很有帮助。

一种方法可能是这样的(虽然看起来很丑陋):

for f in *.html
do
    read -r array1[i] array2[i] array3[i] array4[i] . . . <<< $(
        awk '
            /selector1/ {var1 = $2}
            /selector2/ {split($0,temparray,"<[^>]*>"); split(temparray[2],temparray); var2 = gensub("[[:punct:]]","","g",a[3])}
            /selector3/ {split($0,temparray,"<[^>]*>"); split(temparray[2],temparray); var3 = gensub("[[:punct:]]","","g",a[3])}
            . . .
            END { print var1, var2, var3, var4 . . . }' "$f"
((i++))
done

awk 脚本中的数组下标选择由数据的实际布局决定。可能有更好的方法,但这种方法可以避免生成大约 1,600,000 个进程(20,000 个文件 * 20 个变量 * 4 个进程/变量),因此只生成大约 20,000 个进程(每个文件一个)。

您没有说明您获得了什么样的执行时间,但通过这种优化,它可能足够快,您可以花时间在较新的系统中调查问题。

答案3

您的描述太模糊了,很难给您建议。无论如何,单个目录中有 20k 个文件是很多,但也不是那么多。

很多时候,重新思考你所做的事情可以加快速度。你的循环中当前发生了什么?你的脚本是否需要读取 20 000 个文件 20 000 次?如果是这样,是否可以修改你的脚本以仅执行读取 20 000 个文件并进行比较 20 000 次?我的意思是:1) 读取一个文件,2) 对该文件执行所有可能的比较,3) 继续下一个文件。

您提到了数组中的文件名,但在这种情况下这意味着什么?脚本是否仍需要执行 20 000 * 20 000 次读取操作,而不是 20 000 次读取操作?

相关内容