panic 和 recover 在 Go 官方的 JSON 包中也有使用。但是我一直这种直接 panic 并 recover 的方式性能如何,所以有了这次测试。
先说结论:
goos: darwin
goarch: arm64
pkg: go-recover-test
cpu: Apple M1
BenchmarkErrorReturn-8 1000000000 0.3197 ns/op 0 B/op 0 allocs/op
BenchmarkErrorReturnWithDefer-8 576616402 2.110 ns/op 0 B/op 0 allocs/op
BenchmarkPanicRecover-8 16081614 74.77 ns/op 0 B/op 0 allocs/op
BenchmarkErrorsAs-8 33246062 37.72 ns/op 0 B/op 0 allocs/op
BenchmarkErrorsAsWrapped-8 23144929 49.71 ns/op 0 B/op 0 allocs/op
BenchmarkTypeAssertion-8 1000000000 0.3166 ns/op 0 B/op 0 allocs/op
BenchmarkReturnBusinessError-8 1000000000 0.4713 ns/op 0 B/op 0 allocs/op
BenchmarkPanicRecoverBusinessError-8 16641386 96.30 ns/op 0 B/op 0 allocs/op
PASS
ok go-recover-test 8.533s
基础错误处理性能
- 错误返回性能:
- 普通 error 返回:~0.32 纳秒/操作
- 带 defer 的错误返回:~2.07 纳秒/操作
- panic/recover:~74.10 纳秒/操作
- 错误类型检查性能:
- 普通类型断言:~0.32 纳秒/操作
- errors.As(直接错误):~36.32 纳秒/操作
- errors.As(包装错误):~48.62 纳秒/操作
- 自定义错误处理性能:
- 返回自定义业务错误:~0.48 纳秒/操作
- panic/recover 自定义业务错误:~73.39 纳秒/操作
主要结论
错误返回方式的性能差异:
- 直接返回 error 是最快的(亚纳秒级)
- defer 会带来约 6 倍的性能开销
- panic/recover 的开销最大,比直接返回慢约 230 倍
错误类型检查的性能差异:
- 直接类型断言最快(与普通错误返回相当)
- errors.As 有显著开销(约 36-49 纳秒)
- 错误包装会进一步增加 errors.As 的开销(约 34% 的额外开销)
自定义错误的影响:
- 使用自定义错误类型的开销很小(比简单错误多约 0.15 纳秒)
- 自定义错误的复杂度(字段数量)对性能影响很小
- panic/recover 的性能开销主要来自机制本身,与错误类型关系不大
实践建议
- 日常错误处理:
- 优先使用返回 error 的方式
- 不用担心自定义错误类型带来的性能影响
- 需要时可以放心使用 defer,其开销在大多数场景下可以接受
- 错误类型检查:
- 如果知道具体错误类型,优先使用类型断言
- 需要处理错误包装链时才使用 errors.As
- 避免在热点代码路径中过度使用 errors.As
- panic/recover 使用:
- 仅用于真正的异常情况
- 不适合用作常规错误处理机制
- 适用于程序初始化或不可恢复的错误场景
性能优化建议
- 在热点代码路径中避免使用 panic/recover
- 如果需要频繁检查错误类型,优先使用类型断言
- 在错误处理的性能要求不高的场景,可以优先考虑代码的清晰度和可维护性
panic 和 recover 不能随便用,但是 errors.As 虽然也有性能开销,但是那一点也无所谓,因为这对于错误的传播非常有帮助。比如我就自定义了个 HTTPError,我可以直接返回这个错误,然后再进行统一的错误处理。比如直接返回一个 HTTPError,在错误处理时,这个错误肯定是可以被用户知道的。但是如果你返回了其他类型的错误,比如数据库错误,那么这个错误肯定是要被遮蔽的,需要通过日志进行输出,而不是告知用户。
测试代码
package main
import (
"errors"
"fmt"
"testing"
)
// 自定义错误类型
type CustomError struct {
msg string
}
func (e *CustomError) Error() string {
return e.msg
}
// 另一个自定义错误类型,用于测试性能
type BusinessError struct {
Code int
Message string
}
func (e *BusinessError) Error() string {
return fmt.Sprintf("error code: %d, message: %s", e.Code, e.Message)
}
var (
errTest = errors.New("test error")
customErr = &CustomError{msg: "custom error"}
wrappedErr = fmt.Errorf("wrapped: %w", customErr)
businessErr = &BusinessError{Code: 500, Message: "internal server error"}
)
// 使用传统错误返回的函数
func returnError() error {
return errTest
}
// 返回自定义错误
func returnCustomError() error {
return customErr
}
// 返回业务错误
func returnBusinessError() error {
return businessErr
}
// 返回包装的错误
func returnWrappedError() error {
return wrappedErr
}
// panic 业务错误
func panicBusinessError() {
panic(businessErr)
}
// 使用 defer 的传统错误返回函数
func returnErrorWithDefer() error {
var err error
defer func() {
// 空的 defer,用于测试 defer 的开销
}()
err = errTest
return err
}
// 不使用 defer 直接调用 recover(错误示范)
func returnErrorWithoutDefer() error {
// recover 在非 defer 函数中无效,总是返回 nil
if r := recover(); r != nil {
return r.(error)
}
panic(errTest)
}
// 使用 panic 的函数
func doPanic() {
panic(errTest)
}
// 包装 panic/recover 的函数
func returnErrorWithRecover() error {
var err error
func() {
defer func() {
if r := recover(); r != nil {
err = r.(error)
}
}()
doPanic()
}()
return err
}
// 包装 panic/recover 业务错误的函数
func returnBusinessErrorWithRecover() error {
var err error
func() {
defer func() {
if r := recover(); r != nil {
err = r.(error)
}
}()
panicBusinessError()
}()
return err
}
// 测试传统错误返回的性能
func BenchmarkErrorReturn(b *testing.B) {
for i := 0; i < b.N; i++ {
err := returnError()
if err != nil {
_ = err
}
}
}
// 测试带 defer 的错误返回性能
func BenchmarkErrorReturnWithDefer(b *testing.B) {
for i := 0; i < b.N; i++ {
err := returnErrorWithDefer()
if err != nil {
_ = err
}
}
}
// 测试 panic/recover 的性能
func BenchmarkPanicRecover(b *testing.B) {
for i := 0; i < b.N; i++ {
err := returnErrorWithRecover()
if err != nil {
_ = err
}
}
}
// 测试没有 defer 的 recover(会导致 panic)
func BenchmarkRecoverWithoutDefer(b *testing.B) {
// 这个测试会失败,因为 recover 必须在 defer 中
b.Skip("这个测试会导致程序崩溃,因为 recover 必须在 defer 中使用")
for i := 0; i < b.N; i++ {
err := returnErrorWithoutDefer()
if err != nil {
_ = err
}
}
}
// 测试简单的 errors.As 性能
func BenchmarkErrorsAs(b *testing.B) {
var customErr *CustomError
for i := 0; i < b.N; i++ {
err := returnCustomError()
if errors.As(err, &customErr) {
_ = customErr
}
}
}
// 测试带包装的 errors.As 性能
func BenchmarkErrorsAsWrapped(b *testing.B) {
var customErr *CustomError
for i := 0; i < b.N; i++ {
err := returnWrappedError()
if errors.As(err, &customErr) {
_ = customErr
}
}
}
// 测试普通的类型断言性能(作为对照)
func BenchmarkTypeAssertion(b *testing.B) {
for i := 0; i < b.N; i++ {
err := returnCustomError()
if ce, ok := err.(*CustomError); ok {
_ = ce
}
}
}
// 测试返回自定义业务错误的性能
func BenchmarkReturnBusinessError(b *testing.B) {
for i := 0; i < b.N; i++ {
err := returnBusinessError()
if err != nil {
if be, ok := err.(*BusinessError); ok {
_ = be.Code
_ = be.Message
}
}
}
}
// 测试 panic/recover 自定义业务错误的性能
func BenchmarkPanicRecoverBusinessError(b *testing.B) {
for i := 0; i < b.N; i++ {
err := returnBusinessErrorWithRecover()
if err != nil {
if be, ok := err.(*BusinessError); ok {
_ = be.Code
_ = be.Message
}
}
}
}
执行测试:
go test -bench=. -benchmem