gawk和....之间的性能差异是什么?

时间:2015-04-23 14:02:06

标签: c perl awk

此问题已经过讨论here on Meta,我的answer会提供指向测试系统的链接以解答此问题。

由于性能问题,经常会出现关于是否使用gawk或mawk或C或其他语言的问题,所以让我们为一个简单而典型的awk程序创建一个规范的问题/答案。

这样的结果将提供一个答案,它可以比较在简单输入文件上执行正则表达式匹配和字段拆分的基本文本处理任务的不同工具的性能。如果工具X的速度是此任务的每个其他工具的两倍,则这是有用的信息。如果所有工具花费大约相同的时间,那么这也是有用的信息。

这将起作用的方式是,在接下来的几天内,许多人将提供“答案”,这些是要测试的程序,然后一个人(志愿者?)将在一个平台(或几个)上测试所有这些人们将在他们的平台上测试一些子集,以便我们进行比较)然后将所有结果收集到一个答案中。

给出由此脚本创建的1000万行输入文件:

$ awk 'BEGIN{for (i=1;i<=10000000;i++) print (i%5?"miss":"hit"),i,"  third\t \tfourth"}' > file

$ wc -l file
10000000 file

$ head -10 file
miss 1   third          fourth
miss 2   third          fourth
miss 3   third          fourth
miss 4   third          fourth
hit 5   third           fourth
miss 6   third          fourth
miss 7   third          fourth
miss 8   third          fourth
miss 9   third          fourth
hit 10   third          fourth

并且给出了这个awk脚本,它打印了以“hit”开头的每一行的第4个然后第1个和第3个字段,然后是偶数:

$ cat tst.awk
/hit [[:digit:]]*0 / { print $4, $1, $3 }

以下是预期输出的前5行:

$ awk -f tst.awk file | head -5
fourth hit third
fourth hit third
fourth hit third
fourth hit third
fourth hit third

这是通过管道传输到第二个awk脚本来验证上面的主脚本实际上是否按预期运行时的结果:

$ awk -f tst.awk file |
awk '!seen[$0]++{unq++;r=$0} END{print ((unq==1) && (seen[r]==1000000) && (r=="fourth hit third")) ? "PASS" : "FAIL"}'
PASS

以下是在cygwin64上以bash 4.3.33运行的第3次执行 gawk 4.1.1的时间结果:

$ time awk -f tst.awk file > /dev/null
real    0m4.711s
user    0m4.555s
sys     0m0.108s

注意以上是删除缓存差异的第3次执行。

任何人都可以提供等效的C,perl,python,无论代码如何:

$ cat tst.awk
/hit [[:digit:]]*0 / { print $4, $1, $3 }

即。在一条线上找到THAT REGEXP(我们不是在寻找一些适用于正则表达式的其他解决方案),在每个连续的空白系列中分割线并打印第4个,然后是第1个,然后是第3个由a分隔的字段单个空白字符?

如果是这样,我们可以在一个平台上测试它们以查看/记录性能差异。

到目前为止的代码:

AWK(可以针对gawk等进行测试,但是mawk,nawk和其他人可能需要[0-9]而不是[:digit:])

awk '/hit [[:digit:]]*0 / { print $4, $1, $3 }' file

PHP

php -R 'if(preg_match("/hit \d*0 /", $argn)){$f=preg_split("/\s+/", $argn); echo $f[3]." ".$f[0]." ".$f[2];}' < file

egrep 'hit [[:digit:]]*0 ' file | awk '{print $4, $1, $3}'
grep --mmap -E "^hit [[:digit:]]*0 " file | awk '{print $4, $1, $3 }'

红宝石

$ cat tst.rb
File.open("file").readlines.each do |line|
  line.gsub(/(hit)\s[0-9]*0\s+(.*?)\s+(.*)/) { puts "#{$3} #{$1} #{$2}" }
end
$ ruby tst.rb

的Perl

$ cat tst.pl
#!/usr/bin/perl -nl
# A solution much like the Ruby one but with atomic grouping
print "$4 $1 $3" if /^(hit)(?>\s+)(\d*0)(?>\s+)((?>[^\s]+))(?>\s+)(?>([^\s]+))$/
$ perl tst.pl file

的Python

none yet

C

none yet

5 个答案:

答案 0 :(得分:4)

在awk之前应用egrep可以提高速度:

paul@home ~ % wc -l file
    10000000 file
paul@home ~ % for i in {1..5}; do time egrep 'hit [[:digit:]]*0 ' file | awk '{print $4, $1, $3}' | wc -l ; done
    1000000
    egrep --color=auto 'hit [[:digit:]]*0 ' file  0.63s user 0.02s system 85% cpu 0.759 total
    awk '{print $4, $1, $3}'  0.70s user 0.01s system 93% cpu 0.760 total
    wc -l  0.00s user 0.02s system 2% cpu 0.760 total
    1000000
    egrep --color=auto 'hit [[:digit:]]*0 ' file  0.65s user 0.01s system 85% cpu 0.770 total
    awk '{print $4, $1, $3}'  0.71s user 0.01s system 93% cpu 0.771 total
    wc -l  0.00s user 0.02s system 2% cpu 0.771 total
    1000000
    egrep --color=auto 'hit [[:digit:]]*0 ' file  0.64s user 0.02s system 82% cpu 0.806 total
    awk '{print $4, $1, $3}'  0.73s user 0.01s system 91% cpu 0.807 total
    wc -l  0.02s user 0.00s system 2% cpu 0.807 total
    1000000
    egrep --color=auto 'hit [[:digit:]]*0 ' file  0.63s user 0.02s system 86% cpu 0.745 total
    awk '{print $4, $1, $3}'  0.69s user 0.01s system 92% cpu 0.746 total
    wc -l  0.00s user 0.02s system 2% cpu 0.746 total
    1000000
    egrep --color=auto 'hit [[:digit:]]*0 ' file  0.62s user 0.02s system 88% cpu 0.727 total
    awk '{print $4, $1, $3}'  0.67s user 0.01s system 93% cpu 0.728 total
    wc -l  0.00s user 0.02s system 2% cpu 0.728 total

paul@home ~ % for i in {1..5}; do time gawk '/hit [[:digit:]]*0 / { print $4, $1, $3 }' file > /dev/null; done
    gawk '/hit [[:digit:]]*0 / { print $4, $1, $3 }' file > /dev/null  2.46s user 0.04s system 97% cpu 2.548 total
    gawk '/hit [[:digit:]]*0 / { print $4, $1, $3 }' file > /dev/null  2.43s user 0.03s system 98% cpu 2.508 total
    gawk '/hit [[:digit:]]*0 / { print $4, $1, $3 }' file > /dev/null  2.40s user 0.04s system 98% cpu 2.489 total
    gawk '/hit [[:digit:]]*0 / { print $4, $1, $3 }' file > /dev/null  2.38s user 0.04s system 98% cpu 2.463 total
    gawk '/hit [[:digit:]]*0 / { print $4, $1, $3 }' file > /dev/null  2.39s user 0.03s system 98% cpu 2.465 total

&#39; NAWK&#39;甚至更慢!

paul@home ~ % for i in {1..5}; do time nawk '/hit [[:digit:]]*0 / { print $4, $1, $3 }' file > /dev/null; done                                          
    nawk '/hit [[:digit:]]*0 / { print $4, $1, $3 }' file > /dev/null  6.05s user 0.06s system 92% cpu 6.606 total
    nawk '/hit [[:digit:]]*0 / { print $4, $1, $3 }' file > /dev/null  6.11s user 0.05s system 96% cpu 6.401 total
    nawk '/hit [[:digit:]]*0 / { print $4, $1, $3 }' file > /dev/null  5.78s user 0.04s system 97% cpu 5.975 total
    nawk '/hit [[:digit:]]*0 / { print $4, $1, $3 }' file > /dev/null  5.71s user 0.04s system 98% cpu 5.857 total
    nawk '/hit [[:digit:]]*0 / { print $4, $1, $3 }' file > /dev/null  6.34s user 0.05s system 93% cpu 6.855 total

答案 1 :(得分:3)

在OSX Yosemite上

time bash -c 'grep --mmap -E "^hit [[:digit:]]*0 " file | awk '\''{print $4, $1, $3 }'\''' >/dev/null


real    0m5.741s
user    0m6.668s
sys     0m0.112s

答案 2 :(得分:2)

以下是PHP中的等价物:

$ time php -R 'if(preg_match("/hit \d*0 /", $argn)){$f=preg_split("/\s+/", $argn); echo $f[3]." ".$f[0]." ".$f[2];}' < file > /dev/null
real    2m42.407s
user    2m41.934s
sys 0m0.355s

与您的awk相比:

$ time awk -f tst.awk file > /dev/null
real    0m3.271s
user    0m3.165s
sys 0m0.104s

我在PHP中尝试了一种不同的方法,我手动迭代该文件,这使事情变得更快但我仍然没有留下深刻的印象:

tst.php

<?php

$fd=fopen('file', 'r');
while($line = fgets($fd)){
    if(preg_match("/hit \d*0 /", $line)){
        $f=preg_split("/\s+/", $line);
        echo $f[3]." ".$f[0]." ".$f[2]."\n";
    }
}
fclose($fd);

结果:

$ time php  tst.php > /dev/null 
real    0m27.354s
user    0m27.042s
sys 0m0.296s

答案 3 :(得分:2)

  • 第一个想法

    $('#calendar').fullCalendar({
            header: {
                left: 'prev,next today',
                center: 'title',
                right: 'agendaWeek'
            },
            defaultDate: '2015-04-20',
            editable: false,
            eventLimit: true,
            firstDay: 1,
            defaultView: 'agendaWeek',
            timeFormat: 'HH:mm',
            lang: 'de',
            columnFormat: 'dddd D.M.',
            allDaySlot: false,
            slotDuration: '00:30:00', // default is 30 min
            minTime: '12:00:00',
            // maxTime: '23:00:00',
            contentHeight: 600,
            events: [
                {
                    title: '',
                    start: '2014-09-01T18:00:00',
                    end: '2014-09-01T20:00:00',
                    dow: [1], // repeat same weekday
                    rendering: 'background', 
                    color: '#6BA5C2'
                },
                {
                    title: '',
                    start: '2014-09-02T20:00:00',
                    end: '2014-09-02T22:00:00',
                    dow: [2], // repeat same weekday
                    rendering: 'background', 
                    color: '#6BA5C2'
                },
                {
                    title: 'Event',
                    start: '2015-04-24 13:00:00',
                    end: '2015-04-24 14:00:00',
                    color: '#F77'
                },
            ],
            // * no callback
            viewDisplay: function (element) {
                alert(element);
            },
    
            // adding an event
            selectable: true,
            selectHelper: true,
            select: function(start, end) {
                var title = prompt('Deine E-Mail:');
                var eventData;
                if(title) {
                    eventData = {
                        title: title,
                        start: start,
                        end: end,
                        color: '#00AA00',
                        editable: true,
                        eventDurationEditable: true,
                        // * no callback
                        eventResizeStop: function(event, jsEvent, ui, view) {
                            alert('end');
                        },
                        // * no callback
                        eventClick: function(calEvent, jsEvent, view) {
                            alert('clicked');
                        },
                        // * no callback
                        eventDrop: function(event, delta, revertFunc, jsEvent, ui, view) {
                            alert('dropped');
                        },
                        // * no callback
                        eventResize: function(event, delta, revertFunc, jsEvent, ui, view) {
                            alert(event.title + ' end is now ' + event.end.format());
                        },
                    };
                    $('#calendar').fullCalendar('renderEvent', eventData, true); // stick = true
                    $('#calendar').fullCalendar('refetchEvents');
                    // not helping either
                    $('#calendar').fullCalendar('rerenderEvents');
                }
                $('#calendar').fullCalendar('unselect');
            },
    
        });
    
  • 第二个想法

    File.open("file").readlines.each do |line|
      line.gsub(/(hit)\s[0-9]*0\s+(.*?)\s+(.*)/) { puts "#{$3} #{$1} #{$2}" }
    end
    

尝试获得能够比较答案的内容我最终创建了一个github repo here。每次推送到此repo都会触发travis-ci上的构建,该构建将构成一个markdown文件,该文件依次推送到gh-pages分支,以更新web page以及构建结果的视图。

任何希望参与的人都可以分享github仓库,添加测试并执行pull request,如果它不会破坏其他测试,我会尽快合并。

答案 4 :(得分:1)

mawk略快于gawk

$ time bash -c 'mawk '\''/hit [[:digit:]]*0 / { print $4, $1, $3 }'\'' file | wc -l'
0

real    0m1.160s
user    0m0.484s
sys     0m0.052s

$ time bash -c 'gawk '\''/hit [[:digit:]]*0 / { print $4, $1, $3 }'\'' file | wc -l'
100000

real    0m1.648s
user    0m0.996s
sys     0m0.060s

(我的输入文件中只有1,000,000行。许多显示的最佳结果,尽管它们非常一致。)