Комментарии 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 гораздо коварнее, чем принятно думать:
Как врожденные недостатки Go незаметно фальсифицируют Выборы https://www.linkedin.com/pulse/how-gos-inherent-flaws-can-insensibly-distort-results-derevyago-x7r8f
Это не 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 defer: что не сказали в книгах