【问题标题】:Slow speed with gawk for multiple edits the same file使用gawk慢速多次编辑同一个文件
【发布时间】:2021-11-13 01:45:21
【问题描述】:

我运行一个测试环境,在其中我使用 lorem alg 创建了 40 000 个测试文件。文件大小在 200k 到 5 MB 之间。我想修改很多随机文件。我将通过删除 2 行并使用 base64 字符串插入 1 行来更改 5% 的行。

问题是这个过程需要很多时间每个文件。我尝试通过将 testfile 复制到 ram 并在那里进行更改来修复它,但我看到一个仅使用一个完整核心的单个线程和 gawk 显示最多的 cpu 工作。我正在寻找一些解决方案,但我找不到正确的建议。我认为 gawk 可以一步完成,但是对于大文件,当我使用“getconf ARG_MAX”进行计算时,我会得到一个很长的字符串。

如何加快速度?

zeilen=$(wc -l < testfile$filecount.txt);
    
    durchlauf=$(($zeilen/20))
    zeilen=$((zeilen-2))
    for (( c=1; c<=durchlauf; c++ ))
    do
        zeile=$(shuf -i 1-$zeilen -n 1);
        
        zeile2=$((zeile+1))
        zeile3=$((zeile2+1))
        
        string=$(base64 /dev/urandom | tr -dc '[[:print:]]' | head -c 230)
        
        if [[ $c -eq 1 ]] 
        then
        gawk -v n1="$zeile" -v n2="$zeile2" -v n3="$zeile3" -v s="$string" 'NR==n1{next;print} \
        NR==n2{next; print} NR==n3{print s}1' testfile$filecount.txt > /mnt/RAM/tempfile.tmp
        else
        gawk -i inplace -v n1="$zeile" -v n2="$zeile2" -v n3="$zeile3" -v s="$string" 'NR==n1{next; print} \
        NR==n2{next; print} NR==n3{print s}1' /mnt/RAM/tempfile.tmp
        fi
       
    done

【问题讨论】:

  • 听起来好像是 CPU 受限,而不是 I/O 受限。也许您可以使用multi-threading 来并行处理文件?
  • gawk 不是你的问题。在 shell 循环中重复调用 gawk 和其他工具是您的问题。有关详细信息,请参阅why-is-using-a-shell-loop-to-process-text-considered-bad-practiceedit 您的问题显示minimal reproducible example 具有简洁、可测试的样本输入和预期输出,并解释您的要求,以便我们为您提供帮助。您可能还希望在发布示例时使用英文变量名,以便更多人能够理解您的代码。
  • 一次性完成确实会快得多,并且可以使用小而恒定大小的参数来完成。
  • {next; print} 并没有像您认为的那样做; next 说要跳过 gawk 脚本的其余部分,回到 gawk 脚本的开头并处理下一个输入 => print 永远不会被处理;这就解释了为什么{next; loop change same fileprint} 不会产生错误……loop change same fileprint 永远不会被读取/处理;我假设您想跳过当前行,阅读下一行并从脚本中的同一点继续处理,在这种情况下,您可能想用getline 替换next,尽管'next' 应该足够了整体逻辑的一些变化
  • to:Ed Morton:我想改变量名,但后来我忘了。
    to:mark-fuso:我从其他帖子中复制了它。对于一份小工作来说,很难理解 awk。 “循环更改相同的文件打印”是一个复制错误 - 不是我的。我会删除它

标签: bash performance file awk


【解决方案1】:

假设:

  • 生成$durchlauf(一个数字)随机行号;我们将单个数字称为n ...
  • 从输入文件中删除编号为nn+1 的行并取而代之...
  • 插入$string(随机生成的base64字符串)
  • 此随机行号列表不能有任何连续的行号

正如其他人指出的那样,您希望将自己限制为每个输入文件的单个 gawk 调用。

新方法:

  • 生成$durchlauf(计数)随机数(见gen_numbers()函数)
  • 生成 $durchlauf (count) base64 字符串(我们将重用 Ed Morton 的代码)
  • paste 将这 2 组数据合并到单个输入流/文件中
  • 将 2 个文件提供给 gawk ... paste 结果和要修改的实际文件
  • 我们将无法使用 gawk-i inplace,因此我们将使用中间 tmp 文件
  • 当我们在输入文件中找到匹配行时,我们将 1) 插入 base64 字符串,然后 2) 跳过/删除当前/下一个输入线条;这应该解决我们有两个随机数不同+1 的问题

确保我们不会生成连续行号的一个想法:

  • 将我们的行号集分解为范围,例如,将 100 行分成 5 个范围 => 1-20 / 21-40 / 41-60 / 61-80 / 81-100
  • 将每个范围的末尾减1,例如1-19 / 21-39 / 41-59 / 61-79 / 81-99
  • 使用$RANDOM 生成每个范围之间的数字(这往往比类似的shuf 调用至少快一个数量级)

我们将使用一个函数来生成我们的非连续行号列表:

gen_numbers () {

max=$1                             # $zeilen     eg, 100
count=$2                           # $durchlauf  eg, 5

interval=$(( max / count ))        # eg, 100 / 5 = 20

for (( start=1; start<max; start=start+interval ))
do
        end=$(( start + interval - 2 ))

        out=$(( ( RANDOM % interval ) + start ))
        [[ $out -gt $end ]] && out=${end}

        echo ${out}
done
}

示例运行:

$ zeilen=100
$ durchlauf=5
$ gen_numbers ${zeilen} ${durchlauf}
17
31
54
64
86

paste/gen_numbers/base64/tr/gawk 理念的演示:

$ zeilen=300
$ durchlauf=3
$ paste <( gen_numbers ${zeilen} ${durchlauf} ) <( base64 /dev/urandom | tr -dc '[[:print:]]' | gawk -v max="${durchlauf}" -v RS='.{230}' '{print RT} FNR==max{exit}' ) 

这会生成:

74      7VFhnDN4J...snip...rwnofLv
142     ZYv07oKMB...snip...xhVynvw
261     gifbwFCXY...snip...hWYio3e

主要代码:

tmpfile=$(mktemp)

while/for loop ... # whatever OP is using to loop over list of input files
do
    zeilen=$(wc -l < "testfile${filecount}".txt)
    durchlauf=$(( $zeilen/20 ))

    awk '

    # process 1st file (ie, paste/gen_numbers/base64/tr/gawk)

    FNR==NR        { ins[$1]=$2                 # store base64 in ins[] array
                     del[$1]=del[($1)+1]        # make note of zeilen and zeilen+1 line numbers for deletion
                     next
                   }

    # process 2nd file

    FNR in ins     { print ins[FNR] }           # insert base64 string?

    ! (FNR in del)                              # if current line number not in del[] array then print the line

    ' <( paste <( gen_numbers ${zeilen} ${durchlauf} ) <( base64 /dev/urandom | tr -dc '[[:print:]]' | gawk -v max="${durchlauf}" -v RS='.{230}' '{print RT} FNR==max{exit}' )) "testfile${filecount}".txt > "${tmpfile}"

    # the last line with line continuations for readability:
    #' <( paste \
    #         <( gen_numbers ${zeilen} ${durchlauf} ) \
    #         <( base64 /dev/urandom | tr -dc '[[:print:]]' | gawk -v max="${durchlauf}" -v RS='.{230}' '{print RT} FNR==max{exit}' ) \
    #   ) \
    #"testfile${filecount}".txt > "${tmpfile}"

    mv "${tmpfile}" "testfile${filecount}".txt

done

awk 代码的简单示例:

$ cat orig.txt
line1
line2
line3
line4
line5
line6
line7
line8
line9

$ cat paste.out           # simulated output from paste/gen_numbers/base64/tr/gawk
1 newline1
5 newline5

$ awk '...' paste.out orig.txt
newline1
line3
line4
newline5
line7
line8
line9

【讨论】:

  • 感谢您的提示。我的帖子在你的上面——我不知道为什么——现在它被关注了——这个工具很有趣
【解决方案2】:

我不知道你脚本的其余部分在做什么,但下面将告诉你如何大幅提高它的性能。

在循环的每次迭代中调用base64trheadawk 而不是这样,所有开销都意味着:

for (( c=1; c<=3; c++ ))
do
    string=$(base64 /dev/urandom | tr -dc '[[:print:]]' | head -c 230)
    echo "$string" | awk '{print "<" $0 ">"}'
done
<nSxzxmRQc11+fFnG7ET4EBIBUwoflPo9Mop0j50C1MtRoLNjb43aNTMNRSMePTnGub5gqDWeV4yEyCVYC2s519JL5OLpBFxSS/xOjbL4pkmoFqOceX3DTmsZrl/RG+YLXxiLBjL//I220MQAzpQE5bpfQiQB6BvRw64HbhtVzHYMODbQU1UYLeM6IMXdzPgsQyghv1MCFvs0Nl4Mez2Zh98f9+472c6K+44nmi>
<9xfgBc1Y7P/QJkB6PCIfNg0b7V+KmSUS49uU7XdT+yiBqjTLcNaETpMhpMSt3MLs9GFDCQs9TWKx7yXgbNch1p849IQrjhtZCa0H5rtCXJbbngc3oF9LYY8WT72RPiV/gk4wJrAKYq8/lKYzu0Hms0lHaOmd4qcz1hpzubP7NuiBjvv16A8T3slVG1p4vwxa5JyfgYIYo4rno219ba/vRMB1QF9HaAppdRMP32>
<K5kNgv9EN1a/c/7eatrivNeUzKYolCrz5tHE2yZ6XNm1aT4ZZq3OaY5UgnwF8ePIpMKVw5LZNstVwFdVaNvtL6JreCkcO+QtebsCYg5sAwIdozwXFs4F4hZ/ygoz3DEeMWYgFTcgFnfoCV2Rct2bg/mAcJBZ9+4x9IS+JNTA64T1Zl+FJiCuHS05sFIsZYBCqRADp2iL3xcTr913dNplqUvBEEsW1qCk/TDwQh>

你应该这样写,它只调用每个工具一次,因此运行速度会快几个数量级:

$ base64 /dev/urandom | tr -dc '[[:print:]]' |
    gawk -v RS='.{230}' '{print "<" RT ">"} NR==3{exit}'
<X0If1qkQItVLDOmh2BFYyswBgKFZvEwyA+WglyU0BhqWHLzURt/AIRgL3olCWZebktfwBU6sK7N3nwK6QV2g5VheXIY7qPzkzKUYJXWvgGcrIoyd9tLUjkM3eusuTTp4TwNY6E/z7lT0/2oQrLH/yZr2hgAm8IXDVgWNkICw81BRPUqITNt3VqmYt/HKnL4d/i88F4QDE0XgivHzWAk6OLowtmWAiT8k1a0Me6>
<TqCyRXj31xsFcZS87vbA50rYKq4cvIIn1oCtN6PJcIsSUSjG8hIhfP8zwhzi6iC33HfL96JfLIBcLrojOIkd7WGGXcHsn0F0XVauOR+t8SRqv+/t9ggDuVsn6MsY2R4J+mppTMB3fcC5787u0dO5vO1UTFWZG0ZCzxvX/3oxbExXb8M54WL6PZQsNrVnKtkvllAT/s4mKsQ/ojXNB0CTw7L6AvB9HU7W2x+U3j>
<ESsGZlHjX/nslhJD5kJGsFvdMp+PC5KA+xOYlcTbc/t9aXoHhAJuy/KdjoGq6VkP+v4eQ5lNURdyxs+jMHqLVVtGwFYSlc61MgCt0IefpgpU2e2werIQAsrDKKT1DWTfbH1qaesTy2IhTKcEFlW/mc+1en8912Dig7Nn2MD8VQrGn6BzvgjzeGRqGLAtWJWkzQjfx+74ffJQUXW4uuEXA8lBvbuJ8+yQA2WHK5>

【讨论】:

  • 我没有意识到循环,但意图很明确
【解决方案3】:

@mark-fuso,哇,速度快得令人难以置信!但是剧本有一个错误。该文件的大小有点增长,这是我必须避免的。我认为如果两个随机行号 ($durchlauf) 彼此跟随,则不会删除一行。老实说,我不完全理解你的命令在做什么,但它工作得很好。我认为对于这样的任务,我必须有更多的 bash 经验。

样本输出:

64
65
66
gOf0Vvb9OyXY1Tjb1r4jkDWC4VIBpQAYnSY7KkT1gl5MfnkCMzUmN798pkgEVAlRgV9GXpknme46yZURCaAjeg6G5f1Fc7nc7AquIGnEER>
AFwB9cnHWu6SRnsupYCPViTC9XK+fwGkiHvEXrtw2aosTGAAFyu0GI8Ri2+NoJAvMw4mv/FE72t/xapmG5wjKpQYsBXYyZ9YVV0SE6c6rL>
70
71

【讨论】:

  • 您的答案可以通过额外的支持信息得到改进。请edit 添加更多详细信息,例如引用或文档,以便其他人可以确认您的答案是正确的。你可以找到更多关于如何写好答案的信息in the help center
  • 这不是一个答案,它应该是一个评论,如果您需要提供像示例输出这样的格式化文本,然后编辑您的问题以包含它。
  • 我试着理解你的命令是做什么的。随之而来的问题:是什么让 del[$1]=del[($1)+1] ?? ; $1 中是当前的 $shuf; del 是一个数组
  • 从您的示例输出中,我假设 2x 行号(要删除)是 6768;对于输入67,脚本将删除行6768并插入gOf0V...;对于输入68,脚本将删除行6869并插入AFwB9...;这意味着行676869 被删除,这就是您的输出显示的内容;您的脚本会重新计算每次传递的行数,而我的脚本只计算一次;在 20x 连续行号的最坏情况下,我的脚本将删除 21 行,而您的脚本将删除 40 行 ...
  • 我已经更新了答案以确保我们不会生成任何连续的行号(通过调用 gen_numbers() 函数);如果这不能解决file grows in size a little bit 的问题,那么我们需要更多详细信息...
猜你喜欢
  • 1970-01-01
  • 2020-10-02
  • 2014-02-18
  • 2020-08-18
  • 1970-01-01
  • 1970-01-01
  • 2020-09-09
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多