Go の条件とその癖

ループ内の条件をテストするためのこれら XNUMX つのオプションはパフォーマンスにおいて同等だと思いますか?

		
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 でのプログラミング経験はそれほど広範囲ではなく、XNUMX 年半ほどです。頻繁ではありますが、純粋に実用的な目的で使用していました (高負荷の http サービスに関連する XNUMX つのプロジェクトを除いては)。それから始まりました。 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.0045 ミリ秒
maxEvenConjunction 結果: 126 期間 114.0065 ミリ秒
maxEvenConjunction2 結果: 126 期間 83.0048 ミリ秒

最大しきい値: 256
maxEvenDividing 結果: 254 期間 111.0063ms
maxEvenDividing2 結果: 254 期間 77.0044 ミリ秒
maxEvenConjunction 結果: 254 期間 110.0063 ミリ秒
maxEvenConjunction2 結果: 254 期間 80.0046 ミリ秒

最大しきい値: 512
maxEvenDividing 結果: 510 期間 114.0066ms
maxEvenDividing2 結果: 510 期間 80.0045 ミリ秒
maxEvenConjunction 結果: 510 期間 110.0063 ミリ秒
maxEvenConjunction2 結果: 510 期間 80.0046 ミリ秒

最大しきい値: 1024
maxEvenDividing 結果: 1022 期間 109.0063ms
maxEvenDividing2 結果: 1022 期間 77.0044 ミリ秒
maxEvenConjunction 結果: 1022 期間 111.0063 ミリ秒
maxEvenConjunction2 結果: 1022 期間 81.0047 ミリ秒

最大しきい値: 2048
maxEvenDividing 結果: 2046 期間 114.0065ms
maxEvenDividing2 結果: 2046 期間 79.0045 ミリ秒
maxEvenConjunction 結果: 2046 期間 113.0065 ミリ秒
maxEvenConjunction2 結果: 2046 期間 81.0046 ミリ秒

最大しきい値: 4096
maxEvenDividing 結果: 4094 期間 114.0065ms
maxEvenDividing2 結果: 4094 期間 80.0046 ミリ秒
maxEvenConjunction 結果: 4094 期間 111.0063 ミリ秒
maxEvenConjunction2 結果: 4094 期間 78.0045 ミリ秒

最大しきい値: 8192
maxEvenDividing 結果: 8190 期間 107.0062ms
maxEvenDividing2 結果: 8190 期間 77.0044 ミリ秒
maxEvenConjunction 結果: 8190 期間 111.0063 ミリ秒
maxEvenConjunction2 結果: 8190 期間 77.0044 ミリ秒

最大しきい値: 16384
maxEvenDividing 結果: 16382 期間 109.0063ms
maxEvenDividing2 結果: 16382 期間 77.0044 ミリ秒
maxEvenConjunction 結果: 16382 期間 108.0062 ミリ秒
maxEvenConjunction2 結果: 16382 期間 77.0044 ミリ秒

最大しきい値: 32768
maxEvenDividing 結果: 32766 期間 112.0064ms
maxEvenDividing2 結果: 32766 期間 77.0044 ミリ秒
maxEvenConjunction 結果: 32766 期間 109.0062 ミリ秒
maxEvenConjunction2 結果: 32766 期間 78.0045 ミリ秒

最大しきい値: 65536
maxEvenDividing 結果: 65534 期間 109.0062ms
maxEvenDividing2 結果: 65534 期間 75.0043 ミリ秒
maxEvenConjunction 結果: 65534 期間 109.0063 ミリ秒
maxEvenConjunction2 結果: 65534 期間 79.0045 ミリ秒

最大しきい値: 131072
maxEvenDividing 結果: 131070 期間 108.0061ms
maxEvenDividing2 結果: 131070 期間 76.0044 ミリ秒
maxEvenConjunction 結果: 131070 期間 110.0063 ミリ秒
maxEvenConjunction2 結果: 131070 期間 80.0046 ミリ秒

最大しきい値: 262144
maxEvenDividing 結果: 262142 期間 110.0063ms
maxEvenDividing2 結果: 262142 期間 76.0044 ミリ秒
maxEvenConjunction 結果: 262142 期間 107.0061 ミリ秒
maxEvenConjunction2 結果: 262142 期間 78.0044 ミリ秒

最大しきい値: 524288
maxEvenDividing 結果: 524286 期間 109.0062ms
maxEvenDividing2 結果: 524286 期間 78.0045 ミリ秒
maxEvenConjunction 結果: 524286 期間 109.0062 ミリ秒
maxEvenConjunction2 結果: 524286 期間 80.0046 ミリ秒

最大しきい値: 1048576
maxEvenDividing 結果: 1048574 期間 109.0063ms
maxEvenDividing2 結果: 1048574 期間 80.0045 ミリ秒
maxEvenConjunction 結果: 1048574 期間 114.0066 ミリ秒
maxEvenConjunction2 結果: 1048574 期間 78.0044 ミリ秒

最大しきい値: 2097152
maxEvenDividing 結果: 2097150 期間 111.0064ms
maxEvenDividing2 結果: 2097150 期間 79.0045 ミリ秒
maxEvenConjunction 結果: 2097150 期間 112.0064 ミリ秒
maxEvenConjunction2 結果: 2097150 期間 77.0044 ミリ秒

最大しきい値: 4194304
maxEvenDividing 結果: 4194302 期間 111.0063ms
maxEvenDividing2 結果: 4194302 期間 78.0045 ミリ秒
maxEvenConjunction 結果: 4194302 期間 111.0063 ミリ秒
maxEvenConjunction2 結果: 4194302 期間 77.0044 ミリ秒

最大しきい値: 8388608
maxEvenDividing 結果: 8388606 期間 109.0062ms
maxEvenDividing2 結果: 8388606 期間 78.0045 ミリ秒
maxEvenConjunction 結果: 8388606 期間 114.0065 ミリ秒
maxEvenConjunction2 結果: 8388606 期間 78.0045 ミリ秒

最大しきい値: 16777216
maxEvenDividing 結果: 16777214 期間 109.0062ms
maxEvenDividing2 結果: 16777214 期間 77.0044 ミリ秒
maxEvenConjunction 結果: 16777214 期間 109.0063 ミリ秒
maxEvenConjunction2 結果: 16777214 期間 77.0044 ミリ秒

最大しきい値: 33554432
maxEvenDividing 結果: 33554430 期間 113.0065ms
maxEvenDividing2 結果: 33554430 期間 78.0045 ミリ秒
maxEvenConjunction 結果: 33554430 期間 110.0063 ミリ秒
maxEvenConjunction2 結果: 33554430 期間 80.0045 ミリ秒

最大しきい値: 67108864
maxEvenDividing 結果: 67108860 期間 112.0064ms
maxEvenDividing2 結果: 67108860 期間 77.0044 ミリ秒
maxEvenConjunction 結果: 67108860 期間 112.0064 ミリ秒
maxEvenConjunction2 結果: 67108860 期間 80.0046 ミリ秒

最大しきい値: 134217728
maxEvenDividing 結果: 134217726 期間 109.0063ms
maxEvenDividing2 結果: 134217726 期間 78.0044 ミリ秒
maxEvenConjunction 結果: 134217726 期間 114.0065 ミリ秒
maxEvenConjunction2 結果: 134217726 期間 81.0047 ミリ秒

最大しきい値: 268435456
maxEvenDividing 結果: 268435446 期間 111.0064ms
maxEvenDividing2 結果: 268435446 期間 79.0045 ミリ秒
maxEvenConjunction 結果: 268435446 期間 114.0065 ミリ秒
maxEvenConjunction2 結果: 268435446 期間 79.0045 ミリ秒

最大しきい値: 536870912
maxEvenDividing 結果: 536870910 期間 107.0062ms
maxEvenDividing2 結果: 536870910 期間 76.0043 ミリ秒
maxEvenConjunction 結果: 536870910 期間 109.0062 ミリ秒
maxEvenConjunction2 結果: 536870910 期間 80.0046 ミリ秒

Go コンパイラーがコードを最適化せず、最初の条件が false であっても常に XNUMX 番目の条件をチェックする理由については、明確な説明が見つかりませんでした。 それとも私の目がぼやけていて、明らかな間違いに気づかないだけでしょうか? それとも、コンパイラーに特別な命令を提供する必要がありますか? 賢明なコメントをいただければ幸いです。

PS: はい、楽しみのために、Java 5 と Java 7/8 で同様のテストを実行しました。すべてが明らかで、実行時間も同じでした。

出所: habr.com

コメントを追加します