Условите во Го и нивните чуда

Дали мислите дека овие две опции за тестирање на условите во јамката се еквивалентни во перформансите?

		
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 не го оптимизира кодот и секогаш го проверува вториот услов, дури и ако првиот е лажен. Или можеби очите ми се само заматени и не гледам очигледна грешка? Или треба да му дадете некои посебни инструкции на компајлерот? Ќе ми биде драго за разумни коментари.

PS: Да, само за забава, извршив слични тестови на Java 5 и Java 7/8 - сè е јасно, времето на извршување е исто.

Извор: www.habr.com

Додадете коментар