2014-11-03 13 views
9

Ho scritto un pezzo di codice per illustrare il comando standard grep in Go, ma la velocità è gran lunga alle spalle, qualcuno potrebbe darmi eventuali anticipi? ecco il codice:Potrebbe essere più efficiente in 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) 
} 

Il time in Go:

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

Total 21089 

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

Il time in grep:

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

21089 

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

si riferisce al 'tempo' precedente, Go vince in' sys', ma perde nella parte 'utente', dose che significa che il mio codice mangia il tempo? – askingyj

+1

Sembra che una routine di go per riga (si veda 'grepLine') potrebbe essere un overhead che è possibile rimuovere. Lanciare più linee a ogni routine di routine significherebbe meno tempo a creare routine di go e più tempo a fare ricerche di testo. Se non lo hai già io consiglio Rob Pikes http://talks.golang.org/2012/concurrency.slide#1 – miltonb

+1

Stai leggendo ogni riga più volte: scansione per \ n in bufio, quindi copia i byte in una stringa , quindi eseguire la scansione di DEBUG. Solo quest'ultimo viene eseguito in parallelo nel tuo codice. Mi aspetto che Grep faccia tutto in un solo passaggio. –

risposta

13

Per un punto di riferimento facilmente riproducibile, ho contato il numero di occorrenze del testo "e" in Shakespeare.

 
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 è scritto in Go. È veloce.

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

dati: Shakespeare: pg100.txt

+0

Questo è un benchmark interessante ma non strettamente valido - hai implementato 'fgrep', senza espressioni regolari. Non è giusto confrontare 'petergrep' contro' grep'. –

+0

... ma tu * hai * risposto bene alla domanda. –

4

Vai espressioni regolari sono completamente utf-8 e penso che ha qualche sovraccarico. Hanno anche a different theoretical basis che significa che correranno sempre in un tempo proporzionale alla lunghezza dell'input. È evidente che le espressioni regolari di Go non sono così veloci come le espressioni regolari di pcre utilizzate da altri linguaggi. Se guardi il benchmarks game shootouts for the regexp test vedrai cosa intendo.

È sempre possibile utilizzare il pcre library directly se volete un po 'più di velocità però.

+0

La domanda non usa le espressioni regolari Go. – peterSO

0

Un punto dati sulla rilevanza di UTF-8 in regexp parsing: Ho uno script personalizzato perl5 a lungo utilizzato per grep fonte. L'ho modificato di recente per supportare UTF-8 in modo che potesse corrispondere ai nomi dei simboli di fantasia golang. Ha eseguito un PIENO ORDINE DI MAGNITUDINE più lento nei test ripetuti. Così, mentre regexp di golang paga un prezzo per la prevedibilità del suo runtime, dobbiamo anche considerare la gestione UTF-8 nell'equazione.

+2

Sembra che questa risposta potrebbe essere più appropriatamente inserita come commento. – category

Problemi correlati