Почему метод дженерик типа в го существенно медленнее нативного?

Ведь по заявлению разработчиков дженерики собираются в отдельные типы на стадии компиляции, а не в рантайме.
Результат работы бенчмарков выдает либо не оптимизированную реализацию, либо жесткий баг, либо я не правильно понял слова разработчиков.
Демонстрирую на игрушечном примере функции, суть которой проста:
func sumincr(arr []float64, incr float64) (sum float64) {
	for _, x := range arr {
		sum += x + incr
	}
	return sum
}

Код бенчей в песочнице по причинам объёма
https://go.dev/play/p/3wYzYzsIT_8
Результаты: go test -bench=. -count=2
goos: windows
goarch: amd64
pkg: github.com/ogau/evo/bugtesting
cpu: AMD Ryzen 3 PRO 2200G with Radeon Vega Graphics
BenchmarkNativeFunc-4            4113319               298.7 ns/op
BenchmarkNativeFunc-4            3958936               295.3 ns/op
BenchmarkNativeMethod-4          4124042               290.2 ns/op
BenchmarkNativeMethod-4          3952284               295.9 ns/op
BenchmarkGenericFunc-4           2836564               409.9 ns/op
BenchmarkGenericFunc-4           2877542               423.8 ns/op
BenchmarkGenericMethod-4          443005              2628 ns/op
BenchmarkGenericMethod-4          399085              2555 ns/op

Несложно посчитать, что функция полностью использующая механизм обобщения с методом типа расходует в ~8.5 раз больше процессорного времени.
Кстати, что меня при тестах случайно поразило - если поменять порядок проведения бенчмарков, то результаты становятся непредсказуемыми (перепроверено много раз):
goos: windows
goarch: amd64
pkg: github.com/ogau/evo/bugtesting
cpu: AMD Ryzen 3 PRO 2200G with Radeon Vega Graphics
BenchmarkGenericMethod-4          476750              2654 ns/op
BenchmarkGenericMethod-4          427164              2593 ns/op
BenchmarkNativeFunc-4            2104059               571.9 ns/op
BenchmarkNativeFunc-4            2003337               565.6 ns/op
BenchmarkNativeMethod-4          2121109               579.8 ns/op
BenchmarkNativeMethod-4          2095996               575.0 ns/op
BenchmarkGenericFunc-4           2091612               574.3 ns/op
BenchmarkGenericFunc-4           2085957               574.6 ns/op

Вопрос продублирую на StackOverflow, ссылку прикреплю позже.
UPD: https://stackoverflow.com/questions/71649364/why-i...
  • Вопрос задан
  • 635 просмотров
Решения вопроса 1
hugga
@hugga Автор вопроса
Из обсуждения https://github.com/golang/go/issues/50182 видно что проблема с производительностью generics не новая.
При запуске бенчмарков с флагами получается ожидаемое мной ± равное время выполнения ф-ций:
go test -bench=. -count=2 -gcflags=all=-d=unified

BenchmarkNativeFunc-4            2029149               600.5 ns/op
BenchmarkNativeFunc-4            2018277               591.7 ns/op
BenchmarkNativeMethod-4          1962086               592.0 ns/op
BenchmarkNativeMethod-4          2024234               603.9 ns/op
BenchmarkGenericFunc-4           1985313               573.4 ns/op
BenchmarkGenericFunc-4           2053454               585.1 ns/op
BenchmarkGenericMethod-4         2067790               591.3 ns/op
BenchmarkGenericMethod-4         2054346               612.2 ns/op

что форсирует применение при компиляции stenciling (трафарет по отдельным типам) вместо словарного подхода реализации дженериков (текущая реализация).
Остаётся ждать оптимизаций в обновлениях языка
Ответ написан
Комментировать
Пригласить эксперта
Ответы на вопрос 1
yellow79
@yellow79
Senior Software Engineer
Полагаю, что проблема с реализацией. Нет желания искать в ваших функциях кто прав, кто виноват. давайте так, вы пишите две реализации одного и тоже действия, но одно дженерики, другое по старинке. После напишите бенчмарк к этим двум функциям.

Судя по результатам бенчмарков, результаты с дженериками потребляют сильно больше памяти, причина 100% в этом
Ответ написан
Ваш ответ на вопрос

Войдите, чтобы написать ответ

Похожие вопросы