2014-11-03 6 views
9

Ich schrieb ein Stück Code, um den Standardbefehl grep in Go zu veranschaulichen, aber die Geschwindigkeit ist weit dahinter, konnte mir jemand irgendwelche Fortschritte geben? hier ist der Code:Könnte das in Go effizienter sein?

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) 
} 

Die time in Go:

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

Total 21089 

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

Die time in grep:

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

21089 

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

beziehen sich auf die "Zeit" oben, Go gewinnt in 'sys', aber verlieren in den' user' Teil, Dosis, die meine Code essen die Zeit bedeuten? – askingyj

+1

Es scheint, dass eine Go-Routine pro Zeile (siehe "grepLine") ein Overhead sein kann, den Sie entfernen können. Mehrere Zeilen bei jeder Routine zu werfen, würde weniger Zeit für die Erstellung von Routinen und mehr Zeit für Textsuchen bedeuten. Wenn Sie nicht bereits Ich empfehle Rob Pikes http://talks.golang.org/2012/concurrency.slide#1 – miltonb

+1

Sie lesen jede Zeile mehrmals: Scannen für \ n in Bufio, dann kopieren Sie die Bytes in eine Zeichenfolge , dann nach DEBUG scannen. Nur letzteres wird in Ihrem Code parallel ausgeführt. Ich würde erwarten, Grep macht alles in einem einzigen Durchgang. –

Antwort

13

Für einen leicht reproduzierbaren Benchmark habe ich die Anzahl der Vorkommen des Textes "and" in Shakespeare gezählt.

 
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 ist in Go geschrieben. Es ist schnell.

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) 
} 

Daten: Shakespeare: pg100.txt

+0

Dies ist ein interessanter Benchmark, aber nicht streng gültig - Sie haben 'fgrep' implementiert, ohne reguläre Ausdrücke. Es ist nicht fair, "Petergrep" mit "Grep" zu vergleichen. –

+0

... aber Sie * haben * die Frage gut beantwortet. –

4

gehen reguläre Ausdrücke sind voll utf-8 und ich denke, hat etwas Overhead. Sie haben auch a different theoretical basis, was bedeutet, dass sie immer in einer Zeit laufen, die proportional zur Länge der Eingabe ist. Es fällt auf, dass Go regexps nicht so schnell sind wie pcre regexp in anderen Sprachen. Wenn Sie sich die benchmarks game shootouts for the regexp test ansehen, werden Sie sehen, was ich meine.

Sie können immer die pcre library directly verwenden, wenn Sie ein bisschen mehr Geschwindigkeit möchten.

+0

Die Frage verwendet keine regulären Go-Ausdrücke. – peterSO

0

Ein Datenpunkt über die Relevanz von UTF-8 in regexp-Analyse: Ich habe einen lange verwendet, um benutzerdefinierten perl5 Skript für Quelle Grepping. Ich habe es vor Kurzem modifiziert, um UTF-8 zu unterstützen, damit es die Namen von Golong-Symbolen erkennen kann. Es führte in wiederholten Tests eine VOLLE ORDNUNG DER MAGNITUDE langsamer durch. Während Goleg Regexps einen Preis für die Vorhersagbarkeit ihrer Laufzeit zahlen müssen, müssen wir auch die UTF-8-Behandlung in die Gleichung einbeziehen.

+2

Es scheint, dass diese Antwort besser als Kommentar gepostet werden könnte. – category

Verwandte Themen