2017-02-26 29 views
2

當我對一個修改其輸入的函數進行基準測試時,我必須複製每個基準循環的測試數據,並在我這樣做時暫停計時器。這可能意味着如果我運行go test -bench MyTest -benchtime 1s測試可能需要2分鐘而不是1秒。如何基準修改其輸入的函數?

我做錯了什麼,或者我只能忍受嗎?


更多背景:

我正在寫一個程序讀取的syslog日誌。我的日誌記錄範例的一部分是記錄消息的第一行包含可讀文本,並且以下行包含「額外信息」,如堆棧跟蹤。因此,我的日誌閱讀器(除其他外)在第一個換行符上拆分了消息,rsyslog將其跳轉到#012

下面是該代碼:

// Splits the main line from extra information 
func splitMessageExtra(line *string) string { 
    var prev rune 

    for i, char := range *line { 
     if prev == 0 && char == '#' { 
      prev = char 
      continue 
     } 

     if prev == '#' && char == '0' { 
      prev = char 
      continue 
     } 

     if prev == '0' && char == '1' { 
      prev = char 
      continue 
     } 

     if prev == '1' && char == '2' { 
      extra := (*line)[i+1:] 
      *line = (*line)[0 : i-3] 

      return extra 
     } 

     prev = 0 
    } 

    return "" 
} 

它最初使用strings.Split並返回新的字符串,但CPU性能分析表明,它是太緩慢。

這裏是基準功能:

var testMessage = `Feb 10 15:16:20 foo_stats[-] (warning): [foo_stats.postfix, line 166, thread "processor_mta03"]: Skipped line because there is no context:#012Feb 10 15:16:20 mta03 postfix/qmgr[7419]: ABCDEF123: from=<>, size=24431, nrcpt=1 (queue active)` 

func BenchmarkSplitMessageExtra(b *testing.B) { 
    for i := 0; i < b.N; i++ { 
     b.StopTimer() 
     msg := string([]byte(testMessage)) 
     b.StartTimer() 

     splitMessageExtra(&msg) 
    } 
} 

這裏有一個跑步無需暫停定時器:

 
$ go test -bench SplitMessageExtra -benchtime 1s 
BenchmarkSplitMessageExtra-8  3000000   434 ns/op 
PASS 
ok  github.com/Hubro/logreader 1.730s 

下面是與上面的確切的基準功能的運行:

 
$ go test -bench SplitMessageExtra -benchtime 1s 
BenchmarkSplitMessageExtra-8  5000000   385 ns/op 
PASS 
ok  github.com/Hubro/logreader 100.563s 

注意它需要AGES運行。

回答

0

沒有什麼不對。 StopTimerStartTimer應該比splitMessageExtra貴得多。他們都稱爲runtime.ReadMemStats。見here

+0

你會如何解決它?該函數旨在修改輸入,因此在基準中包含分配內存是錯誤的。應該有一種方法來暫停計時器,而不需要大量的大量開銷。 – Hubro

+0

嘗試使用更復雜和更長的'testMessage'。也許這就是你想要的。 – cshu

+0

這不會有什麼區別,目前該函數每秒運行約1000萬次... – Hubro

1

您的代碼和基準確實看起來很慢。這是一個更快的版本。

package main 

import (
    "strings" 
    "testing" 
) 

// Splits the main line from extra information 
func splitMessageExtra(line *string) string { 
    const newline = "#012" 
    i := strings.Index(*line, newline) 
    if i < 0 { 
     return "" 
    } 
    extra := (*line)[i+len(newline):] 
    *line = (*line)[0:i] 
    return extra 
} 

var testMessage = `Feb 10 15:16:20 foo_stats[-] (warning): [foo_stats.postfix, line 166, thread "processor_mta03"]: Skipped line because there is no context:#012Feb 10 15:16:20 mta03 postfix/qmgr[7419]: ABCDEF123: from=<>, size=24431, nrcpt=1 (queue active)` 

func BenchmarkSplitMessageExtra(b *testing.B) { 
    for i := 0; i < b.N; i++ { 
     msg := testMessage 
     splitMessageExtra(&msg) 
    } 
} 

輸出:

$ go test -bench=. 
goos: linux 
goarch: amd64 
pkg: extra 
BenchmarkSplitMessageExtra-4 50000000   32.2 ns/op 
PASS 
ok  extra 1.647s 

爲了便於比較,這裏有從您的代碼和基準測試的結果。你的代碼和基準比我的要慢:968 ns/op和50.184s,而32.2 ns/op和1.647s。

package main 

import (
    "testing" 
) 

// Splits the main line from extra information 
func splitMessageExtra(line *string) string { 
    var prev rune 
    for i, char := range *line { 
     if prev == 0 && char == '#' { 
      prev = char 
      continue 
     } 
     if prev == '#' && char == '0' { 
      prev = char 
      continue 
     } 
     if prev == '0' && char == '1' { 
      prev = char 
      continue 
     } 
     if prev == '1' && char == '2' { 
      extra := (*line)[i+1:] 
      *line = (*line)[0 : i-3] 

      return extra 
     } 
     prev = 0 
    } 
    return "" 
} 

var testMessage = `Feb 10 15:16:20 foo_stats[-] (warning): [foo_stats.postfix, line 166, thread "processor_mta03"]: Skipped line because there is no context:#012Feb 10 15:16:20 mta03 postfix/qmgr[7419]: ABCDEF123: from=<>, size=24431, nrcpt=1 (queue active)` 

func BenchmarkSplitMessageExtra(b *testing.B) { 
    for i := 0; i < b.N; i++ { 
     b.StopTimer() 
     msg := string([]byte(testMessage)) 
     b.StartTimer() 
     splitMessageExtra(&msg) 
    } 
} 

輸出:

$ go test -bench=. 
goos: linux 
goarch: amd64 
pkg: extra 
BenchmarkSplitMessageExtra-4  2000000   968 ns/op  
PASS 
ok  extra 50.184s 

一些你的代碼是不必要的;它使用CPU時間和觸發器分配。例如,將utf-8字節轉換爲符文,for i, char := range *line {},並將string轉換爲[]bytestring,string([]byte(testMessage))。一些算法可以改進。例如,搜索換行符。

+0

我可能誤解了你的基準測試,但是你在相同的輸入上反覆運行該功能。這是否意味着函數的第一個調用會拆分字符串,並且每個後續調用都會快速檢查是否沒有換行並返回空字符串?我在測試文件中做了string([] byte(testMessage))'的原因是爲了深度拷貝字符串,這是必要的,因爲函數修改了它的輸入。 – Hubro

+0

好的,我明白了。我對'go'中的字符串有一些誤解。 – Hubro

相關問題