Как стать автором
Обновить

Комментарии 20

А вы уверены что здесь компилятор вообще не выкинет ваш цикл с пустой функцией? (Которая без defer)

И как следует из статьи надо сравнивать:

file, _ := os.Open("file.go")

// do something

file.Close()

И вариант с defer

file, err := os.Open("file.go")
defer file.Close()

if err != nil {
	log.Fatal(err)
}

// do something

Спасибо, вы правы
обновил код

У вас очень большая разница получилась в бенчмарках. Пойду потестирую :-)

В любом случае, defer обычно используют в больших функциях, чтобы не забыть выполнить отложенную операцию, а они как правило все равно не инлайнятся :-)

поправил, спасибо

Как и обещал, у меня результаты с кодом из статьи вот такие:
withDefer: 263.026779ms
withoutDefer: 137.569092ms

О замедлении в 7-8 раз речи на конкретном примере не идет.

А вот реальный пример с открытием

package main

import (
	"fmt"
	"os"
	"time"
)

func withDefer() {
	file, err := os.Open("test.txt")

	if err != nil {
		panic(err)
	}

	defer file.Close()
}

func withoutDefer() {
	file, err := os.Open("test.txt")

	if err != nil {
		panic(err)
	}

	file.Close()
}

func main() {
	const iterations = 100_000_0

	start := time.Now()
	for i := 0; i < iterations; i++ {
		withDefer()
	}
	fmt.Printf("withDefer: %v\n", time.Since(start))

	start = time.Now()
	for i := 0; i < iterations; i++ {
		withoutDefer()
	}
	fmt.Printf("withoutDefer: %v\n", time.Since(start))
}


Результаты:

withDefer: 6.810516492s
withoutDefer: 6.829267096s

Пару раз получал, что с defer вообще быстрее.

Вывод - файловые операции, для которых крайне рекомендуют defer использовать внезапно не инлайнятся в любом случае, поэтому используйте defer и не парьтесь - влияния на производительность никакого нет.

Спасибо, предлагаю версус батл с господином @VladimirFarshatov

Я не против вашего батла с ним, начинайте :-)

имхо, ваш вариант с запросами к внешнему api, совсем неправильный. Числодробилка была бы значительно стабильнее

А зачем в числодробилке defer?

Хорошо, хотя странно это обьяснять: запрос к внешнему api, это не прогнозируемое время ответа, соответственно в тестах на производительность его использовать как минимум неудачная идея. Числодробилка (математика), тоже в теории может давать небольшой разброс, но определенности по времени здесь все же есть, поэтому для тестов подходит. defer здесь ни причем, но тесты на производительность с ним/без вы пытаетесь проводить.

А я точно спрашивал про то, про что вы отвечаете? :-)

Посыл статьи был в том, что: "Нам всю жизнь говорили, что надо использовать defer, чтобы не забыть закрыть ресурс, но функции с defer не инлайнятся, поэтому так делать на самом деле не надо, ибо производительность."

Что я хочу сказать: Те функции, где имеет смысл использовать defer, все равно не будут инлайниться, а продолбать закрытие файла очень легко. Поэтому используйте defer и не парьтесь, разницы в скорости не будет.

Понятно, что тест с числодробилкой более стабильный, но вы правда там будете defer использовать? К слову, у вас же в числодробилке наверняка есть какой-нибудь цикл, да? Или сложная логика.

package main

import (
	"fmt"
	"time"
)

func doSomethingWithResult() {
}

func withDefer() {
	var result int
	defer doSomethingWithResult()

	for i := 0; i <= 1000; i++ {
		result += 1
	}
}

func withoutDefer() {
	var result int

	for i := 0; i <= 1000; i++ {
		result += 1
	}

	doSomethingWithResult()
}

func main() {
	const iterations = 100_000_000

	start := time.Now()
	for i := 0; i < iterations; i++ {
		withDefer()
	}
	fmt.Printf("withDefer: %v\n", time.Since(start))

	start = time.Now()
	for i := 0; i < iterations; i++ {
		withoutDefer()
	}
	fmt.Printf("withoutDefer: %v\n", time.Since(start))
}

Пример числодробилки с defer (прости, господи). С моей колокольни разница в рамках погрешности измерения (причем в пользу defer, LOL)

withDefer: 2m44.10170003s
withoutDefer: 2m49.283898221s

Поверхностно знаком с Go, вот это непонятно:

Результаты:

go run main.go
withDefer: 89.974ms
withoutDefer: 132.4544ms

defer замедляет работу функции на порядок!

С defer 90ms, без него 133ms -> с defer быстрее в 1.5 раза -> defer ускоряет выполнение функции в 1.5 раза 🤔

Вообще, сам бенчмарк кажется некорректным

PS На порядок -- это обычно "в 10 раз"

Это не defer коварный, это люди не знают в чем разница между передачей по значению и по указателю.
Вроде бы одна звездочка, а какая разница в результатах!

func (s SomeStructure) addValue(value int) {
  s.value = value
}

func (s *SomeStructure) addValue(value int) {
  s.value = value
}

А что там неожиданного?
Все ровно так, как описан в документации defer.
И это не недостаток а преимущество. Просто defer-ом нужно уметь пользоваться.

Мой любимый кейс `defer f()()`

Это очень сильно ускоряет программу.

Насчёт "очень сильно" - момент спорный. "Инлайн" тоже не всегда соответствует ожиданиям. Современные процессоры весьма коварные, когда дело касается оптимизации и Go, поэтому вызов функции может стоить дешевле, чем развёрнутый копилятором Go "инлайн".

Например, компилируем (go1.24.0) код первого примера, где func withDefer(){ defer func() {}()} на RaspberryPi 4, ARM Cortex-A72:

Включен inline:

withDefer: 723.351364ms
withoutDefer: 435.683374ms

Выключен inline (go build -gcflags='-l')

withDefer: 786.459222ms
withoutDefer: 395.149118ms

С ассемблерным CALL (для ARM Cortex - BL, на самом деле ), - то есть, без inline, - для отсутствующего defer, всё равно получилось, как минимум, не медленнее (вообще, по коду выглядит так, что будет даже несколько быстрее).

Вариант с defer - понятно, что медленнее в любом случае, но defer приносит с собой важные семантические преимущества.

На порядок - это не в два раза (кроме двоичной системы счисления). Обычно в 10 раз.

Из за тенденции перехода программистов php на go
Моё мнение, программисту PHP желательно знать GO lang

Зарегистрируйтесь на Хабре, чтобы оставить комментарий

Публикации