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)+"\t\tduration %s", result.Duration)
// запускаем тест "конъюнкции"
result = maxEvenConjunction("maxEvenConjunction", arr)
log.Printf(result.Name+"\t result: "+fmt.Sprint(result.Value)+"\t\tduration %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
}
Получаем результат, на котором видно, что тем больше threshold, тем все чаще появляются флуктуации по части производительности.
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
Понятно, что в данном случае, для разных threshold мы имеем разные наборы тестовых данных, загрузка процессора (на моем ноутбуке i5-2540M) варьируется в районе 20..30%, память занимаемая приложением запущенным из под GoLand в среднем около 813Мб — это тоже влияет на достоверность результата, нужно реализовать сохранение тестовых наборов на диске и прогнать все тесты для каждого threshold изолировано друг от друга.
И вот раздумывая над тем как-бы с минимальными затратами все это реализовать, я машинально исправляю проверку условия
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
}
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
Внятного обьяснения, почему компилятор Go не оптимизирует код и всегда проверяет второе условие, даже если первое ложно — я не нашел. А может у меня просто глаз «замылился» и я не вижу какой то очевидной ошибки? Или надо указывать какие то особенные инструкции компилятору? Был бы рад толковым комментариям.
PS: Да, для интереса, прогнал аналогичные тесты на Java 5 и Java 7/8 — все четко, время выполнения одинаковое.
Комментарии (39)
Sap_ru
18.03.2019 13:16"if value > current && value&1 == 0 {"
А этот код точно делает то, что нужно?
Я про порядок операций. У "==" приоритет выше, чем у "&", есть такое мнение.
А вообще, код нужно тестировать. Хотя бы минимально, руками. Вы гоняете код, но не проверили правильность выдаваемого этим кодом результата ;)tuxi Автор
18.03.2019 13:25+1идею понял, поправил
if value > current && (value&1) == 0 { if (value&1) == 0 { и if value > current && (value%2) == 0 { if (value%2) == 0 {
но увы все осталосьmax threshold: 65536
maxEvenDividing result: 65534 duration 112.0064ms
maxEvenDividing2 result: 65534 duration 79.0045ms
maxEvenConjunction result: 65534 duration 109.0062ms
maxEvenConjunction2 result: 65534 duration 79.0046ms
max threshold: 131072
maxEvenDividing result: 131070 duration 110.0063ms
maxEvenDividing2 result: 131070 duration 77.0044ms
maxEvenConjunction result: 131070 duration 109.0062ms
maxEvenConjunction2 result: 131070 duration 82.0047ms
kmansoft
18.03.2019 13:22Я про порядок операций. У "==" приоритет выше, чем у "&", есть такое мнение.
Это как это?
https://golang.org/ref/spec#Operators
Precedence Operator 5 * / % << >> & &^ 4 + - | ^ 3 == != < <= > >= 2 && 1 ||
mrobespierre
18.03.2019 13:40Кто-то из основных разрабов Go писал: приоритет скорости компиляции, а не оптимизациям. Т.е. оптимизации тоже запиливаются, но не ценой скорости компиляции.
Cryvage
18.03.2019 14:26Тут речь не только об оптимизации. Если правая операция имеет побочные эффекты, то будет вообще разный результат.
upd: впрочем, вероятность такого сценария кажется крайне малой. Скорее всего дело в чём-то другом.
kmansoft
18.03.2019 14:23+3Я не автор но снова глянул в ассемблер, на этот раз с вариантами «2».
Насколько я вижу, разница вот в чём.
В исходном варианте если левая сторона ложна, то выполняется переход на конец внутреннего блока цикла. Выполняется сразу, но — на конец блока. И оттуда выполняется переход на начало блока, то есть на следующую итерацию.
В варианте с явным continue — выполняется переход сразу на начало блока, то есть на следующую итерацию.
Так как внутренность цикла очень короткая, то исключение одного перехода даёт заметную разницу.
И разница тем больше, чем больше таких значений что «value > current» является ложным (то есть мы пропускаем присваивание). На моём компе — аж в 3 раза!kmansoft
18.03.2019 14:46А теперь вот про это:
И разница тем больше, чем больше таких значений что «value > current» является ложным (то есть мы пропускаем присваивание).
Циклов с присваиванием — единицы. Вот данные (assign count — считается сколько раз сделали current = value):
threshold: 1048576 — assign count: 15
threshold: 2097152 — assign count: 17
threshold: 4194304 — assign count: 22
То есть вероятность выполнения присваивания — 1 к сто тысячам или миллиону.
Да мы вообще почти все итерации "пролетаем". Поэтому исключение одного перехода (на итерацию) так сильно ускоряет функцию в целом.
Мои выводы:
1 — Полезно и нужно смотреть ассемблер
2 — Полезно и нужно хорошо понять данные, т.к. они влияют на выполнение кода
PS — как ниже написал rafuck — вполне вероятно, что одна инструкция перехода лучше предсказывается процессором.
tuxi Автор
18.03.2019 14:51по 2 пункту — совершенно верно. Важно понять какое получается распределение чисел. Я давно хочу сравнить реализацию рандомайзера у go (пакет rand) и у пары реализаций в яве
homm
18.03.2019 17:02То есть ветка с
continue
всегда будет быстрее и нужно стараться делать её основной? Тогда что насчет такого варианта?
for _, value := range arr { if value <= current || value%2 == 1 { continue } current = value }
Он по идее должен быть так же быстр, как с двумя ифами.
ertaquo
18.03.2019 14:31+1Я поэкспериментировал с вашим кодом. Получился любопытный результат.
go version go1.11.2 linux/amd64
Для начала, ваша изначальная версия:
Вывод2019/03/18 16:09:47 initial array capacity: 100000000
2019/03/18 16:09:47 max threshold: 128
2019/03/18 16:09:51 maxEvenDividing result: 126 duration 89.412503ms
2019/03/18 16:09:52 maxEvenConjunction result: 126 duration 91.088461ms
2019/03/18 16:09:52 max threshold: 256
2019/03/18 16:09:57 maxEvenDividing result: 254 duration 89.081515ms
2019/03/18 16:09:57 maxEvenConjunction result: 254 duration 89.547376ms
2019/03/18 16:09:57 max threshold: 512
2019/03/18 16:10:02 maxEvenDividing result: 510 duration 88.149262ms
2019/03/18 16:10:02 maxEvenConjunction result: 510 duration 88.361855ms
2019/03/18 16:10:02 max threshold: 1024
2019/03/18 16:10:07 maxEvenDividing result: 1022 duration 91.8891ms
2019/03/18 16:10:07 maxEvenConjunction result: 1022 duration 89.751739ms
2019/03/18 16:10:07 max threshold: 2048
2019/03/18 16:10:11 maxEvenDividing result: 2046 duration 88.284615ms
2019/03/18 16:10:12 maxEvenConjunction result: 2046 duration 88.519161ms
2019/03/18 16:10:12 max threshold: 4096
tuxi Автор
18.03.2019 14:49Надо за памятью и CPU смотреть. Я поэтому и упомянул про следующий шаг — запуск всех тестов изолированно.
Еще пример где много интересных цифр вылезает: массив для передачи в функцию например можно было обьявить так
type numbers [size]int32
....
var arr = new(numbers)
там тоже много чего интересного можно накопать
еще я смотрел на конкуренцию за ресурсы запуская горутины и отдавая результаты через каналы
вот там просто поле непаханное для размышлений :)
MaximChistov
18.03.2019 15:17Технически, эквивалент
if value > current && value&1 == 0 { current = value }
Это
if value > current { if value&1 == 0 { current = value } }
а не ваше
if value <= current { continue; } if value&1 == 0 { current = value }
tuxi Автор
18.03.2019 15:23В контексте кол-ва операций внутри цикла, я обычно в работе использую паттерн с continue. Такой код прилично легче читается спустя какое то время, да и вложенность со скобками меньше.
Ну и как выяснили выше, разница все же есть
Stronix
18.03.2019 16:25Насколько я понимаю, тест можно было свести к такому виду: https://play.golang.org/p/zn-NaV8XE8k
offtopПредлагаю вместо
log.Println("initial array capacity: " + fmt.Sprint(size))
log.Printf("initial array capacity: %d\n", size)
Ну и вместоlog.Printf(result.Name+"\t result: "+fmt.Sprint(result.Value)+"\t\tduration %s", result.Duration)
log.Printf("%s\t result: %d\t\tduration %s\n", result.Name, result.Value, result.Duration)
homm
18.03.2019 17:10Получаем результат, на котором видно, что тем больше threshold, тем все чаще появляются флуктуации по части производительности.
Вообще не видно. И вообще не понятно, зачем вы тестируете на разных threshold, видно же что время от них почти не зависит. Это только уводит от поиска ответа
tuxi Автор
18.03.2019 18:21Так если бы я не перебирал бы эти пороги, я бы и не узнал бы, что время выполнения от них не сильно зависит. Это штатный рандомайзер вносит свою лепту. В этом примере, мы имеем равномерный закон распределения, но рандомайзер же можно взять и другой, да и пороги снизу и сверху тоже сделать несимметричными.
UPD: и потом, мне было интересно сравнить реальное время вычисления например 16%2 и 1073741822%2
ilyapirogov
18.03.2019 19:03А зачем такой сложный тест? Мне кажется было бы нагляднее, если упростить его до максимума. Тем более, что Golang предоставляет такой замечательный инструмент для тестирования из коробки.
package main import ( "testing" "time" ) var val1 = 10 var val2 = 1000 func longComparasion(val int) bool { time.Sleep(100 * time.Millisecond) return true } func BenchmarkComplexIfDiv(b *testing.B) { for i := 0; i < b.N; i++ { if val1 > val2 && val1%2 == 0 { } } } func BenchmarkComplexIfConj(b *testing.B) { for i := 0; i < b.N; i++ { if val1 > val2 && val1&1 == 0 { } } } func BenchmarkComplexLong(b *testing.B) { for i := 0; i < b.N; i++ { if val1 > val2 && longComparasion(val1) { } } } func BenchmarkTwoSimpleIf(b *testing.B) { for i := 0; i < b.N; i++ { if val1 <= val2 { continue } if val1%2 == 0 { } } } // $ go test -bench . // BenchmarkComplexIfDiv-8 2000000000 0.85 ns/op // BenchmarkComplexIfConj-8 2000000000 0.85 ns/op // BenchmarkComplexLong-8 2000000000 0.57 ns/op // BenchmarkTwoSimpleIf-8 2000000000 0.58 ns/op
Кстати, забавно, но BenchmarkComplexLong такой же быстрый как и вариант с continue
rafuck
18.03.2019 22:15Что-то мне всегда казалось, что нормальный компилятор все эти пустые условия с пустыми циклами должен изничтожить под корень, кроме BenchmarkComplexLong
ilyapirogov
18.03.2019 22:23Возможно он так и делает. В данном случае я больше хотел показать, что в Golang уже все необходимое для измерения производительности есть: golang.org/pkg/testing Так что нету необходимости придумывать велосипеды.
Sly_tom_cat
19.03.2019 15:32Опередил.
Я вот тоже не понял зачем нужен этот велосипед, когда есть go test -bench.tuxi Автор
19.03.2019 16:01Да не было цели изобрести велосипед, была цель проверить несколько предположений: скорость деления, равномерность распределения рандомайзера и тп. Я же не весь код показал, а только часть, которая вызвала вопросы.
Sly_tom_cat
19.03.2019 16:19go test -bench — и есть инструмент для проверки вопросов производительности кода/предположений о производительности.
Вот есть у меня функция, и кажется мне что можно сделать оптимальнее — пишу новую версию и два бенча (для старой и новой) и go test -bench мне четко говорит — прав я был или нет.
Если еще профилировщик подключить (test -bench -benchmem -cpuprofile cpu.out -memprofile mem.out), то там вам еще скажут «почему»/где именно тормозит/память жрет.
Так что не сами ваши эксперименты, но инструменты которые вы использовали для их проверки и есть самый настоящий велосипед с треугольными колесами. :)
Electrohedgehog
Есть этому внятное объяснение. Называется «приоритеты операторов».
tuxi Автор
Разве в Go не так?
Logical AND (&&): Left to right
Sap_ru
Посмотрите на "&" и "==".
Electrohedgehog
До выполнения && будут выполнены все операции с обеих его сторон так как их приоритет выше.
tuxi Автор
Ясно. Спасибо за объяснение
kmansoft
Нет. Только если нужно. Есть && и две стороны. Если левая ложна, то правая вообще не вычисляется.
Вот Вам официальная спека
https://golang.org/ref/spec#Logical_operators
Такая гарантия позволяет писать код вида
if n < array_length && array[n] > 0 {
}
Аналогично в других языках
if (obj != null && obj.value > 0) {
…
}
Я сейчас сделал компиляцию в ассемблер, там есть "short circuit" (насколько я смог понять, в листинге нет исходных строк, только символы).
go tool compile -S -N goboolean.go > goboolean.S
Electrohedgehog
Это в случае если операнд не выражение.
kmansoft
Нет. Есть логическая операция — && или ||.
У неё есть две стороны, два под-выражения. Спецификация языка гарантирует что правая сторона вычисляется только если необходимо.
И каких-либо дополнительных условий — о том что именно внутри левого и правого под-выражения — никаких нет (это было бы очень странно).
Вернёмся к исходной задаче. Посмотрел ещё раз ассемблер.
Модуль ( value%2 == 0 ) довольно дорогой, там 8 инструкций. А проверка бита ( value&1 == 0 ) выполняется одной инструкцией BTL.
Это объясняет "общую" разницу в производительности.
А насчёт вот этой ручной оптимизации ничего не могу сказать.
Нужно смотреть ассемблер и сравнивать с тем как было в одном условии с &&. Может быть автор статьи захочет копнуть глубже.
Electrohedgehog
Простите, я днище. Был не прав, всё так и есть
BugM
Вы это серьезно?
То есть стандартное:
if(i<arr.length && arr[i]>0)
будет кидать исключения?
kmansoft
Тут речь не о приоритетах вообще. С ними как раз ясно, вот это
a > b && c*2 > d
эквивалентно
(a > b) && (c*2 > d)
Речь о том что у нас в условии логическое (а не двоичное) И.
И если левая сторона ложна, то нет необходимости вычислять правую сторону. Это стандартная оптимизация во многих языках / компиляторах. А по бенчмаркам есть подозрение что вычисляются обе стороны, всегда.
tuxi Автор
Да, java например не вычисляет второе значение если первое ложно. Это меня и «погубило» в смысле и заставило написать эту заметку.
UPD: Поторопился я. Надо было прогнать на всем диапазоне тестов. Тоже есть разница.«Век живи, век учись»ertaquo
Странно. Решил проверить: play.golang.org/p/fjXUB-4em4q
Выполняет только первую часть условия, вторая сразу отбрасывается.
tuxi Автор
go1.11.2 под win7 64