Conditions à Go et leurs bizarreries

Pensez-vous que ces deux options pour tester les conditions à l’intérieur d’une boucle sont équivalentes en termes de performances ?

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


Tout a commencé par un « échauffement cérébral » ; je devais donner un exemple de recherche optimale du plus grand nombre pair dans un tableau d'entiers [-x....x]. Je me demandais à quel point les performances seraient meilleures si j'utilisais la multiplication logique par 1 pour déterminer si un nombre est pair ou non.


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

Mon expérience en programmation dans Go n'est pas très étendue, un peu plus d'un an et demi, je l'ai utilisé, bien que souvent, mais purement à des fins utilitaires (enfin, peut-être sauf pour un projet lié à un service http à forte charge), donc je commencé avec ça. Ouvrez GoLand et écrivez un test simple


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
}

Nous obtenons un résultat qui montre que plus le seuil est élevé, plus les fluctuations des performances apparaissent souvent.

Comparermax 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

Il est clair que dans ce cas, pour différents seuils, nous avons différents ensembles de données de test, la charge du processeur (sur mon ordinateur portable i5-2540M) varie autour de 20..30%, la mémoire occupée par l'application exécutée depuis GoLand est en moyenne environ 813 Mo - cela affecte également la fiabilité du résultat, vous devez enregistrer les cas de test sur le disque et exécuter tous les tests pour chaque seuil indépendamment les uns des autres.

Et maintenant, en réfléchissant à la manière de mettre en œuvre tout cela avec des coûts minimes, je corrige automatiquement le contrôle d'état

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

sur

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

Je relance les tests... et je ne comprends plus rien :)

Le temps passé à l'exécution commence à différer non pas en pourcentages/fractions de pour cent, mais de 10..15 %. J'ajoute rapidement 2 tests supplémentaires :

		
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
}

Je le lance et j'obtiens cette image :capacité initiale du réseau : 100000000 XNUMX XNUMX XNUMX

seuil maximum : 128
résultat maxEvenDividing : 126 durée 116.0066 ms
résultat maxEvenDividing2 : 126 durée 79.0045 ms
résultat maxEvenConjunction : 126 durée 114.0065 ms
résultat maxEvenConjunction2 : 126 durée 83.0048 ms

seuil maximum : 256
résultat maxEvenDividing : 254 durée 111.0063 ms
résultat maxEvenDividing2 : 254 durée 77.0044 ms
résultat maxEvenConjunction : 254 durée 110.0063 ms
résultat maxEvenConjunction2 : 254 durée 80.0046 ms

seuil maximum : 512
résultat maxEvenDividing : 510 durée 114.0066 ms
résultat maxEvenDividing2 : 510 durée 80.0045 ms
résultat maxEvenConjunction : 510 durée 110.0063 ms
résultat maxEvenConjunction2 : 510 durée 80.0046 ms

seuil maximum : 1024
résultat maxEvenDividing : 1022 durée 109.0063 ms
résultat maxEvenDividing2 : 1022 durée 77.0044 ms
résultat maxEvenConjunction : 1022 durée 111.0063 ms
résultat maxEvenConjunction2 : 1022 durée 81.0047 ms

seuil maximum : 2048
résultat maxEvenDividing : 2046 durée 114.0065 ms
résultat maxEvenDividing2 : 2046 durée 79.0045 ms
résultat maxEvenConjunction : 2046 durée 113.0065 ms
résultat maxEvenConjunction2 : 2046 durée 81.0046 ms

seuil maximum : 4096
résultat maxEvenDividing : 4094 durée 114.0065 ms
résultat maxEvenDividing2 : 4094 durée 80.0046 ms
résultat maxEvenConjunction : 4094 durée 111.0063 ms
résultat maxEvenConjunction2 : 4094 durée 78.0045 ms

seuil maximum : 8192
résultat maxEvenDividing : 8190 durée 107.0062 ms
résultat maxEvenDividing2 : 8190 durée 77.0044 ms
résultat maxEvenConjunction : 8190 durée 111.0063 ms
résultat maxEvenConjunction2 : 8190 durée 77.0044 ms

seuil maximum : 16384
résultat maxEvenDividing : 16382 durée 109.0063 ms
résultat maxEvenDividing2 : 16382 durée 77.0044 ms
résultat maxEvenConjunction : 16382 durée 108.0062 ms
résultat maxEvenConjunction2 : 16382 durée 77.0044 ms

seuil maximum : 32768
résultat maxEvenDividing : 32766 durée 112.0064 ms
résultat maxEvenDividing2 : 32766 durée 77.0044 ms
résultat maxEvenConjunction : 32766 durée 109.0062 ms
résultat maxEvenConjunction2 : 32766 durée 78.0045 ms

seuil maximum : 65536
résultat maxEvenDividing : 65534 durée 109.0062 ms
résultat maxEvenDividing2 : 65534 durée 75.0043 ms
résultat maxEvenConjunction : 65534 durée 109.0063 ms
résultat maxEvenConjunction2 : 65534 durée 79.0045 ms

seuil maximum : 131072
résultat maxEvenDividing : 131070 durée 108.0061 ms
résultat maxEvenDividing2 : 131070 durée 76.0044 ms
résultat maxEvenConjunction : 131070 durée 110.0063 ms
résultat maxEvenConjunction2 : 131070 durée 80.0046 ms

seuil maximum : 262144
résultat maxEvenDividing : 262142 durée 110.0063 ms
résultat maxEvenDividing2 : 262142 durée 76.0044 ms
résultat maxEvenConjunction : 262142 durée 107.0061 ms
résultat maxEvenConjunction2 : 262142 durée 78.0044 ms

seuil maximum : 524288
résultat maxEvenDividing : 524286 durée 109.0062 ms
résultat maxEvenDividing2 : 524286 durée 78.0045 ms
résultat maxEvenConjunction : 524286 durée 109.0062 ms
résultat maxEvenConjunction2 : 524286 durée 80.0046 ms

seuil maximum : 1048576
résultat maxEvenDividing : 1048574 durée 109.0063 ms
résultat maxEvenDividing2 : 1048574 durée 80.0045 ms
résultat maxEvenConjunction : 1048574 durée 114.0066 ms
résultat maxEvenConjunction2 : 1048574 durée 78.0044 ms

seuil maximum : 2097152
résultat maxEvenDividing : 2097150 durée 111.0064 ms
résultat maxEvenDividing2 : 2097150 durée 79.0045 ms
résultat maxEvenConjunction : 2097150 durée 112.0064 ms
résultat maxEvenConjunction2 : 2097150 durée 77.0044 ms

seuil maximum : 4194304
résultat maxEvenDividing : 4194302 durée 111.0063 ms
résultat maxEvenDividing2 : 4194302 durée 78.0045 ms
résultat maxEvenConjunction : 4194302 durée 111.0063 ms
résultat maxEvenConjunction2 : 4194302 durée 77.0044 ms

seuil maximum : 8388608
résultat maxEvenDividing : 8388606 durée 109.0062 ms
résultat maxEvenDividing2 : 8388606 durée 78.0045 ms
résultat maxEvenConjunction : 8388606 durée 114.0065 ms
résultat maxEvenConjunction2 : 8388606 durée 78.0045 ms

seuil maximum : 16777216
résultat maxEvenDividing : 16777214 durée 109.0062 ms
résultat maxEvenDividing2 : 16777214 durée 77.0044 ms
résultat maxEvenConjunction : 16777214 durée 109.0063 ms
résultat maxEvenConjunction2 : 16777214 durée 77.0044 ms

seuil maximum : 33554432
résultat maxEvenDividing : 33554430 durée 113.0065 ms
résultat maxEvenDividing2 : 33554430 durée 78.0045 ms
résultat maxEvenConjunction : 33554430 durée 110.0063 ms
résultat maxEvenConjunction2 : 33554430 durée 80.0045 ms

seuil maximum : 67108864
résultat maxEvenDividing : 67108860 durée 112.0064 ms
résultat maxEvenDividing2 : 67108860 durée 77.0044 ms
résultat maxEvenConjunction : 67108860 durée 112.0064 ms
résultat maxEvenConjunction2 : 67108860 durée 80.0046 ms

seuil maximum : 134217728
résultat maxEvenDividing : 134217726 durée 109.0063 ms
résultat maxEvenDividing2 : 134217726 durée 78.0044 ms
résultat maxEvenConjunction : 134217726 durée 114.0065 ms
résultat maxEvenConjunction2 : 134217726 durée 81.0047 ms

seuil maximum : 268435456
résultat maxEvenDividing : 268435446 durée 111.0064 ms
résultat maxEvenDividing2 : 268435446 durée 79.0045 ms
résultat maxEvenConjunction : 268435446 durée 114.0065 ms
résultat maxEvenConjunction2 : 268435446 durée 79.0045 ms

seuil maximum : 536870912
résultat maxEvenDividing : 536870910 durée 107.0062 ms
résultat maxEvenDividing2 : 536870910 durée 76.0043 ms
résultat maxEvenConjunction : 536870910 durée 109.0062 ms
résultat maxEvenConjunction2 : 536870910 durée 80.0046 ms

Je n'ai pas trouvé d'explication claire pour laquelle le compilateur Go n'optimise pas le code et vérifie toujours la deuxième condition, même si la première est fausse. Ou peut-être que mes yeux sont simplement flous et que je ne vois aucune erreur évidente ? Ou devez-vous fournir des instructions spéciales au compilateur ? Je serais heureux de recevoir des commentaires sensés.

PS: Oui, juste pour m'amuser, j'ai fait des tests similaires sur Java 5 et Java 7/8 - tout est clair, le temps d'exécution est le même.

Source: habr.com

Ajouter un commentaire