7 ms·
Anyone who’s curious why `errors.Is()` is so slow (relatively speaking) should read the implementation [0]. It’s pretty obvious once you read the code—it relies
by jay-barronville 2y ago
Anyone who’s curious why `errors.Is()` is so slow (relatively speaking) should read the implementation [0]. It’s pretty obvious once you read the code—it relies on reflection followed by a tree-walking algorithm.
[0]: https://github.com/golang/go/blob/9b43bfbc51c469ec13fca24960834a75b2bf66eb/src/errors/wrap.go#L27-L78 https://github.com/golang/go/blob/9b43bfbc51c469ec13fca24960...
- esprehn 2y agoThe happy path is still just nil comparison though: if err == nil || target == nil { return err == target } So the overhead is the function call and 2 comparisons. I wonder if the `isComparable := reflectlite.TypeOf(target).Comparable()` line was moved into a separate function if the compiler would inline Is() reducing the perf impact. Possibly related: https://github.com/golang/go/issues/61502 https://github.com/golang/go/issues/61502 Looks like they're working on improving the inliner.
- deleted 2y ago[deleted]
- jamespwilliams 2y agoThat err == nil check was added very recently and isn’t included in the latest Go release, which the author used in this article.
- esprehn 2y agoAh ha! So the issue was always going through the error type reflection even for the success path. https://github.com/golang/go/commit/af43932c20d5b59cdffca45406754dbccbb46dfa https://github.com/golang/go/commit/af43932c20d5b59cdffca454... I'd be interesting to see the benchmark after that.
- Yoric 2y ago[flagged]
- cedws 2y agoRuntime cost or compile time cost, take your pick.
- kbolino 2y agoOptimized Go code will generally perform on par with optimized Rust code. Exceptions exist and Go is a much less expressive language but that affects developers more than users. Go's error handling with wrapping is very similar to the anyhow crate which also has runtime overhead. The happy path should occur much more often than the error path and so the overhead is usually worth it for developer convenience in debugging. The quality of code relying on reflection varies wildly (even within the standard library) so I don't think anything about the reflection system itself is the problem. I've written reflection-based solutions to problems sparingly but with an eye to giving good output on errors so debugging is easy. You can write some awful and hard-to-debug macros in Rust too.
- neonsunset 2y ago> Optimized Go code will generally perform on par with optimized Rust code. This is an incredibly dangerous assumption. Go and Rust are in a completely different weight class of compiler capability. Before making an assumption like this, I strongly suggest building a sample application (that is more complex than hello world) in Go and Rust, then compiling both with optimizations and taking a peek at what they compile to with Ghidra/IDA Pro/any other disassembler. C# + .NET 8 can sometimes trade blows with Rust + LLVM, particularly with struct generics, but Go definitely can't, is far behind in compiler features and doesn't leverage techniques that reduce cost of non-free abstractions that .NET and OpenJDK employ.
- kbolino 2y agoI should clarify that by "optimized" I meant hand optimized, not just compiler optimized. You can easily code yourself into surprisingly poor performance with Go, though usually for the same/similar reasons as C#/Java. Rust makes it a little harder to get into the same situations though it's by no means impossible. As to the zero/low-cost abstractions, Go has very few abstractions at a language level and that is what I meant by "much less expressive". If you try too hard to write Go as though it were another language, you will shoot yourself in the foot. However the Go compiler, standard library, and broader ecosystem have come a long way and enable very similar performance profiles to most other compiled languages. It has not been my experience, at least in micro-benchmarks, to see any major performance differences in Go vs e.g. C on things like encryption, hashing, RNG, SIMD, etc. You do need to avoid allocations in hot code paths but that's true of any garbage-collected language. The outstanding performance issues I'm aware of are around deferring, calling methods through an interface, and calling out to cgo. These are pretty minor in most situations but can eat up a lot of CPU cycles if done often in tight loops. They've also been getting better over time and can sometimes be optimized away with PGO.