Обновить

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

А вы уверены что здесь компилятор вообще не выкинет ваш цикл с пустой функцией? (Которая без 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?

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

так это сугубо ваше личное мнение, зачем засорять раздел материалом, который к нему не имеет никакого отношения?

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

Публикации