Pensi che queste due opzioni per testare le condizioni all'interno di un ciclo siano equivalenti in termini di prestazioni?
if a > b && c*2 > d {
....
}
// и
if a <= b {
continue;
}
if c*2 > d {
....
}
Tutto è iniziato con un “riscaldamento cerebrale”; dovevo fornire un esempio di ricerca ottimale del numero pari più grande in un array di numeri interi [-x....x]. Mi chiedevo quanto sarebbero migliori le prestazioni se utilizzassi la moltiplicazione logica per 1 per capire se un numero è pari o meno.
//у четных чисел последний бит всегда равен 0
value & 1 == 0
//vs классический метод
value % 2 == 0
La mia esperienza di programmazione in Go non è molto ampia, poco più di un anno e mezzo, l'ho usato, anche se spesso, ma puramente per scopi utilitaristici (beh, forse tranne un progetto relativo a un servizio http ad alto carico), quindi ho iniziato con esso. Apri GoLand e scrivi un semplice 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
}
Otteniamo un risultato che mostra che quanto più alta è la soglia, tanto più spesso si verificano fluttuazioni delle prestazioni.
Confrontomax 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
È chiaro che in questo caso, per soglie diverse abbiamo set di dati di test diversi, il carico del processore (sul mio laptop i5-2540M) varia intorno al 20..30%, la memoria occupata dall'applicazione in esecuzione da GoLand è in media circa 813 MB: anche questo influisce sull'affidabilità del risultato, è necessario salvare i casi di test su disco ed eseguire tutti i test per ciascuna soglia separatamente gli uni dagli altri.
E ora, pensando a come implementare tutto questo con costi minimi, correggo automaticamente il controllo delle condizioni
if value > current && value&1 == 0 {
current = value
}
su
if value <= current {
continue;
}
if value&1 == 0 {
current = value
}
Rieseguo i test... e non ci capisco più nulla :)
Il tempo impiegato per l'esecuzione inizia a differire non più di percentuali/frazioni percentuali, ma del 10...15%. Aggiungo rapidamente altri 2 test:
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
}
Lo eseguo e ottengo questa immagine:capacità iniziale dell'array: 100000000
soglia massima: 128
maxEvenRisultato della divisione: 126 durata 116.0066 ms
maxEvenDividing2 risultato: 126 durata 79.0045 ms
maxEvenCongiunzione risultato: 126 durata 114.0065ms
risultato maxEvenCongiunzione2: 126 durata 83.0048 ms
soglia massima: 256
maxEvenRisultato della divisione: 254 durata 111.0063 ms
maxEvenDividing2 risultato: 254 durata 77.0044 ms
maxEvenCongiunzione risultato: 254 durata 110.0063ms
risultato maxEvenCongiunzione2: 254 durata 80.0046 ms
soglia massima: 512
maxEvenRisultato della divisione: 510 durata 114.0066 ms
maxEvenDividing2 risultato: 510 durata 80.0045 ms
maxEvenCongiunzione risultato: 510 durata 110.0063ms
risultato maxEvenCongiunzione2: 510 durata 80.0046 ms
soglia massima: 1024
maxEvenRisultato della divisione: 1022 durata 109.0063 ms
maxEvenDividing2 risultato: 1022 durata 77.0044 ms
maxEvenCongiunzione risultato: 1022 durata 111.0063ms
risultato maxEvenCongiunzione2: 1022 durata 81.0047 ms
soglia massima: 2048
maxEvenRisultato della divisione: 2046 durata 114.0065 ms
maxEvenDividing2 risultato: 2046 durata 79.0045 ms
maxEvenCongiunzione risultato: 2046 durata 113.0065ms
risultato maxEvenCongiunzione2: 2046 durata 81.0046 ms
soglia massima: 4096
maxEvenRisultato della divisione: 4094 durata 114.0065 ms
maxEvenDividing2 risultato: 4094 durata 80.0046 ms
maxEvenCongiunzione risultato: 4094 durata 111.0063ms
risultato maxEvenCongiunzione2: 4094 durata 78.0045 ms
soglia massima: 8192
maxEvenRisultato della divisione: 8190 durata 107.0062 ms
maxEvenDividing2 risultato: 8190 durata 77.0044 ms
maxEvenCongiunzione risultato: 8190 durata 111.0063ms
risultato maxEvenCongiunzione2: 8190 durata 77.0044 ms
soglia massima: 16384
maxEvenRisultato della divisione: 16382 durata 109.0063 ms
maxEvenDividing2 risultato: 16382 durata 77.0044 ms
maxEvenCongiunzione risultato: 16382 durata 108.0062ms
risultato maxEvenCongiunzione2: 16382 durata 77.0044 ms
soglia massima: 32768
maxEvenRisultato della divisione: 32766 durata 112.0064 ms
maxEvenDividing2 risultato: 32766 durata 77.0044 ms
maxEvenCongiunzione risultato: 32766 durata 109.0062ms
risultato maxEvenCongiunzione2: 32766 durata 78.0045 ms
soglia massima: 65536
maxEvenRisultato della divisione: 65534 durata 109.0062 ms
maxEvenDividing2 risultato: 65534 durata 75.0043 ms
maxEvenCongiunzione risultato: 65534 durata 109.0063ms
risultato maxEvenCongiunzione2: 65534 durata 79.0045 ms
soglia massima: 131072
maxEvenRisultato della divisione: 131070 durata 108.0061 ms
maxEvenDividing2 risultato: 131070 durata 76.0044 ms
maxEvenCongiunzione risultato: 131070 durata 110.0063ms
risultato maxEvenCongiunzione2: 131070 durata 80.0046 ms
soglia massima: 262144
maxEvenRisultato della divisione: 262142 durata 110.0063 ms
maxEvenDividing2 risultato: 262142 durata 76.0044 ms
maxEvenCongiunzione risultato: 262142 durata 107.0061ms
risultato maxEvenCongiunzione2: 262142 durata 78.0044 ms
soglia massima: 524288
maxEvenRisultato della divisione: 524286 durata 109.0062 ms
maxEvenDividing2 risultato: 524286 durata 78.0045 ms
maxEvenCongiunzione risultato: 524286 durata 109.0062ms
risultato maxEvenCongiunzione2: 524286 durata 80.0046 ms
soglia massima: 1048576
maxEvenRisultato della divisione: 1048574 durata 109.0063 ms
maxEvenDividing2 risultato: 1048574 durata 80.0045 ms
maxEvenCongiunzione risultato: 1048574 durata 114.0066ms
risultato maxEvenCongiunzione2: 1048574 durata 78.0044 ms
soglia massima: 2097152
maxEvenRisultato della divisione: 2097150 durata 111.0064 ms
maxEvenDividing2 risultato: 2097150 durata 79.0045 ms
maxEvenCongiunzione risultato: 2097150 durata 112.0064ms
risultato maxEvenCongiunzione2: 2097150 durata 77.0044 ms
soglia massima: 4194304
maxEvenRisultato della divisione: 4194302 durata 111.0063 ms
maxEvenDividing2 risultato: 4194302 durata 78.0045 ms
maxEvenCongiunzione risultato: 4194302 durata 111.0063ms
risultato maxEvenCongiunzione2: 4194302 durata 77.0044 ms
soglia massima: 8388608
maxEvenRisultato della divisione: 8388606 durata 109.0062 ms
maxEvenDividing2 risultato: 8388606 durata 78.0045 ms
maxEvenCongiunzione risultato: 8388606 durata 114.0065ms
risultato maxEvenCongiunzione2: 8388606 durata 78.0045 ms
soglia massima: 16777216
maxEvenRisultato della divisione: 16777214 durata 109.0062 ms
maxEvenDividing2 risultato: 16777214 durata 77.0044 ms
maxEvenCongiunzione risultato: 16777214 durata 109.0063ms
risultato maxEvenCongiunzione2: 16777214 durata 77.0044 ms
soglia massima: 33554432
maxEvenRisultato della divisione: 33554430 durata 113.0065 ms
maxEvenDividing2 risultato: 33554430 durata 78.0045 ms
maxEvenCongiunzione risultato: 33554430 durata 110.0063ms
risultato maxEvenCongiunzione2: 33554430 durata 80.0045 ms
soglia massima: 67108864
maxEvenRisultato della divisione: 67108860 durata 112.0064 ms
maxEvenDividing2 risultato: 67108860 durata 77.0044 ms
maxEvenCongiunzione risultato: 67108860 durata 112.0064ms
risultato maxEvenCongiunzione2: 67108860 durata 80.0046 ms
soglia massima: 134217728
maxEvenRisultato della divisione: 134217726 durata 109.0063 ms
maxEvenDividing2 risultato: 134217726 durata 78.0044 ms
maxEvenCongiunzione risultato: 134217726 durata 114.0065ms
risultato maxEvenCongiunzione2: 134217726 durata 81.0047 ms
soglia massima: 268435456
maxEvenRisultato della divisione: 268435446 durata 111.0064 ms
maxEvenDividing2 risultato: 268435446 durata 79.0045 ms
maxEvenCongiunzione risultato: 268435446 durata 114.0065ms
risultato maxEvenCongiunzione2: 268435446 durata 79.0045 ms
soglia massima: 536870912
maxEvenRisultato della divisione: 536870910 durata 107.0062 ms
maxEvenDividing2 risultato: 536870910 durata 76.0043 ms
maxEvenCongiunzione risultato: 536870910 durata 109.0062ms
risultato maxEvenCongiunzione2: 536870910 durata 80.0046 ms
Non sono riuscito a trovare una spiegazione chiara del motivo per cui il compilatore Go non ottimizza il codice e controlla sempre la seconda condizione, anche se la prima è falsa. O forse i miei occhi sono semplicemente sfocati e non vedo alcun errore evidente? Oppure è necessario fornire alcune istruzioni speciali al compilatore? Sarei felice di ricevere commenti sensati.
PS: Sì, solo per divertimento, ho eseguito test simili su Java 5 e Java 7/8: tutto è chiaro, il tempo di esecuzione è lo stesso.
Fonte: habr.com