Оптимизация производительности языка Go — для исследования производительности Range

задняя часть Go GitHub переводчик

Если мы хотим просмотреть массив, коллекцию карт, фрагмент среза и т. д., язык Go (Golang) предоставляет нам более полезный метод For Range. Range – это ключевое слово, представляющее диапазон. Его можно использовать вместе с for для перебора коллекций, таких как массивы и Карты. Его использование лаконично, а карта, канал и т. д. также используются для диапазона, поэтому в кодировании мы используемfor rangeВыполнение итераций цикла - это самое то. Для этой наиболее часто используемой итерации, особенно иfor i=0;i<N;i++Как производительность в сравнении? Разберем следующий пример, давайтеfor rangeБолее глубокое понимание циклов позволяет нам писать программы с более высокой производительностью.

Основное использование

for rangeИспользование очень простое, и здесь демонстрируется использование следующих двух типов коллекций.

package main

import "fmt"

func main() {
	ages:=[]string{"10", "20", "30"}

	for i,age:=range ages{
		fmt.Println(i,age)
	}
}

Это для итеративного использования слайсов Slice, используяrangeключевое слово возвращает две переменныеi,age, первое — это индекс слайса Slice, а второе — содержимое слайса Slice, поэтому мы печатаем:

0 10
1 20
2 30

О нарезке Slice в языке Go вы можете обратиться к этой статье, которую я написал ранее.Боевые заметки на языке Go (5) | Go slice

Давайте посмотрим на карту (словарь) нижеfor rangeИспользуйте примеры.

package main

import "fmt"

func main() {
	ages:=map[string]int{"张三":15,"李四":20,"王武":36}

	for name,age:=range ages{
		fmt.Println(name,age)
	}
}

в настоящее время используетfor rangeПри переборе карты первая возвращаемая переменнаяkey, вторая переменнаяvalue, что соответствуетnameиages. Давайте запустим программу и посмотрим на результат.

张三 15
李四 20
王武 36

Здесь следует отметить, что,for range mapвозвращениеK-VПорядок пар ключ-значение не фиксирован, он случайный, на этот раз он может быть张三-15Первый появится, следующий запуск может быть王武-36Печатается первый. Для получения более подробной информации о Map, пожалуйста, обратитесь к моей предыдущей статьеБоевые заметки о языке Go (6) | Go Map.

Регулярное сравнение циклов

Например, для срезов Slice у нас есть две итерации: одна обычнаяfor i:=0;i<N;i++путь; одинfor rangeКстати, ниже мы рассмотрим производительность двух итераций.

func ForSlice(s []string) {
	len := len(s)
	for i := 0; i < len; i++ {
		_, _ = i, s[i]
	}
}

func RangeForSlice(s []string) {
	for i, v := range s {
		_, _ = i, v
	}
}

Для тестирования я написал эти две функции, которые зацикливают и повторяют срезы Slice.С точки зрения реализации, их логика одинакова, что гарантирует, что мы можем тестировать в той же ситуации.

import "testing"

const N  =  1000

func initSlice() []string{
	s:=make([]string,N)
	for i:=0;i<N;i++{
		s[i]="www.flysnow.org"
	}
	return s;
}

func BenchmarkForSlice(b *testing.B) {
	s:=initSlice()

	b.ResetTimer()
	for i:=0; i<b.N;i++  {
		ForSlice(s)
	}
}

func BenchmarkRangeForSlice(b *testing.B) {
	s:=initSlice()

	b.ResetTimer()
	for i:=0; i<b.N;i++  {
		RangeForSlice(s)
	}
}

Варианты использования этого теста Bench находятся в одной и той же ситуации, моделируя обход среза длиной 1000. Затем мы бежимgo test -bench=. -run=NONEПосмотреть результаты теста производительности.

BenchmarkForSlice-4              5000000    287 ns/op
BenchmarkRangeForSlice-4         3000000    509 ns/op

Как видно из теста производительности, обычный цикл for более эффективен, чемfor rangeПроизводительностьfor rangeКаждый раз это копия элементов цикла, поэтому чем сложнее бюджет в коллекции, тем хуже производительность.В отличие от обычного цикла for, он получает элементы в коллекции черезs[i], этот метод ссылки на указатель индекса намного выше, чем производительность копирования.

Поскольку это проблема копирования элементов, цель итерации срезов Slice — получение элементов, тогда мы реализуем это другим способом.for range.

func RangeForSlice(s []string) {
	for i, _ := range s {
		_, _ = i, s[i]
	}
}

Теперь давайте снова запустим тест производительности Benchmark, чтобы увидеть результат.

BenchmarkForSlice-4              5000000    280 ns/op
BenchmarkRangeForSlice-4         5000000    277 ns/op

Ну, как мы и думали, производительность поднялась, и она на уровне обычного цикла for. Причина в том, что мы проходим_отбрасывает копирование элемента, затем проходитs[i]Получение элементов итерации повышает производительность и служит цели.

Обход карты

Для карты мы не можем использоватьfor i:=0;i<N;i++Кстати, если у вас есть всеkeyЗа исключением списка элементов, поэтому в большинстве случаев мы используемfor rangeПуть.

func RangeForMap1(m map[int]string) {
	for k, v := range m {
		_, _ = k, v
	}
}

const N = 1000

func initMap() map[int]string {
	m := make(map[int]string, N)
	for i := 0; i < N; i++ {
		m[i] = fmt.Sprint("www.flysnow.org",i)
	}
	return m
}

func BenchmarkRangeForMap1(b *testing.B) {
	m:=initMap()

	b.ResetTimer()
	for i := 0; i < b.N; i++ {
		RangeForMap1(m)
	}
}

www.flysnow.org/

Беспощадный блог Blizzard

Эта статья является оригинальной статьей, перепечатайте ее и укажите источник: «Всегда есть плохие люди, которые удаляют мое первоначальное описание, когда берут статью».flysnow_orgили сайтwww.flysnow.org/, и впервые прочитал последующие замечательные статьи. «Замечания против гнилых людей **...&*¥» Если вы считаете, что это хорошо, поделитесь им в кругу друзей, спасибо за вашу поддержку.

Приведенный выше пример — это функция обхода карты и бенчмарк-тест. Я написал их вместе. Запустите тест, чтобы увидеть эффект.

BenchmarkForSlice-8              5000000    298 ns/op
BenchmarkRangeForSlice-8         3000000    475 ns/op
BenchmarkRangeForMap1-8           100000    14531 ns/op

По сравнению со Slice производительность обхода карты хуже, что можно назвать ужасающей. Что ж, приступим к оптимизации, и идея в том, чтобы уменьшить ценность копирования. Причина, по которой RangeForSlice в тесте тоже медленный, заключается в том, что я восстановил RangeForSlice до достойной копии, чтобы сравнить производительность.

func RangeForMap2(m map[int]string) {
	for k, _ := range m {
		_, _ = k, m[k]
	}
}

func BenchmarkRangeForMap2(b *testing.B) {
	m := initMap()

	b.ResetTimer()
	for i := 0; i < b.N; i++ {
		RangeForMap2(m)
	}
}

Запустите тест производительности еще раз, чтобы увидеть эффект.

BenchmarkForSlice-8              5000000               298 ns/op
BenchmarkRangeForSlice-8         3000000               475 ns/op
BenchmarkRangeForMap1-8           100000             14531 ns/op
BenchmarkRangeForMap2-8           100000             23199 ns/op

Эм, вы нашли что-то не так, методBenchmarkRangeForMap2Производительность значительно упала, что видно по затратам времени на каждую операцию (хотя количество запусков теста производительности в секунду осталось прежним). В отличие от Slice, который мы тестировали выше, на этот раз не только не улучшилось, но и ухудшилось.

продолжить редактированиеMap2Реализация функции такова:

func RangeForMap2(m map[int]Person) {
	for  range m {
	}
}

Ничего не делайте, просто повторите и снова запустите тест производительности.

BenchmarkForSlice-8              5000000               301 ns/op
BenchmarkRangeForSlice-8         3000000               478 ns/op
BenchmarkRangeForMap1-8           100000             14822 ns/op
BenchmarkRangeForMap2-8           100000             14215 ns/op

*Мы поражены тем, что ничего не делаем, и получаемK-VПроизводительность операции значения такая же, и она полностью отличается от Slice, если не сказатьfor rangeА как насчет эффективности потери копии? Куда все ушли? Угадайте все, вы можете комбинировать принципы следующего раздела для достижения

по принципу диапазона

просмотревGitHub.com/go wave/go fr…исходный код, мы можем найтиfor rangeРеализация:

// Arrange to do a loop appropriate for the type.  We will produce
  //   for INIT ; COND ; POST {
  //           ITER_INIT
  //           INDEX = INDEX_TEMP
  //           VALUE = VALUE_TEMP // If there is a value
  //           original statements
  //   }

И есть разные реализации компиляции для Slice, Map и т. д., давайте сначала посмотримfor range sliceконкретная реализация

  // The loop we generate:
  //   for_temp := range
  //   len_temp := len(for_temp)
  //   for index_temp = 0; index_temp < len_temp; index_temp++ {
  //           value_temp = for_temp[index_temp]
  //           index = index_temp
  //           value = value_temp
  //           original body
  //   }

Сначала сделайте копию обходимого среза, получите длину, а затем используйте обычныйforЦикл проходится и возвращается копия значения.

посмотри сноваfor range mapКонкретная реализация:

  // The loop we generate:
  //   var hiter map_iteration_struct
  //   for mapiterinit(type, range, &hiter); hiter.key != nil; mapiternext(&hiter) {
  //           index_temp = *hiter.key
  //           value_temp = *hiter.val
  //           index = index_temp
  //           value = value_temp
  //           original body
  //   }

первый справаmapинициализируется, потому чтоmapда*hashmap, так что вот на самом деле*hashmapКопия указателя.

Сочетание этих двух конкретныхfor rangeРеализация компилятора, вы можете понять, почемуfor range sliceиз_оптимизация полезна, аfor range mapкстати бесполезно? Добро пожаловать, чтобы оставить сообщение, чтобы ответить.

Эта статья является оригинальной статьей, перепечатайте ее и укажите источник: «Всегда есть плохие люди, которые удаляют мое первоначальное описание, когда берут статью».flysnow_orgили сайтwww.flysnow.org/, и впервые прочитал последующие замечательные статьи. «Замечания против гнилых людей **...&*¥» Если вы считаете, что это хорошо, поделитесь им в кругу друзей, спасибо за вашу поддержку.

扫码关注