2017-07-17 88 views
-1

對於下列功能:性能降低與函數調用

func CycleClock(c *ballclock.Clock) int { 
    for i := 0; i < fiveMinutesPerDay; i++ { 
     c.TickFive() 
    } 

    return 1 + CalculateBallCycle(append([]int{}, c.BallQueue...)) 
} 

其中c.BallQueue被定義爲[]intCalculateBallCycle被定義爲func CalculateBallCycle(s []int) int。我在for循環和return聲明之間的性能下降很大。

我寫了下面的基準測試。第一基準的整函數,所述第二基準的for循環,而第三基準的CalculateBallCycle功能:

func BenchmarkCycleClock(b *testing.B) { 
    for i := ballclock.MinBalls; i <= ballclock.MaxBalls; i++ { 
     j := i 
     b.Run("BallCount="+strconv.Itoa(i), func(b *testing.B) { 
      for n := 0; n < b.N; n++ { 
       c, _ := ballclock.NewClock(j) 

       CycleClock(c) 
      } 
     }) 
    } 
} 

func BenchmarkCycle24(b *testing.B) { 
    for i := ballclock.MinBalls; i <= ballclock.MaxBalls; i++ { 
     j := i 
     b.Run("BallCount="+strconv.Itoa(i), func(b *testing.B) { 
      for n := 0; n < b.N; n++ { 
       c, _ := ballclock.NewClock(j) 

       for k := 0; k < fiveMinutesPerDay; k++ { 
        c.TickFive() 
       } 
      } 
     }) 
    } 
} 

func BenchmarkCalculateBallCycle123(b *testing.B) { 
    m := []int{8, 62, 42, 87, 108, 35, 17, 6, 22, 75, 116, 112, 39, 119, 52, 60, 30, 88, 56, 36, 38, 26, 51, 31, 55, 120, 33, 99, 111, 24, 45, 21, 23, 34, 43, 41, 67, 65, 66, 85, 82, 89, 9, 25, 109, 47, 40, 0, 83, 46, 73, 13, 12, 63, 15, 90, 121, 2, 69, 53, 28, 72, 97, 3, 4, 94, 106, 61, 96, 18, 80, 74, 44, 84, 107, 98, 93, 103, 5, 91, 32, 76, 20, 68, 81, 95, 29, 27, 86, 104, 7, 64, 113, 78, 105, 58, 118, 117, 50, 70, 10, 101, 110, 19, 1, 115, 102, 71, 79, 57, 77, 122, 48, 114, 54, 37, 59, 49, 100, 11, 14, 92, 16} 

    for n := 0; n < b.N; n++ { 
     CalculateBallCycle(m) 
    } 
} 

使用123個球,這給出以下結果:

BenchmarkCycleClock/BallCount=123-8     200   9254136 ns/op 
BenchmarkCycle24/BallCount=123-8     200000    7610 ns/op 
BenchmarkCalculateBallCycle123-8     3000000    456 ns/op 

尋找這個,基準之間存在巨大差異。我預計第一個基準大約需要~8000 ns/op,因爲這將是各部分的總和。

Here是github存儲庫。

編輯:

我發現,從基準,從運行的程序,結果的結果差異很大。我採取了什麼@yazgazan發現並main.go模擬修改了基準功能多少有些BenchmarkCalculateBallCycle123main_test.go

func Benchmark() { 
    for i := ballclock.MinBalls; i <= ballclock.MaxBalls; i++ { 
     if i != 123 { 
      continue 
     } 

     start := time.Now() 

     t := CalculateBallCycle([]int{8, 62, 42, 87, 108, 35, 17, 6, 22, 75, 116, 112, 39, 119, 52, 60, 30, 88, 56, 36, 38, 26, 51, 31, 55, 120, 33, 99, 111, 24, 45, 21, 23, 34, 43, 41, 67, 65, 66, 85, 82, 89, 9, 25, 109, 47, 40, 0, 83, 46, 73, 13, 12, 63, 15, 90, 121, 2, 69, 53, 28, 72, 97, 3, 4, 94, 106, 61, 96, 18, 80, 74, 44, 84, 107, 98, 93, 103, 5, 91, 32, 76, 20, 68, 81, 95, 29, 27, 86, 104, 7, 64, 113, 78, 105, 58, 118, 117, 50, 70, 10, 101, 110, 19, 1, 115, 102, 71, 79, 57, 77, 122, 48, 114, 54, 37, 59, 49, 100, 11, 14, 92, 16}) 

     duration := time.Since(start) 

     fmt.Printf("Ballclock with %v balls took %s;\n", i, duration) 
    } 
} 

這給了輸出:

Ballclock with 123 balls took 11.86748ms; 

正如你所看到的,總時間爲11.86 ms,所有這些都花在CalculateBallCycle函數中。當運行的程序運行在11867480 ms/op附近時,什麼會導致基準運行在456 ns/op

+1

前兩個測試功能有嵌套的for循環,而最後一個不... –

+0

@ dev.bmax這不是一個問題。基準測試在'b.Run()'調用中完成。 – JRLambert

+0

@JRLambert你可以做一個能夠重現這個問題的主旨嗎?或者告訴我們'TickFive'中發生了什麼事情,以便我們可以嘗試並重現它? – yazgazan

回答

2

你寫CalcualteBallCycle()修改設計切片。

我不能到這種做法的正確性說話,但它爲什麼BenchmarkCalculateBallCycle123基準時間是如此不同。

在第一次運行它意料之中的事情,但在隨後的運行中會產生完全不同的,因爲你傳遞不同的數據作爲輸入。

基準此修改後的代碼:

func BenchmarkCalculateBallCycle123v2(b *testing.B) { 
    m := []int{8, 62, 42, 87, 108, 35, 17, 6, 22, 75, 116, 112, 39, 119, 52, 60, 30, 88, 56, 36, 38, 26, 51, 31, 55, 120, 33, 99, 111, 24, 45, 21, 23, 34, 43, 41, 67, 65, 66, 85, 82, 89, 9, 25, 109, 47, 40, 0, 83, 46, 73, 13, 12, 63, 15, 90, 121, 2, 69, 53, 28, 72, 97, 3, 4, 94, 106, 61, 96, 18, 80, 74, 44, 84, 107, 98, 93, 103, 5, 91, 32, 76, 20, 68, 81, 95, 29, 27, 86, 104, 7, 64, 113, 78, 105, 58, 118, 117, 50, 70, 10, 101, 110, 19, 1, 115, 102, 71, 79, 57, 77, 122, 48, 114, 54, 37, 59, 49, 100, 11, 14, 92, 16} 
    for n := 0; n < b.N; n++ { 
     tmp := append([]int{}, m...) 
     CalculateBallCycle(tmp) 
    } 
} 

這工作,解決此問題通過使m的副本,讓CalculateBallCycle修改一個本地副本。

運行時間變得更像人:

BenchmarkCalculateBallCycle123-8   3000000   500 ns/op 
BenchmarkCalculateBallCycle123v2-8   100  10483347 ns/op 
+0

*臉部的Palm *,謝謝。我應該意識到這一切正在發生。 – JRLambert

0

在您的CycleClock功能中,您正在複製c.BallQueue切片。您可以顯著使用CalculateBallCycle(c.BallQueue)代替(假設CalculateBallCycle不修改切片)

例如提高性能:

func Sum(values []int) int { 
    sum := 0 
    for _, v := range values { 
     sum += v 
    } 

    return sum 
} 

func BenchmarkNoCopy(b *testing.B) { 
    for n := 0; n < b.N; n++ { 
     Sum(m) 
    } 
} 

func BenchmarkWithCopy(b *testing.B) { 
    for n := 0; n < b.N; n++ { 
     Sum(append([]int{}, m...)) 
    } 
} 

// BenchmarkNoCopy-4  20000000   73.5 ns/op 
// BenchmarkWithCopy-4  5000000   306 ns/op 
// PASS 
+0

我做了改變,並沒有提高,但還沒到那個我希望的程度。隨着變化,現在的基準標記是'8930347 ns/op',仍然比我所希望的大約高出1000倍。 – JRLambert

+0

此外,'CalculateBallCycle'將修改切片,但這是預期和想要的副作用。 – JRLambert

0

有在測試中一個微妙的錯誤。

兩種方法BenchmarkCycleClockBenchmarkCycle24在for循環中運行基準,將封閉傳遞給b.Run。在這些關閉的內部,您使用如下這樣的循環變量i來初始化時鐘:ballclock.NewClock(i)

問題是,您的匿名函數的所有實例共享相同的變量。並且,當測試運行器運行該函數時,循環將結束,所有時鐘將使用相同的值進行初始化:ballclock.MaxBalls

這可以使用一個局部變量修正:

for i := ballclock.MinBalls; i <= ballclock.MaxBalls; i++ { 
    i := i 
    b.Run("BallCount="+strconv.Itoa(i), func(b *testing.B) { 
     for n := 0; n < b.N; n++ { 
      c, _ := ballclock.NewClock(i) 

      CycleClock(c) 
     } 
    }) 
} 

i := ii的電流值(你的匿名函數的每個實例不同)的副本。

+0

感謝您指出這一點,但這只是在使用'testing.T'並在提供的'func'中調用't.Parallel()'時的一個問題。 'b.Run()'按順序運行,所以傳遞給'b.Run()'的'func'中的'i'的值是正確的值。將基準更改爲使用局部變量不會影響基準的結果。 [文件](https://golang.org/pkg/testing/#hdr-Subtests_and_Sub_benchmarks) – JRLambert