【问题标题】:A solution works faster than a for loop [closed]解决方案比 for 循环运行得更快[关闭]
【发布时间】:2020-03-11 19:21:33
【问题描述】:

我有一个包含 150 万个文本文件的目录,其内容如下:

TDB_TCFE9:
POLY version  3.32
POLY:POLY:POLY:POLY:POLY: Using global minimization procedure
Calculated         71400 grid points in             1 s
Found the set of lowest grid points in              1 s
 Calculated POLY solution      11 s, total time    13  s
POLY: VPV(LIQUID)=0
POLY:POLY:POLY:POLY:POLY:POLY: Using global minimization procedure
Calculated         71400 grid points in             1 s
Found the set of lowest grid points in              0 s
 Calculated POLY solution       5 s, total time     6  s
POLY: *** STATUS FOR ALL PHASES

文件被命名为id1.TCM.log。只有idchanges 之后的数字。

我想要做的是 grep VPV(LIQUID)= 之后的值并将这个值提供给 x1 然后 x2。 如果x2大于0.0001,则将对应的文件移动到目录with_liquid。如果没有,什么也不做。

我使用的代码是

for j in `seq 1500000`
do
echo ${j}
  x1=`grep -a VPV\(LIQUID\) id${j}.TCM.log |sed s/POLY_3://g|awk 'BEGIN{FS="="}{print $2}'|tail -1`
    x2=$(printf "%.14f" $x1)
    if [ $(echo "$x2 > 0.0001"| bc -l)  -eq 1 ]; then
    mv id${j}.TCM.log with_liquid
    fi
done 

效果很好。唯一的问题是耗时太长。我怎样才能做得更快?我也对 python 代码或任何其他解决方案持开放态度。

非常感谢。

【问题讨论】:

  • 您是否考虑过使用多处理来利用所有 CPU 内核?
  • @Chris 我对此持开放态度,但不知道该怎么做。
  • 每个文件都只有示例那么大吗?

标签: python linux grep


【解决方案1】:

仅提高脚本性能的第一步:

for file in id{0..1500000}.TCM.log; do  # Use a brace expansion
                                        # instead of the subshell to seq
    printf 'Processing %s\n' "$file"
    x1=$(grep -o 'VPV(LIQUID)=\d*\.\?\d*' "$file")  # VPV(LIQUID)=0.0 or the like
    printf -v x2 '%.14f' "${x1#*=}"  # Parameter expansion to strip the part before the =
    bc -l <<< "$x2 > 0.0001" ||  # Use the exit status value directly
        mv "$file" with_liquid/
done

鉴于您在这里有多少文件,如何实现这一点很重要,但不如并行化重要。一个简单的方法是使用带有 -P 标志的xargs 之类的工具。更强大的实现是GNU Parallel。假设您在名为 xsplit 的可执行文件中定义脚本的中间部分(for 循环内的所有内容,file 重命名为 $1)。

printf '%s\n' id{0..1500000}.TCM.log | xargs -P 50 ./xsplit

您需要仔细调整maxprocs 以达到您的目的——这是xargs 将产生的最大进程数。我这里随便选了50。

另一个考虑因素是,根据您提供的示例文件,这些文件都非常小——不到 600 字节。因此,整个 1.5M 文件集不到 1 GB——您可以在商用硬件上一次将所有文件加载到内存中。这将整个脚本归结为

[[ $(<file) =~ VPV\(LIQUID\)=([[:digit:]]\.?[[:digit:]]*) ]] &&
    bc -l <<< "${BASH_REMATCH[1]} > 0.0001" ||
    mv "$file" with_liquid/

【讨论】:

  • 我在Processing id212.TCM.log ./del_liq_new.sh: line 7: printf: `%.14f': not a valid identifier ^C(standard_in) 1: syntax error上方编写脚本时收到这样的错误消息
  • 很抱歉,这是一个错字。我修好了它。应该是printf -v x1 ...
  • 你的意思是这样做for file in id{0..1500000}.TCM.log; do [[ $(&lt;file) =~ VPV\(LIQUID\)=([[:digit:]]\.?[[:digit:]]*) ]] &amp;&amp; bc -l &lt;&lt;&lt; "${BASH_REMATCH[1]} &gt; 0.0001" || mv "$file" with_liquid/; done
【解决方案2】:

您的代码运行缓慢的主要原因是每个文件需要大约 9 个分叉。

如果你不分叉,它会快很多:

awk -F= '/VPV\(LIQUID\)/ && $2 > 0.0001 { print FILENAME }' id*.TCM.log |
    xargs mv -t with_liquid 

【讨论】:

  • 将 xargs 并行化,您将更加出色。
  • 不错的答案。如果您在print 之后使用gawknextfile,您可能会获得更高的性能,因此您不必费心搜索VPV(LIQUID) 之后的其余行。如果您将搜索锚定在行首,甚至可能会更多。
【解决方案3】:

这是一个可能的 Python 实现。选择是使用适合 I/O 绑定作业的轻量级线程还是使用可以更好地处理 CPU 密集型操作的进程。目前使用进程的代码已经用#注释掉了。您应该尝试两者,看看哪种效果更好。还要考虑线程数(进程数不应大于您实际拥有的处理器数)。

#!/usr/bin/env python3

from concurrent.futures import ThreadPoolExecutor
#from concurrent.futures import ProcessPoolExecutor
import re, glob, os

NUMBER_OF_THREADS = 20
NUMBER_OF_PROCESSES = 5

x1_regex = re.compile(r'VPV\(LIQUID\)=(\d+(?:\.\d*)?|\.\d+)')

def process_file(i):
    fn = f'id{i}.TCM.log'
    try:
        with open(fn) as f:
            text = f.read()
        m = x1_regex.search(text)
        if m:
            x1 = float(m[1])
            if x1 > .0001:
                os.rename(fn, f'with_liquid/{fn}')
        return None # no error
    except Exception as e:
        return (e, fn)

if __name__ == '__main__':
    with ThreadPoolExecutor(max_workers=NUMBER_OF_THREADS) as executor:
    #with ProcessPoolExecutor(max_workers=NUMBER_OF_PROCESSES) as executor:
        for result in executor.map(process_file, range(150001)):
            if result:
                print(f'Exception {result[0]} processing file {result[1]}.')

【讨论】:

  • @MarkSetchell 只是运气不好,我猜。
  • 我的意思是应该不可能回答一个已经关闭了一个小时的问题。
猜你喜欢
  • 2018-11-01
  • 2012-03-10
  • 2022-01-15
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2014-06-22
相关资源
最近更新 更多