Condiții în Go și ciudățeniile lor

Ce credeți, sunt echivalente în performanță aceste două variante de verificare a condițiilor în interiorul unui ciclu?

		
if a > b && c*2 > d {
	....
}
// și
if a  d {
 ....
}


Totul a început cu un „încălzitor pentru creier”, trebuia să ofer un exemplu de căutare optimă într-un array de numere întregi [-x….x] pentru cel mai mare număr par. M-a făcut curios cât de mult va crește performanța dacă pentru a determina dacă un număr este par sau nu, folosesc înmulțirea logică cu 1.


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

Experiența mea de programare în Go nu este foarte mare, puțin peste un an și jumătate, l-am folosit deși des, dar pur și simplu în scopuri utilitare (ă poate cu excepția unui singur proiect legat de un serviciu http cu încărcare mare), așa că am început exact cu acesta. Deschidem GoLand și scriem un test simplu


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("capacitatea inițială a array-ului: " + fmt.Sprint(size))
	var maxValue int32
        // Vom varia intervalul numerelor de la minim 
        // la maxim. Cu cât intervalul este mai mic, cu atât mai mult 
        // timp procesorul va cheltui pe operația 
        // de comparare a numărului curent, cu cel anterior găsit și invers
	for maxValue = 128; maxValue  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
}

Obținem un rezultat, pe care se vede că cu cât pragul este mai mare, cu atât apar din ce în ce mai frecvent fluctuații în ceea ce privește performanța.

Comparațipragul maxim: 128
rezultatul maxEvenDividing: 126 durata 116.0067ms
rezultatul maxEvenConjunction: 126 durata 116.0066ms

prag maxim: 16384
maxEvenDividing rezultat: 16382 durata 115.0066ms
maxEvenConjunction rezultat: 16382 durata 111.0064ms

......

prag maxim: 8388608
maxEvenDividing rezultat: 8388606 durata 109.0063ms
rezultatul maxEvenConjunction: 8388606 durata 109.0062ms

prag maxim: 16777216
rezultatul maxEvenDividing: 16777214 durata 108.0062ms
rezultatul maxEvenConjunction: 16777214 durata 109.0062ms

prag maxim: 33554432
rezultatul maxEvenDividing: 33554430 durata 114.0066ms
rezultatul maxEvenConjunction: 33554430 durata 110.0063ms

prag maxim: 67108864
rezultatul maxEvenDividing: 67108860 durata 111.0064ms
rezultatul maxEvenConjunction: 67108860 durata 109.0062ms

prag maxim: 134217728
rezultatul maxEvenDividing: 134217726 durata 108.0062ms
rezultatul maxEvenConjunction: 134217726 durata 109.0063ms

prag maxim: 268435456
rezultatul maxEvenDividing: 268435446 durata 111.0063ms
rezultatul maxEvenConjunction: 268435446 durata 110.0063ms

Este clar că, în acest caz, pentru diferite praguri avem seturi diferite de date de test, utilizarea procesorului (pe laptopul meu i5-2540M) variază în jur de 20..30%, iar memoria utilizată de aplicația rulată din GoLand este în medie de aproximativ 813Mb — acest lucru influențează, de asemenea, credibilitatea rezultatului, trebuie implementată salvarea seturilor de teste pe disc și rularea tuturor testelor pentru fiecare prag izolat unul de celălalt.

Și gândindu-mă cum să implementez toate acestea cu costuri minime, corectez automat condiția de verificare.

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

pe

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

rulând din nou testele... și nu mai înțeleg nimic 🙂

Timpul necesar pentru execuție începe să varieze nu pe procente/ fracțiuni de procent, ci pe 10..15%. Scriu rapid încă 2 teste:

		
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
}

rulând și obținând această imagine:capacitatea inițială a array-ului: 100000000

pragul maxim: 128
rezultatul maxEvenDividing: 126 durata 116.0066ms
rezultatul maxEvenDividing2: 126 durata 79.0045ms
rezultatul maxEvenConjunction: 126 durata 114.0065ms
rezultatul maxEvenConjunction2: 126 durata 83.0048ms

prag maxim: 256
rezultatul maxEvenDividing: 254 durata 111.0063ms
rezultatul maxEvenDividing2: 254 durata 77.0044ms
rezultatul maxEvenConjunction: 254 durata 110.0063ms
rezultatul maxEvenConjunction2: 254 durata 80.0046ms

prag maxim: 512
rezultatul maxEvenDividing: 510 durata 114.0066ms
rezultatul maxEvenDividing2: 510 durata 80.0045ms
rezultatul maxEvenConjunction: 510 durata 110.0063ms
rezultatul maxEvenConjunction2: 510 durata 80.0046ms

prag maxim: 1024
rezultatul maxEvenDividing: 1022 durata 109.0063ms
rezultatul maxEvenDividing2: 1022 durata 77.0044ms
rezultatul maxEvenConjunction: 1022 durata 111.0063ms
rezultatul maxEvenConjunction2: 1022 durata 81.0047ms

prag maxim: 2048
rezultatul maxEvenDividing: 2046 durata 114.0065ms
rezultatul maxEvenDividing2: 2046 durata 79.0045ms
rezultatul maxEvenConjunction: 2046 durata 113.0065ms
maxEvenConjunction2 rezultat: 2046 durată 81.0046ms

prag maxim: 4096
maxEvenDividing rezultat: 4094 durată 114.0065ms
maxEvenDividing2 rezultat: 4094 durată 80.0046ms
maxEvenConjunction rezultat: 4094 durată 111.0063ms
maxEvenConjunction2 rezultat: 4094 durată 78.0045ms

prag maxim: 8192
maxEvenDividing rezultat: 8190 durată 107.0062ms
maxEvenDividing2 rezultat: 8190 durată 77.0044ms
maxEvenConjunction rezultat: 8190 durată 111.0063ms
maxEvenConjunction2 rezultat: 8190 durată 77.0044ms

prag maxim: 16384
maxEvenDividing rezultat: 16382 durată 109.0063ms
maxEvenDividing2 rezultat: 16382 durată 77.0044ms
maxEvenConjunction rezultat: 16382 durată 108.0062ms
maxEvenConjunction2 rezultat: 16382 durată 77.0044ms

prag maxim: 32768
maxEvenDividing rezultat: 32766 durată 112.0064ms
maxEvenDividing2 rezultat: 32766 durată 77.0044ms
maxEvenConjunction rezultat: 32766 durată 109.0062ms
maxEvenConjunction2 rezultat: 32766 durată 78.0045ms

prag maxim: 65536
maxEvenDividing rezultat: 65534 durată 109.0062ms
maxEvenDividing2 rezultat: 65534 durată 75.0043ms
maxEvenConjunction rezultat: 65534 durată 109.0063ms
maxEvenConjunction2 rezultat: 65534 durată 79.0045ms

prag maxim: 131072
maxEvenDividing rezultat: 131070 durată 108.0061ms
maxEvenDividing2 rezultat: 131070 durată 76.0044ms
maxEvenConjunction rezultat: 131070 durată 110.0063ms
maxEvenConjunction2 rezultat: 131070 durată 80.0046ms

prag maxim: 262144
maxEvenDividing rezultat: 262142 durată 110.0063ms
maxEvenDividing2 rezultat: 262142 durată 76.0044ms
maxEvenConjunction rezultat: 262142 durată 107.0061ms
maxEvenConjunction2 rezultat: 262142 durată 78.0044ms

prag maxim: 524288
maxEvenDividing rezultat: 524286 durată 109.0062ms
maxEvenDividing2 rezultat: 524286 durată 78.0045ms
maxEvenConjunction rezultat: 524286 durată 109.0062ms
maxEvenConjunction2 rezultat: 524286 durată 80.0046ms

prag maxim: 1048576
maxEvenDividing rezultat: 1048574 durată 109.0063ms
maxEvenDividing2 rezultat: 1048574 durată 80.0045ms
maxEvenConjunction rezultat: 1048574 durată 114.0066ms
maxEvenConjunction2 rezultat: 1048574 durată 78.0044ms

prag maxim: 2097152
maxEvenDividing rezultat: 2097150 durată 111.0064ms
maxEvenDividing2 rezultat: 2097150 durată 79.0045ms
maxEvenConjunction rezultat: 2097150 durată 112.0064ms
maxEvenConjunction2 rezultat: 2097150 durată 77.0044ms

prag maxim: 4194304
maxEvenDividing rezultat: 4194302 durată 111.0063ms
maxEvenDividing2 rezultat: 4194302 durată 78.0045ms
maxEvenConjunction rezultat: 4194302 durată 111.0063ms
maxEvenConjunction2 rezultat: 4194302 durată 77.0044ms

prag maxim: 8388608
maxEvenDividing rezultat: 8388606 durată 109.0062ms
maxEvenDividing2 rezultat: 8388606 durată 78.0045ms
maxEvenConjunction rezultat: 8388606 durată 114.0065ms
maxEvenConjunction2 rezultat: 8388606 durată 78.0045ms

prag maxim: 16777216
maxEvenDividing rezultat: 16777214 durată 109.0062ms
maxEvenDividing2 rezultat: 16777214 durată 77.0044ms
maxEvenConjunction rezultat: 16777214 durată 109.0063ms
maxEvenConjunction2 rezultat: 16777214 durată 77.0044ms

prag maxim: 33554432
maxEvenDividing rezultat: 33554430 durată 113.0065ms
maxEvenDividing2 rezultat: 33554430 durată 78.0045ms
rezultatul maxEvenConjunction: 33554430 durata 110.0063ms
maxEvenConjunction2 rezultat: 33554430 durată 80.0045ms

prag maxim: 67108864
maxEvenDividing rezultat: 67108860 durată 112.0064ms
maxEvenDividing2 rezultat: 67108860 durată 77.0044ms
maxEvenConjunction rezultat: 67108860 durată 112.0064ms
maxEvenConjunction2 rezultat: 67108860 durată 80.0046ms

prag maxim: 134217728
maxEvenDividing rezultat: 134217726 durată 109.0063ms
maxEvenDividing2 rezultat: 134217726 durată 78.0044ms
maxEvenConjunction rezultat: 134217726 durată 114.0065ms
maxEvenConjunction2 rezultat: 134217726 durată 81.0047ms

prag maxim: 268435456
maxEvenDividing rezultat: 268435446 durată 111.0064ms
maxEvenDividing2 rezultat: 268435446 durată 79.0045ms
maxEvenConjunction rezultat: 268435446 durată 114.0065ms
maxEvenConjunction2 rezultat: 268435446 durată 79.0045ms

prag maxim: 536870912
maxEvenDividing rezultat: 536870910 durată 107.0062ms
maxEvenDividing2 rezultat: 536870910 durată 76.0043ms
maxEvenConjunction rezultatul: 536870910 durata 109.0062ms
maxEvenConjunction2 rezultatul: 536870910 durata 80.0046ms

Nu am găsit o explicație clară de ce compilatorul Go nu optimizează codul și verifică întotdeauna a doua condiție, chiar dacă prima este falsă. Sau poate că pur și simplu mi s-a „înăsprit” privirea și nu văd o eroare evidentă? Sau trebuie să indic anumite instrucțiuni speciale compilatorului? Aș aprecia comentarii utile.

PS: Da, din curiozitate, am rulat teste similare pe Java 5 și Java 7/8 - toate clare, timpul de execuție este același.

Sursa: habr.com

Cumpără un hosting fiabil pentru site-uri cu protecție DDoS, servere VPS VDS 🔥 Cumpără un hosting fiabil pentru site-uri cu protecție DDoS, servere VPS VDS | ProHoster