Go 中的条件及其奇怪之处

您认为这两种在循环内测试条件的选项在性能上是否等效?

		
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 中的编程经验不是很丰富,只有一年半多,我虽然经常使用它,但纯粹是出于功利目的(好吧,也许除了一个与高负载 http 服务相关的项目之外),所以我从它开始。 打开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.0045ms
maxEvenConjunction 结果:126 持续时间 114.0065ms
maxEvenConjunction2 结果:126 持续时间 83.0048ms

最大阈值:256
maxEvenDividing 结果:254 持续时间 111.0063ms
maxEvenDividing2 结果:254 持续时间 77.0044ms
maxEvenConjunction 结果:254 持续时间 110.0063ms
maxEvenConjunction2 结果:254 持续时间 80.0046ms

最大阈值:512
maxEvenDividing 结果:510 持续时间 114.0066ms
maxEvenDividing2 结果:510 持续时间 80.0045ms
maxEvenConjunction 结果:510 持续时间 110.0063ms
maxEvenConjunction2 结果:510 持续时间 80.0046ms

最大阈值:1024
maxEvenDividing 结果:1022 持续时间 109.0063ms
maxEvenDividing2 结果:1022 持续时间 77.0044ms
maxEvenConjunction 结果:1022 持续时间 111.0063ms
maxEvenConjunction2 结果:1022 持续时间 81.0047ms

最大阈值:2048
maxEvenDividing 结果:2046 持续时间 114.0065ms
maxEvenDividing2 结果:2046 持续时间 79.0045ms
maxEvenConjunction 结果:2046 持续时间 113.0065ms
maxEvenConjunction2 结果:2046 持续时间 81.0046ms

最大阈值:4096
maxEvenDividing 结果:4094 持续时间 114.0065ms
maxEvenDividing2 结果:4094 持续时间 80.0046ms
maxEvenConjunction 结果:4094 持续时间 111.0063ms
maxEvenConjunction2 结果:4094 持续时间 78.0045ms

最大阈值:8192
maxEvenDividing 结果:8190 持续时间 107.0062ms
maxEvenDividing2 结果:8190 持续时间 77.0044ms
maxEvenConjunction 结果:8190 持续时间 111.0063ms
maxEvenConjunction2 结果:8190 持续时间 77.0044ms

最大阈值:16384
maxEvenDividing 结果:16382 持续时间 109.0063ms
maxEvenDividing2 结果:16382 持续时间 77.0044ms
maxEvenConjunction 结果:16382 持续时间 108.0062ms
maxEvenConjunction2 结果:16382 持续时间 77.0044ms

最大阈值:32768
maxEvenDividing 结果:32766 持续时间 112.0064ms
maxEvenDividing2 结果:32766 持续时间 77.0044ms
maxEvenConjunction 结果:32766 持续时间 109.0062ms
maxEvenConjunction2 结果:32766 持续时间 78.0045ms

最大阈值:65536
maxEvenDividing 结果:65534 持续时间 109.0062ms
maxEvenDividing2 结果:65534 持续时间 75.0043ms
maxEvenConjunction 结果:65534 持续时间 109.0063ms
maxEvenConjunction2 结果:65534 持续时间 79.0045ms

最大阈值:131072
maxEvenDividing 结果:131070 持续时间 108.0061ms
maxEvenDividing2 结果:131070 持续时间 76.0044ms
maxEvenConjunction 结果:131070 持续时间 110.0063ms
maxEvenConjunction2 结果:131070 持续时间 80.0046ms

最大阈值:262144
maxEvenDividing 结果:262142 持续时间 110.0063ms
maxEvenDividing2 结果:262142 持续时间 76.0044ms
maxEvenConjunction 结果:262142 持续时间 107.0061ms
maxEvenConjunction2 结果:262142 持续时间 78.0044ms

最大阈值:524288
maxEvenDividing 结果:524286 持续时间 109.0062ms
maxEvenDividing2 结果:524286 持续时间 78.0045ms
maxEvenConjunction 结果:524286 持续时间 109.0062ms
maxEvenConjunction2 结果:524286 持续时间 80.0046ms

最大阈值:1048576
maxEvenDividing 结果:1048574 持续时间 109.0063ms
maxEvenDividing2 结果:1048574 持续时间 80.0045ms
maxEvenConjunction 结果:1048574 持续时间 114.0066ms
maxEvenConjunction2 结果:1048574 持续时间 78.0044ms

最大阈值:2097152
maxEvenDividing 结果:2097150 持续时间 111.0064ms
maxEvenDividing2 结果:2097150 持续时间 79.0045ms
maxEvenConjunction 结果:2097150 持续时间 112.0064ms
maxEvenConjunction2 结果:2097150 持续时间 77.0044ms

最大阈值:4194304
maxEvenDividing 结果:4194302 持续时间 111.0063ms
maxEvenDividing2 结果:4194302 持续时间 78.0045ms
maxEvenConjunction 结果:4194302 持续时间 111.0063ms
maxEvenConjunction2 结果:4194302 持续时间 77.0044ms

最大阈值:8388608
maxEvenDividing 结果:8388606 持续时间 109.0062ms
maxEvenDividing2 结果:8388606 持续时间 78.0045ms
maxEvenConjunction 结果:8388606 持续时间 114.0065ms
maxEvenConjunction2 结果:8388606 持续时间 78.0045ms

最大阈值:16777216
maxEvenDividing 结果:16777214 持续时间 109.0062ms
maxEvenDividing2 结果:16777214 持续时间 77.0044ms
maxEvenConjunction 结果:16777214 持续时间 109.0063ms
maxEvenConjunction2 结果:16777214 持续时间 77.0044ms

最大阈值:33554432
maxEvenDividing 结果:33554430 持续时间 113.0065ms
maxEvenDividing2 结果:33554430 持续时间 78.0045ms
maxEvenConjunction 结果:33554430 持续时间 110.0063ms
maxEvenConjunction2 结果:33554430 持续时间 80.0045ms

最大阈值:67108864
maxEvenDividing 结果:67108860 持续时间 112.0064ms
maxEvenDividing2 结果:67108860 持续时间 77.0044ms
maxEvenConjunction 结果:67108860 持续时间 112.0064ms
maxEvenConjunction2 结果:67108860 持续时间 80.0046ms

最大阈值:134217728
maxEvenDividing 结果:134217726 持续时间 109.0063ms
maxEvenDividing2 结果:134217726 持续时间 78.0044ms
maxEvenConjunction 结果:134217726 持续时间 114.0065ms
maxEvenConjunction2 结果:134217726 持续时间 81.0047ms

最大阈值:268435456
maxEvenDividing 结果:268435446 持续时间 111.0064ms
maxEvenDividing2 结果:268435446 持续时间 79.0045ms
maxEvenConjunction 结果:268435446 持续时间 114.0065ms
maxEvenConjunction2 结果:268435446 持续时间 79.0045ms

最大阈值:536870912
maxEvenDividing 结果:536870910 持续时间 107.0062ms
maxEvenDividing2 结果:536870910 持续时间 76.0043ms
maxEvenConjunction 结果:536870910 持续时间 109.0062ms
maxEvenConjunction2 结果:536870910 持续时间 80.0046ms

我找不到明确的解释为什么 Go 编译器不优化代码并且总是检查第二个条件,即使第一个条件为 false。 或者也许我的眼睛很模糊,我没有看到任何明显的错误? 或者需要给编译器提供一些特殊的指令? 我很高兴收到明智的评论。

PS: 是的,只是为了好玩,我在 Java 5 和 Java 7/8 上运行了类似的测试 - 一切都很清楚,执行时间是相同的。

来源: habr.com

添加评论