延遲調用(defer)確實是一種 「優雅」 機制。可簡化代碼,並確保即使發生 panic 依然會被執行。如將 panic/recover 比做 try/except,那麼 defer 彷佛可看作 finally。併發
如同異常保護被濫用同樣,defer 被無限制使用的例子比比皆是。函數
只需稍稍瞭解 defer 實現機制,就不難理解會有這樣的性能差別。性能
編譯器經過 runtime.deferproc 「註冊」 延遲調用,除目標函數地址外,還會複製相關參數(包括 receiver)。在函數返回前,執行 runtime.deferreturn 提取相關信息執行延遲調用。這其中的代價天然不是普通函數調用一條 CALL 指令所能比擬的。spa
或許你會以爲 4x 的性能差別算不得什麼,但若是是下面這樣呢?設計
當多個 goroutine 執行該函數時,只怕性能差別就不是 4x,還得算上 httpGet 所需時間。本來的併發設計,由於錯誤的 defer 調用變成 「串行」。對象
與之相似的,還有下面這樣的寫法。圖片
若是 files 是個 「超大」 列表,只怕在 analysis 結束前,會有不小的隱式 「資源泄露」,這些不能及時回收的對象,會致使 GC 在內的相關性能問題。資源
解決方法麼,要麼去掉 f.close 前的 defer,要麼將內層處理邏輯重構爲獨立函數(好比匿名函數調用)。編譯器
除此以外,單個函數裏過多的 defer 調用可嘗試合併。最起碼,在併發競爭激烈時,mutex.Unlock 不該該使用 defer,而應儘快執行,僅保護最短的代碼片斷。it
最新動態,請掃碼關注