Go 中的條件及其怪癖

您認為這兩種在循環內測試條件的選項在效能上是否等效?

		
if a > b && c*2 > d {
	....
}
// и
if a <= b  { 
  continue;
}
if c*2 > d {
 ....
}


這一切都從「大腦熱身」開始;我必須給出一個在整數數組 [-x....x] 中最佳搜尋最大偶數的例子。 我想知道如果我使用邏輯乘以 1 來計算一個數字是否為偶數,性能會提高多少。


//у четных чисел последний бит всегда равен 0
value & 1 == 0
//vs классический метод
value % 2 == 0

我在Go 方面的程式設計經驗不是很豐富,只有一年半多,我雖然經常使用它,但純粹是出於功利目的(好吧,也許除了一個與高負載http 服務相關的專案之外),所以我從它開始。 打開GoLand並編寫一個簡單的測試


package main
import (
	"fmt"
	"log"
	"math"
	"math/rand"
	"time"
)
const size = 100000000 //math.MaxInt32*2
type Result struct {
	Name     string
	Duration time.Duration
	Value    int32
}

func main() {
	log.Println("initial array capacity: " + fmt.Sprint(size))
	var maxValue int32
        // Будем варьировать диапазон чисел от минимального 
        // до максимального. Чем меньше диапазон, тем больше 
        // процессорного времени будет уходить на операцию 
        // сравнения текущего числа, с ранее найденным и наоборот
	for maxValue = 128; maxValue < math.MaxInt32/2+1; maxValue = maxValue * 2 {
		test(maxValue)
	}
}

func test(maxValue int32) {
	log.Println("max threshold: " + fmt.Sprint(maxValue))
	arr := make([]int32, size)
	for i := range arr {
		arr[i] = rand.Int31n(maxValue)
                // в тестовых данных нам нужны и отрицательные числа 
		sign := rand.Intn(2)
		if sign == 1 {
			arr[i] = -arr[i]
		}
	}

        // запускаем тест "деление с остатком"
	result := maxEvenDividing("maxEvenDividing", arr)
	log.Printf(result.Name+"t result: "+fmt.Sprint(result.Value)+"ttduration %s", result.Duration)

        // запускаем тест "конъюнкции"
	result = maxEvenConjunction("maxEvenConjunction", arr)
	log.Printf(result.Name+"t result: "+fmt.Sprint(result.Value)+"ttduration %s", result.Duration)
}

func maxEvenDividing(name string, arr []int32) Result {
	start := time.Now()
	var current int32 = math.MinInt32
	for _, value := range arr {
		if value > current && value%2 == 0 {
			current = value
		}
	}
	duration := time.Since(start)
	result := Result{name, duration, current}
	return result
}

func maxEvenConjunction(name string, arr []int32) Result {
	start := time.Now()
	var current int32 = math.MinInt32
	for _, value := range arr {
		if value > current && value&1 == 0 {
			current = value
		}
	}
	duration := time.Since(start)
	result := Result{name, duration, current}
	return result
}

我們得到的結果表明,閾值越高,表現波動就越頻繁地出現。

比較max threshold: 128
maxEvenDividing result: 126 duration 116.0067ms
maxEvenConjunction result: 126 duration 116.0066ms

max threshold: 16384
maxEvenDividing result: 16382 duration 115.0066ms
maxEvenConjunction result: 16382 duration 111.0064ms

......

max threshold: 8388608
maxEvenDividing result: 8388606 duration 109.0063ms
maxEvenConjunction result: 8388606 duration 109.0062ms

max threshold: 16777216
maxEvenDividing result: 16777214 duration 108.0062ms
maxEvenConjunction result: 16777214 duration 109.0062ms

max threshold: 33554432
maxEvenDividing result: 33554430 duration 114.0066ms
maxEvenConjunction result: 33554430 duration 110.0063ms

max threshold: 67108864
maxEvenDividing result: 67108860 duration 111.0064ms
maxEvenConjunction result: 67108860 duration 109.0062ms

max threshold: 134217728
maxEvenDividing result: 134217726 duration 108.0062ms
maxEvenConjunction result: 134217726 duration 109.0063ms

max threshold: 268435456
maxEvenDividing result: 268435446 duration 111.0063ms
maxEvenConjunction result: 268435446 duration 110.0063ms

很明顯,在這種情況下,對於不同的閾值,我們有不同的測試資料集,處理器負載(在我的i5-2540M 筆記型電腦上)變化約為20..30%,從GoLand 運行的應用程式佔用的記憶體平均為大約 813MB - 這也會影響結果的可靠性,您需要將測試案例保存在磁碟上,並彼此隔離地執行每個閾值的所有測試。

現在,考慮如何以最小的成本實現這一切,我自動糾正了條件檢查

		
if value > current && value&1 == 0 {
	current = value
}

		
if value <= current {
        continue;
}
if value&1 == 0 {
	current = value
}

我再次運行測試......我不再理解任何事情:)

執行所花費的時間開始出現差異,不是百分比/百分比的差異,而是 10..15%。我很快又添加了 2 個測試:

		
func maxEvenDividing2(name string, arr []int32) Result {
	start := time.Now()
	var current int32 = math.MinInt32
	for _, value := range arr {
		if value <= current {
			continue
		}

		if value%2 == 0 {
			current = value
		}
	}
	duration := time.Since(start)
	result := Result{name, duration, current}
	return result
}

func maxEvenConjunction2(name string, arr []int32) Result {
	start := time.Now()
	var current int32 = math.MinInt32
	for _, value := range arr {
		if value <= current {
			continue
		}
		if value&1 == 0 {
			current = value
		}
	}
	duration := time.Since(start)
	result := Result{name, duration, current}
	return result
}

我運行它並得到這張圖片:初始數組容量:100000000

最大閾值:128
maxEvenDividing 結果:126 持續時間 116.0066ms
maxEvenDividing2 結果:126 持續時間 79.0045ms
maxEvenConjunction 結果:126 長度 114.0065ms
maxEvenConjunction2 結果:126 長度 83.0048ms

最大閾值:256
maxEvenDividing 結果:254 持續時間 111.0063ms
maxEvenDividing2 結果:254 持續時間 77.0044ms
maxEvenConjunction 結果:254 長度 110.0063ms
maxEvenConjunction2 結果:254 長度 80.0046ms

最大閾值:512
maxEvenDividing 結果:510 持續時間 114.0066ms
maxEvenDividing2 結果:510 持續時間 80.0045ms
maxEvenConjunction 結果:510 長度 110.0063ms
maxEvenConjunction2 結果:510 長度 80.0046ms

最大閾值:1024
maxEvenDividing 結果:1022 持續時間 109.0063ms
maxEvenDividing2 結果:1022 持續時間 77.0044ms
maxEvenConjunction 結果:1022 長度 111.0063ms
maxEvenConjunction2 結果:1022 長度 81.0047ms

最大閾值:2048
maxEvenDividing 結果:2046 持續時間 114.0065ms
maxEvenDividing2 結果:2046 持續時間 79.0045ms
maxEvenConjunction 結果:2046 長度 113.0065ms
maxEvenConjunction2 結果:2046 長度 81.0046ms

最大閾值:4096
maxEvenDividing 結果:4094 持續時間 114.0065ms
maxEvenDividing2 結果:4094 持續時間 80.0046ms
maxEvenConjunction 結果:4094 長度 111.0063ms
maxEvenConjunction2 結果:4094 長度 78.0045ms

最大閾值:8192
maxEvenDividing 結果:8190 持續時間 107.0062ms
maxEvenDividing2 結果:8190 持續時間 77.0044ms
maxEvenConjunction 結果:8190 長度 111.0063ms
maxEvenConjunction2 結果:8190 長度 77.0044ms

最大閾值:16384
maxEvenDividing 結果:16382 持續時間 109.0063ms
maxEvenDividing2 結果:16382 持續時間 77.0044ms
maxEvenConjunction 結果:16382 長度 108.0062ms
maxEvenConjunction2 結果:16382 長度 77.0044ms

最大閾值:32768
maxEvenDividing 結果:32766 持續時間 112.0064ms
maxEvenDividing2 結果:32766 持續時間 77.0044ms
maxEvenConjunction 結果:32766 長度 109.0062ms
maxEvenConjunction2 結果:32766 長度 78.0045ms

最大閾值:65536
maxEvenDividing 結果:65534 持續時間 109.0062ms
maxEvenDividing2 結果:65534 持續時間 75.0043ms
maxEvenConjunction 結果:65534 長度 109.0063ms
maxEvenConjunction2 結果:65534 長度 79.0045ms

最大閾值:131072
maxEvenDividing 結果:131070 持續時間 108.0061ms
maxEvenDividing2 結果:131070 持續時間 76.0044ms
maxEvenConjunction 結果:131070 長度 110.0063ms
maxEvenConjunction2 結果:131070 長度 80.0046ms

最大閾值:262144
maxEvenDividing 結果:262142 持續時間 110.0063ms
maxEvenDividing2 結果:262142 持續時間 76.0044ms
maxEvenConjunction 結果:262142 長度 107.0061ms
maxEvenConjunction2 結果:262142 長度 78.0044ms

最大閾值:524288
maxEvenDividing 結果:524286 持續時間 109.0062ms
maxEvenDividing2 結果:524286 持續時間 78.0045ms
maxEvenConjunction 結果:524286 長度 109.0062ms
maxEvenConjunction2 結果:524286 長度 80.0046ms

最大閾值:1048576
maxEvenDividing 結果:1048574 持續時間 109.0063ms
maxEvenDividing2 結果:1048574 持續時間 80.0045ms
maxEvenConjunction 結果:1048574 長度 114.0066ms
maxEvenConjunction2 結果:1048574 長度 78.0044ms

最大閾值:2097152
maxEvenDividing 結果:2097150 持續時間 111.0064ms
maxEvenDividing2 結果:2097150 持續時間 79.0045ms
maxEvenConjunction 結果:2097150 長度 112.0064ms
maxEvenConjunction2 結果:2097150 長度 77.0044ms

最大閾值:4194304
maxEvenDividing 結果:4194302 持續時間 111.0063ms
maxEvenDividing2 結果:4194302 持續時間 78.0045ms
maxEvenConjunction 結果:4194302 長度 111.0063ms
maxEvenConjunction2 結果:4194302 長度 77.0044ms

最大閾值:8388608
maxEvenDividing 結果:8388606 持續時間 109.0062ms
maxEvenDividing2 結果:8388606 持續時間 78.0045ms
maxEvenConjunction 結果:8388606 長度 114.0065ms
maxEvenConjunction2 結果:8388606 長度 78.0045ms

最大閾值:16777216
maxEvenDividing 結果:16777214 持續時間 109.0062ms
maxEvenDividing2 結果:16777214 持續時間 77.0044ms
maxEvenConjunction 結果:16777214 長度 109.0063ms
maxEvenConjunction2 結果:16777214 長度 77.0044ms

最大閾值:33554432
maxEvenDividing 結果:33554430 持續時間 113.0065ms
maxEvenDividing2 結果:33554430 持續時間 78.0045ms
maxEvenConjunction 結果:33554430 長度 110.0063ms
maxEvenConjunction2 結果:33554430 長度 80.0045ms

最大閾值:67108864
maxEvenDividing 結果:67108860 持續時間 112.0064ms
maxEvenDividing2 結果:67108860 持續時間 77.0044ms
maxEvenConjunction 結果:67108860 長度 112.0064ms
maxEvenConjunction2 結果:67108860 長度 80.0046ms

最大閾值:134217728
maxEvenDividing 結果:134217726 持續時間 109.0063ms
maxEvenDividing2 結果:134217726 持續時間 78.0044ms
maxEvenConjunction 結果:134217726 長度 114.0065ms
maxEvenConjunction2 結果:134217726 長度 81.0047ms

最大閾值:268435456
maxEvenDividing 結果:268435446 持續時間 111.0064ms
maxEvenDividing2 結果:268435446 持續時間 79.0045ms
maxEvenConjunction 結果:268435446 長度 114.0065ms
maxEvenConjunction2 結果:268435446 長度 79.0045ms

最大閾值:536870912
maxEvenDividing 結果:536870910 持續時間 107.0062ms
maxEvenDividing2 結果:536870910 持續時間 76.0043ms
maxEvenConjunction 結果:536870910 長度 109.0062ms
maxEvenConjunction2 結果:536870910 長度 80.0046ms

我找不到明確的解釋為什麼 Go 編譯器不最佳化程式碼並且總是檢查第二個條件,即使第一個條件為 false。 或者也許我的眼睛很模糊,我沒有看到任何明顯的錯誤? 或是需要給編譯器一些特殊的指令? 我很高興收到明智的評論。

注: 是的,只是為了好玩,我在 Java 5 和 Java 7/8 上運行了類似的測試 - 一切都很清楚,執行時間是相同的。

來源: www.habr.com

添加評論