2014-11-03 57 views
9

我写了一段代码来说明Go中的标准命令grep,但速度远远落后于 ,有人可以给我任何进展吗?这里是代码:Go可以更有效吗?

package main 

import (
    "bufio" 
    "fmt" 
    "log" 
    "os" 
    "strings" 
    "sync" 
) 

func parse_args() (file, pat string) { 
    if len(os.Args) < 3 { 
     log.Fatal("usage: gorep2 <file_name> <pattern>") 
    } 

    file = os.Args[1] 
    pat = os.Args[2] 
    return 
} 

func readFile(file string, to chan<- string) { 
    f, err := os.Open(file) 
    if err != nil { 
     log.Fatal(err) 
    } 
    defer f.Close() 

    freader := bufio.NewReader(f) 
    for { 
     line, er := freader.ReadBytes('\n') 
     if er == nil { 
      to <- string(line) 
     } else { 
      break 
     } 

    } 
    close(to) 
} 

func grepLine(pat string, from <-chan string, result chan<- bool) { 
    var wg sync.WaitGroup 

    for line := range from { 
     wg.Add(1) 

     go func(l string) { 
      defer wg.Done() 
      if strings.Contains(l, pat) { 
       result <- true 
      } 
     }(string(line)) 
    } 

    wg.Wait() 
    close(result) 
} 

func main() { 
    file, pat := parse_args() 
    text_chan := make(chan string, 10) 
    result_chan := make(chan bool, 10) 

    go readFile(file, text_chan) 
    go grepLine(pat, text_chan, result_chan) 

    var total uint = 0 
    for r := range result_chan { 
     if r == true { 
      total += 1 
     } 
    } 

    fmt.Printf("Total %d\n", total) 
} 

围棋的time

>>> time gogrep /var/log/task.log DEBUG 

Total 21089 

real 0m0.156s 
user 0m0.156s 
sys 0m0.015s 

timegrep

>>> time grep DEBUG /var/log/task.log | wc -l 

21089 

real 0m0.069s 
user 0m0.046s 
sys 0m0.064s 
+0

参考上面的'time',Go在'sys'中获胜,但在'user'部分丢失,这意味着我的代码吃了这个时间? – askingyj 2014-11-03 08:56:18

+1

看来,每行一个去例行程序(请参阅'grepLine')可能是您可以删除的开销。在每次执行例程时投掷多行代码意味着创建执行例程的时间更少,并且执行文本搜索的时间更多。如果您还没有推荐Rob Pikes http://talks.golang.org/2012/concurrency.slide#1 – miltonb 2014-11-03 09:32:10

+1

您正在多次阅读每一行:在bufio中扫描\ n,然后将这些字节复制到一个字符串中,然后扫描DEBUG。只有后者在您的代码中并行执行。我希望grep能够一次完成所有的事情。 – 2014-11-03 09:40:07

回答

13

对于易于重现的基准,我计算了莎士比亚文本“和”的出现次数。

 
gogrep: 

$ go build gogrep.go && time ./gogrep /home/peter/shakespeare.txt and 
Total 21851 
real 0m0.613s 
user 0m0.651s 
sys 0m0.068s 

grep: 

$ time grep and /home/peter/shakespeare.txt | wc -l 
21851 
real 0m0.108s 
user 0m0.107s 
sys 0m0.014s 

petergrep: 

$ go build petergrep.go && time ./petergrep /home/peter/shakespeare.txt and 
Total 21851 
real 0m0.098s 
user 0m0.092s 
sys 0m0.008s 

petergrep是用Go编写的。它很快。

package main 

import (
    "bufio" 
    "bytes" 
    "fmt" 
    "log" 
    "os" 
) 

func parse_args() (file, pat string) { 
    if len(os.Args) < 3 { 
     log.Fatal("usage: petergrep <file_name> <pattern>") 
    } 
    file = os.Args[1] 
    pat = os.Args[2] 
    return 
} 

func grepFile(file string, pat []byte) int64 { 
    patCount := int64(0) 
    f, err := os.Open(file) 
    if err != nil { 
     log.Fatal(err) 
    } 
    defer f.Close() 
    scanner := bufio.NewScanner(f) 
    for scanner.Scan() { 
     if bytes.Contains(scanner.Bytes(), pat) { 
      patCount++ 
     } 
    } 
    if err := scanner.Err(); err != nil { 
     fmt.Fprintln(os.Stderr, err) 
    } 
    return patCount 
} 

func main() { 
    file, pat := parse_args() 
    total := grepFile(file, []byte(pat)) 
    fmt.Printf("Total %d\n", total) 
} 

数据:Shakespeare: pg100.txt

+0

这是一个有趣的基准,但不是严格有效的 - 你已经实现了'fgrep',没有正则表达式。将'petergrep'与'grep'进行比较是不公平的。 – 2014-11-03 23:43:51

+0

......但你*已*很好地回答了这个问题。 – 2014-11-03 23:54:38

4

转到正则表达式是完全UTF-8,我认为有一些开销。它们也有a different theoretical basis这意味着它们将始终以与输入长度成比例的时间运行。值得注意的是,Go regexps的速度并不像其他语言所使用的pcre regexp那么快。如果你看看benchmarks game shootouts for the regexp test,你会明白我的意思。

如果你想要更快的速度,你总是可以使用pcre library directly

+0

该问题不使用Go正则表达式。 – peterSO 2014-11-03 13:42:18

0

的数据点在UTF-8的正则表达式解析的相关性:我已经为源grepping长期使用的自定义脚本的perl5。我最近修改它以支持UTF-8,因此它可以匹配花式的golang符号名称。它在反复测试中运行缓慢。因此,尽管golang regexp确实为其运行时的可预测性付出代价,但我们还必须将UTF-8处理因素纳入等式。

+2

看起来这个答案可能更适合作为评论发布。 – category 2017-10-30 18:46:12

相关问题