Förhållandena i Go och deras konstigheter

Tror du att dessa två alternativ för att testa förhållanden i en loop är likvärdiga i prestanda?

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


Allt började med en "hjärnuppvärmning", jag var tvungen att ge ett exempel på en optimal sökning efter det största jämna talet i en uppsättning heltal [-x....x]. Jag undrade hur mycket bättre prestanda skulle bli om jag använde logisk multiplikation med 1 för att ta reda på om ett tal är jämnt eller inte.


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

Min programmeringserfarenhet i Go är inte särskilt omfattande, drygt ett och ett halvt år, jag använde den, om än ofta, men enbart i utilitaristiska syften (nåja, kanske förutom ett projekt relaterat till en högbelastad http-tjänst), så jag började med det. Öppna GoLand och skriv ett enkelt test


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
}

Vi får ett resultat som visar att ju högre tröskel, desto oftare uppstår fluktuationer i prestanda.

Jämförmax 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

Det är tydligt att i det här fallet, för olika trösklar har vi olika uppsättningar av testdata, processorbelastningen (på min i5-2540M bärbara dator) varierar runt 20..30%, minnet som upptas av applikationen som körs från GoLand är i genomsnitt ca 813MB - detta påverkar också resultatets tillförlitlighet, du måste spara testfall på disken och köra alla tester för varje tröskel isolerat från varandra.

Och nu, när jag tänker på hur jag ska implementera allt detta med minimala kostnader, korrigerar jag automatiskt tillståndskontrollen

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

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

Jag kör testerna igen... och jag slutar förstå något :)

Tiden som spenderas på exekvering börjar inte längre skilja sig åt med procent/bråkdelar av en procent, utan med 10...15%. Jag lägger snabbt till ytterligare 2 tester:

		
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
}

Jag kör den och får den här bilden:initial arraykapacitet: 100000000

maxtröskel: 128
maxEvenDividing resultat: 126 varaktighet 116.0066ms
maxEvenDividing2 resultat: 126 varaktighet 79.0045ms
maxEvenConjunction resultat: 126 varaktighet 114.0065ms
maxEvenConjunction2 resultat: 126 varaktighet 83.0048ms

maxtröskel: 256
maxEvenDividing resultat: 254 varaktighet 111.0063ms
maxEvenDividing2 resultat: 254 varaktighet 77.0044ms
maxEvenConjunction resultat: 254 varaktighet 110.0063ms
maxEvenConjunction2 resultat: 254 varaktighet 80.0046ms

maxtröskel: 512
maxEvenDividing resultat: 510 varaktighet 114.0066ms
maxEvenDividing2 resultat: 510 varaktighet 80.0045ms
maxEvenConjunction resultat: 510 varaktighet 110.0063ms
maxEvenConjunction2 resultat: 510 varaktighet 80.0046ms

maxtröskel: 1024
maxEvenDividing resultat: 1022 varaktighet 109.0063ms
maxEvenDividing2 resultat: 1022 varaktighet 77.0044ms
maxEvenConjunction resultat: 1022 varaktighet 111.0063ms
maxEvenConjunction2 resultat: 1022 varaktighet 81.0047ms

maxtröskel: 2048
maxEvenDividing resultat: 2046 varaktighet 114.0065ms
maxEvenDividing2 resultat: 2046 varaktighet 79.0045ms
maxEvenConjunction resultat: 2046 varaktighet 113.0065ms
maxEvenConjunction2 resultat: 2046 varaktighet 81.0046ms

maxtröskel: 4096
maxEvenDividing resultat: 4094 varaktighet 114.0065ms
maxEvenDividing2 resultat: 4094 varaktighet 80.0046ms
maxEvenConjunction resultat: 4094 varaktighet 111.0063ms
maxEvenConjunction2 resultat: 4094 varaktighet 78.0045ms

maxtröskel: 8192
maxEvenDividing resultat: 8190 varaktighet 107.0062ms
maxEvenDividing2 resultat: 8190 varaktighet 77.0044ms
maxEvenConjunction resultat: 8190 varaktighet 111.0063ms
maxEvenConjunction2 resultat: 8190 varaktighet 77.0044ms

maxtröskel: 16384
maxEvenDividing resultat: 16382 varaktighet 109.0063ms
maxEvenDividing2 resultat: 16382 varaktighet 77.0044ms
maxEvenConjunction resultat: 16382 varaktighet 108.0062ms
maxEvenConjunction2 resultat: 16382 varaktighet 77.0044ms

maxtröskel: 32768
maxEvenDividing resultat: 32766 varaktighet 112.0064ms
maxEvenDividing2 resultat: 32766 varaktighet 77.0044ms
maxEvenConjunction resultat: 32766 varaktighet 109.0062ms
maxEvenConjunction2 resultat: 32766 varaktighet 78.0045ms

maxtröskel: 65536
maxEvenDividing resultat: 65534 varaktighet 109.0062ms
maxEvenDividing2 resultat: 65534 varaktighet 75.0043ms
maxEvenConjunction resultat: 65534 varaktighet 109.0063ms
maxEvenConjunction2 resultat: 65534 varaktighet 79.0045ms

maxtröskel: 131072
maxEvenDividing resultat: 131070 varaktighet 108.0061ms
maxEvenDividing2 resultat: 131070 varaktighet 76.0044ms
maxEvenConjunction resultat: 131070 varaktighet 110.0063ms
maxEvenConjunction2 resultat: 131070 varaktighet 80.0046ms

maxtröskel: 262144
maxEvenDividing resultat: 262142 varaktighet 110.0063ms
maxEvenDividing2 resultat: 262142 varaktighet 76.0044ms
maxEvenConjunction resultat: 262142 varaktighet 107.0061ms
maxEvenConjunction2 resultat: 262142 varaktighet 78.0044ms

maxtröskel: 524288
maxEvenDividing resultat: 524286 varaktighet 109.0062ms
maxEvenDividing2 resultat: 524286 varaktighet 78.0045ms
maxEvenConjunction resultat: 524286 varaktighet 109.0062ms
maxEvenConjunction2 resultat: 524286 varaktighet 80.0046ms

maxtröskel: 1048576
maxEvenDividing resultat: 1048574 varaktighet 109.0063ms
maxEvenDividing2 resultat: 1048574 varaktighet 80.0045ms
maxEvenConjunction resultat: 1048574 varaktighet 114.0066ms
maxEvenConjunction2 resultat: 1048574 varaktighet 78.0044ms

maxtröskel: 2097152
maxEvenDividing resultat: 2097150 varaktighet 111.0064ms
maxEvenDividing2 resultat: 2097150 varaktighet 79.0045ms
maxEvenConjunction resultat: 2097150 varaktighet 112.0064ms
maxEvenConjunction2 resultat: 2097150 varaktighet 77.0044ms

maxtröskel: 4194304
maxEvenDividing resultat: 4194302 varaktighet 111.0063ms
maxEvenDividing2 resultat: 4194302 varaktighet 78.0045ms
maxEvenConjunction resultat: 4194302 varaktighet 111.0063ms
maxEvenConjunction2 resultat: 4194302 varaktighet 77.0044ms

maxtröskel: 8388608
maxEvenDividing resultat: 8388606 varaktighet 109.0062ms
maxEvenDividing2 resultat: 8388606 varaktighet 78.0045ms
maxEvenConjunction resultat: 8388606 varaktighet 114.0065ms
maxEvenConjunction2 resultat: 8388606 varaktighet 78.0045ms

maxtröskel: 16777216
maxEvenDividing resultat: 16777214 varaktighet 109.0062ms
maxEvenDividing2 resultat: 16777214 varaktighet 77.0044ms
maxEvenConjunction resultat: 16777214 varaktighet 109.0063ms
maxEvenConjunction2 resultat: 16777214 varaktighet 77.0044ms

maxtröskel: 33554432
maxEvenDividing resultat: 33554430 varaktighet 113.0065ms
maxEvenDividing2 resultat: 33554430 varaktighet 78.0045ms
maxEvenConjunction resultat: 33554430 varaktighet 110.0063ms
maxEvenConjunction2 resultat: 33554430 varaktighet 80.0045ms

maxtröskel: 67108864
maxEvenDividing resultat: 67108860 varaktighet 112.0064ms
maxEvenDividing2 resultat: 67108860 varaktighet 77.0044ms
maxEvenConjunction resultat: 67108860 varaktighet 112.0064ms
maxEvenConjunction2 resultat: 67108860 varaktighet 80.0046ms

maxtröskel: 134217728
maxEvenDividing resultat: 134217726 varaktighet 109.0063ms
maxEvenDividing2 resultat: 134217726 varaktighet 78.0044ms
maxEvenConjunction resultat: 134217726 varaktighet 114.0065ms
maxEvenConjunction2 resultat: 134217726 varaktighet 81.0047ms

maxtröskel: 268435456
maxEvenDividing resultat: 268435446 varaktighet 111.0064ms
maxEvenDividing2 resultat: 268435446 varaktighet 79.0045ms
maxEvenConjunction resultat: 268435446 varaktighet 114.0065ms
maxEvenConjunction2 resultat: 268435446 varaktighet 79.0045ms

maxtröskel: 536870912
maxEvenDividing resultat: 536870910 varaktighet 107.0062ms
maxEvenDividing2 resultat: 536870910 varaktighet 76.0043ms
maxEvenConjunction resultat: 536870910 varaktighet 109.0062ms
maxEvenConjunction2 resultat: 536870910 varaktighet 80.0046ms

Jag kunde inte hitta en tydlig förklaring till varför Go-kompilatorn inte optimerar koden och alltid kontrollerar det andra villkoret, även om det första är falskt. Eller kanske mina ögon bara är suddiga och jag ser inget uppenbart misstag? Eller behöver du ge några speciella instruktioner till kompilatorn? Jag skulle bli glad för vettiga kommentarer.

PS: Ja, bara för skojs skull körde jag liknande tester på Java 5 och Java 7/8 - allt är klart, exekveringstiden är densamma.

Källa: will.com

Lägg en kommentar