Conditions in Go and their weirdness

Do you think these two options for checking conditions inside a loop are equivalent in performance?

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


It all started with a β€œwarm-up for the brain”, it was necessary to give an example of the optimal search for the largest even number in the array of integers [-x….x]. I was wondering how much better performance would be if to find out if the number is even or not, use logical multiplication by 1.


//Ρƒ Ρ‡Π΅Ρ‚Π½Ρ‹Ρ… чисСл послСдний Π±ΠΈΡ‚ всСгда Ρ€Π°Π²Π΅Π½ 0
value & 1 == 0
//vs классичСский ΠΌΠ΅Ρ‚ΠΎΠ΄
value % 2 == 0

My programming experience in Go is not very large, just over a year and a half, I used it, although often, but purely for utilitarian purposes (well, maybe except for one project related to a highly loaded http service), so I started with it. Open GoLand and write a simple 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
}

We get the result, which shows that the higher the threshold, the more often fluctuations appear in terms of performance.

Comparemax 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

It is clear that in this case, for different thresholds, we have different sets of test data, the processor load (on my i5-2540M laptop) varies around 20..30%, the memory occupied by the application launched from under GoLand is on average about 813MB - this is also affects the reliability of the result, you need to implement saving test suites on disk and run all tests for each threshold isolated from each other.

And now, thinking about how to implement all this with minimal cost, I automatically correct the condition check

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

on

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

I run the tests again ... and I stop understanding anything πŸ™‚

The time spent on execution begins to differ not by percentages / fractions of a percent, but by 10..15% I quickly add 2 more tests:

		
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
}

I run it and get this picture:initial array capacity: 100000000

max threshold: 128
maxEvenDividing result: 126 duration 116.0066ms
maxEvenDividing2 result: 126 duration 79.0045ms
maxEvenConjunction result: 126 duration 114.0065ms
maxEvenConjunction2 result: 126 duration 83.0048ms

max threshold: 256
maxEvenDividing result: 254 duration 111.0063ms
maxEvenDividing2 result: 254 duration 77.0044ms
maxEvenConjunction result: 254 duration 110.0063ms
maxEvenConjunction2 result: 254 duration 80.0046ms

max threshold: 512
maxEvenDividing result: 510 duration 114.0066ms
maxEvenDividing2 result: 510 duration 80.0045ms
maxEvenConjunction result: 510 duration 110.0063ms
maxEvenConjunction2 result: 510 duration 80.0046ms

max threshold: 1024
maxEvenDividing result: 1022 duration 109.0063ms
maxEvenDividing2 result: 1022 duration 77.0044ms
maxEvenConjunction result: 1022 duration 111.0063ms
maxEvenConjunction2 result: 1022 duration 81.0047ms

max threshold: 2048
maxEvenDividing result: 2046 duration 114.0065ms
maxEvenDividing2 result: 2046 duration 79.0045ms
maxEvenConjunction result: 2046 duration 113.0065ms
maxEvenConjunction2 result: 2046 duration 81.0046ms

max threshold: 4096
maxEvenDividing result: 4094 duration 114.0065ms
maxEvenDividing2 result: 4094 duration 80.0046ms
maxEvenConjunction result: 4094 duration 111.0063ms
maxEvenConjunction2 result: 4094 duration 78.0045ms

max threshold: 8192
maxEvenDividing result: 8190 duration 107.0062ms
maxEvenDividing2 result: 8190 duration 77.0044ms
maxEvenConjunction result: 8190 duration 111.0063ms
maxEvenConjunction2 result: 8190 duration 77.0044ms

max threshold: 16384
maxEvenDividing result: 16382 duration 109.0063ms
maxEvenDividing2 result: 16382 duration 77.0044ms
maxEvenConjunction result: 16382 duration 108.0062ms
maxEvenConjunction2 result: 16382 duration 77.0044ms

max threshold: 32768
maxEvenDividing result: 32766 duration 112.0064ms
maxEvenDividing2 result: 32766 duration 77.0044ms
maxEvenConjunction result: 32766 duration 109.0062ms
maxEvenConjunction2 result: 32766 duration 78.0045ms

max threshold: 65536
maxEvenDividing result: 65534 duration 109.0062ms
maxEvenDividing2 result: 65534 duration 75.0043ms
maxEvenConjunction result: 65534 duration 109.0063ms
maxEvenConjunction2 result: 65534 duration 79.0045ms

max threshold: 131072
maxEvenDividing result: 131070 duration 108.0061ms
maxEvenDividing2 result: 131070 duration 76.0044ms
maxEvenConjunction result: 131070 duration 110.0063ms
maxEvenConjunction2 result: 131070 duration 80.0046ms

max threshold: 262144
maxEvenDividing result: 262142 duration 110.0063ms
maxEvenDividing2 result: 262142 duration 76.0044ms
maxEvenConjunction result: 262142 duration 107.0061ms
maxEvenConjunction2 result: 262142 duration 78.0044ms

max threshold: 524288
maxEvenDividing result: 524286 duration 109.0062ms
maxEvenDividing2 result: 524286 duration 78.0045ms
maxEvenConjunction result: 524286 duration 109.0062ms
maxEvenConjunction2 result: 524286 duration 80.0046ms

max threshold: 1048576
maxEvenDividing result: 1048574 duration 109.0063ms
maxEvenDividing2 result: 1048574 duration 80.0045ms
maxEvenConjunction result: 1048574 duration 114.0066ms
maxEvenConjunction2 result: 1048574 duration 78.0044ms

max threshold: 2097152
maxEvenDividing result: 2097150 duration 111.0064ms
maxEvenDividing2 result: 2097150 duration 79.0045ms
maxEvenConjunction result: 2097150 duration 112.0064ms
maxEvenConjunction2 result: 2097150 duration 77.0044ms

max threshold: 4194304
maxEvenDividing result: 4194302 duration 111.0063ms
maxEvenDividing2 result: 4194302 duration 78.0045ms
maxEvenConjunction result: 4194302 duration 111.0063ms
maxEvenConjunction2 result: 4194302 duration 77.0044ms

max threshold: 8388608
maxEvenDividing result: 8388606 duration 109.0062ms
maxEvenDividing2 result: 8388606 duration 78.0045ms
maxEvenConjunction result: 8388606 duration 114.0065ms
maxEvenConjunction2 result: 8388606 duration 78.0045ms

max threshold: 16777216
maxEvenDividing result: 16777214 duration 109.0062ms
maxEvenDividing2 result: 16777214 duration 77.0044ms
maxEvenConjunction result: 16777214 duration 109.0063ms
maxEvenConjunction2 result: 16777214 duration 77.0044ms

max threshold: 33554432
maxEvenDividing result: 33554430 duration 113.0065ms
maxEvenDividing2 result: 33554430 duration 78.0045ms
maxEvenConjunction result: 33554430 duration 110.0063ms
maxEvenConjunction2 result: 33554430 duration 80.0045ms

max threshold: 67108864
maxEvenDividing result: 67108860 duration 112.0064ms
maxEvenDividing2 result: 67108860 duration 77.0044ms
maxEvenConjunction result: 67108860 duration 112.0064ms
maxEvenConjunction2 result: 67108860 duration 80.0046ms

max threshold: 134217728
maxEvenDividing result: 134217726 duration 109.0063ms
maxEvenDividing2 result: 134217726 duration 78.0044ms
maxEvenConjunction result: 134217726 duration 114.0065ms
maxEvenConjunction2 result: 134217726 duration 81.0047ms

max threshold: 268435456
maxEvenDividing result: 268435446 duration 111.0064ms
maxEvenDividing2 result: 268435446 duration 79.0045ms
maxEvenConjunction result: 268435446 duration 114.0065ms
maxEvenConjunction2 result: 268435446 duration 79.0045ms

max threshold: 536870912
maxEvenDividing result: 536870910 duration 107.0062ms
maxEvenDividing2 result: 536870910 duration 76.0043ms
maxEvenConjunction result: 536870910 duration 109.0062ms
maxEvenConjunction2 result: 536870910 duration 80.0046ms

I did not find a clear explanation why the Go compiler does not optimize the code and always checks the second condition, even if the first one is false. Or maybe my eye is just β€œblurred” and I don’t see some obvious mistake? Or do I need to specify some special instructions to the compiler? I would be glad to sensible comments.

PS: Yes, for fun, I ran similar tests on Java 5 and Java 7/8 - everything is clear, the execution time is the same.

Source: habr.com

Add a comment