Программирование на языке Go: полезные советы. Часть 3
Предлагаем вашему вниманию последний блок полезных советов, которые будут интересны разработчикам Go. Предыдущие части находятся здесь и здесь. Материал является переводом статьи «Go Tips 101», который мы подготовили специально для вас.
1. Как определить фактическое (в зависимости от платформы) значение размера типа word на этапе компиляции?
Данный способ не является специфическим для языка Go:
const Is64bitArch = ^uint(0) >> 63 == 1 const Is32bitArch = ^uint(0) >> 63 == 0 const WordBits = 32 << (^uint(0) >> 63) // 64 or 32
2. Как объявить константы, равные максимальным значениям типов uint и int?
Это делается довольно просто:
const MaxUint = ^uint(0) const MaxInt = int(^uint(0) >> 1)
3. Избегайте автоматического преобразования значений типов больших размеров в значения интерфейсных типов
При присвоении значения неинтерфейсного типа переменной интерфейсного типа создаваемая копия значения неинтерфейсного типа будет автоматически преобразована в значение интерфейсного типа. Затраты вычислительных ресурсов на подобное копирование зависят от размера неинтерфейсного типа. Чем больше размер, тем дороже обходится операция копирования. Поэтому настоятельно рекомендуется избегать автоматического преобразования значений типов больших размеров в значения интерфейсных типов.
В примере ниже вызов двух первых функций вывода осуществляется значительно медленнее, чем вызов двух последних.
package main import "fmt" func main() { var a [1000]int // Ресурсоемкий код: fmt.Println(a) // значение a копируется fmt.Printf("Type of a: %T\n", a) // значение a копируется // The cost of the two lines is low. fmt.Printf("%v\n", a[:]) fmt.Println("Type of a:", fmt.Sprintf("%T", &a)[1:]) }
Подробную информацию о размерах переменных разных типов и ресурсоемкости операций по их копированию см. в этой статье.
4. Оптимизируйте свой код с учётом механизма исключения необязательных проверок границ (bounds check elimination, BCE).
Для начала ознакомьтесь с подробной информацией о механизме BCE и поддержке BCE стандартным компилятором Go в этой статье.
Теперь давайте приведём дополнительный пример:
package main import ( "strings" "testing" ) func NumSameBytes_1(x, y string) int { if len(x) > len(y) { x, y = y, x } for i := 0; i < len(x); i++ { if x[i] != y[i] { return i } } return len(x) } func NumSameBytes_2(x, y string) int { if len(x) > len(y) { x, y = y, x } if len(x) <= len(y) { // более длинный, но более эффективный код for i := 0; i < len(x); i++ { if x[i] != y[i] { // проверка границ не выполняется return i } } } return len(x) } var x = strings.Repeat("hello", 100) + " world!" var y = strings.Repeat("hello", 99) + " world!" func BenchmarkNumSameBytes_1(b *testing.B) { for i := 0; i < b.N; i++ { _ = NumSameBytes_1(x, y) } } func BenchmarkNumSameBytes_2(b *testing.B) { for i := 0; i < b.N; i++ { _ = NumSameBytes_2(x, y) } }
В примере выше функция
BenchmarkNumSameBytes_1-4 10000000 669 ns/op BenchmarkNumSameBytes_2-4 20000000 450 ns/op
Следует иметь в виду, что каждая новая версия стандартного компилятора Go (gc) содержит множество изменений, повышающих производительность. Оптимизация из приведенного выше примера не работает в Go SDK версий ниже 1.11. С другой стороны, вполне возможно, что в следующих более совершенных версиях gc отпадёт необходимость в использовании подобных приемов.