But, this turns that on its head. Microbenchmarks are, of course, to be taken with a grain of salt, but it's still a useful data point. More useful would be a discussion of why it is so.
But, this turns that on its head. Microbenchmarks are, of course, to be taken with a grain of salt, but it's still a useful data point. More useful would be a discussion of why it is so.
That isn't always going to mean that Rust code is faster, but for benchmark games like this, when Rust is slower it almost always means that the benchmark is wrong.
As for dynamic dispatch...does Rust not support it, or is there some language construct that allows solving the problems dynamic dispatch solves without imposing annoying limitations or needless verbosity? And, aren't there fast implementations of dynamic dispatch?
Rust supports dynamic dispatch if you truly need it, but the normal approach is to use trait bounds, which the compiler expands when used. Here's an example:
fn main() {
let r = MemReader::new("hello world\n".as_bytes());
get_line(&r);
}
fn get_line<R: Buffer>(r: &R) -> IoResult<String> {
r.read_line()
}
In this very simple example, the `get_line` function takes any value that implements the `Buffer` trait, which provides the `read_line` method. When `get_line` is called inside of `main`, the compiler creates a special version of `get_line` that takes a `MemReader`, and dispatches to its implementation of `read_line` statically. In practice, this feels a lot like dynamic dispatch, but using trait bounds always results in static dispatch in practice. Also, in this example, the `MemReader` is stack-allocated, and lent to the `get_line` method.In very rare cases, you may want dynamic dispatch. For example, imagine you have an array of Buffers, and want to read a line from each of them. In this case, you use a "trait object" (a `Box<Buffer>`), which moves the object to the heap and allows dynamic dispatch.
In practice, I have written many thousands of lines of production Rust code and have only encountered a need for trait objects a handful of times. This means that idiomatic, normal Rust code is stack-allocated and statically dispatched.
The verbosity of trait bounds has decreased more and more over time, and my favorite proposal (by aturon) for making it pretty close to maximally ergonomic is:
fn main() {
let r = MemReader::new("hello world\n".as_bytes());
get_line(&r);
}
fn get_line(r: &impl Reader) -> IoResult<String> {
r.read_line()
}
I hope this lands as new syntax after 1.0 :)Permalink to the relevant section of the Go source, with descriptive comments: https://github.com/golang/go/blob/04cf881fbea55d5bca584c78b1...
Go is explicitly meant to have _really_ fast compilation times, which precludes the Go compilers from any expensive optimisations. Rust, on the other hand, has an explicit goal of being as fast as idiomatic/safe C/C++ while removing the burden of writing the safety yourself, so it really _has_ to be fast.
But, if it is the primary source, it would tell me that Go will close the gap as the compiler becomes more advanced and takes advantage of more CPU optimization features. Also, it's an interesting difference that Rust is an LLVM language while Go is compiled to native code directly; probably not from a performance perspective (at least not in the long run), but from a development perspective...is it easier or harder to develop new language features on LLVM or a native compiler?
https://groups.google.com/forum/#!topic/golang-nuts/NSPcEqhe...
That is to say: there's not much difference for difficulty of developing new language features, but avoiding LLVM makes it harder to develop new fast language features.
If automatic memory management is built in, that adds some overhead to most allocation situations(and encourages an architecture that exploits easy allocation - e.g. Java libraries where everything you do involves a new()). There are differences between garbage collection(mark-and-sweep) and reference counting as well; an RC system is "pay for what you allocate" with predictable scaling, making it more appropriate to employ for hitting real-time deadlines, however, the state-of-the-art in generational mark-and-sweep GC does better for average case throughput. Or to put it another way, if you have a single batch process that everything goes through, you can usually count on a GC system doing it faster than a RC equivalent. But if you want to run several things concurrently and tally results, or you are running an interactive app and care about getting your latency numbers down, the pauses involved in GC may be more problematic to you. (The semantic downside of RC, which has also made a lot of languages avoid it, is its inability to deal with circular data references.)
Of course, manually managed memory lets you design any allocation strategy you want, and so it can let you blow away automatically managed systems on microbenchmarks like these - stuff that starts mattering in the real world only after you've scaled up near to the constraints of the target hardware and any overhead is a Really Big Problem. There's definitely some more experimental automatic technology out there that "breaks the rules," but in terms of what you see in today's production environments, you can start estimating performance characteristics just by knowing what type of allocation is being done.
The other big issue, of course, is the type system. The design of the type system determines the constraints and difficulty of automated optimizations; classically, Fortran was one of the best languages for low-level optimization because it made things very easy for the compiler, and offloaded many considerations on the programmer. Today C has grown into a roughly equivalent space(pointer aliasing, the last big issue, was tackled in C99). Rust can do everything C does, but it also has a much smarter type system backing it up and enforcing memory safety. Dynamically typed languages like Python, Ruby, Lua etc. are essentially always slower and more memory-hungry because of their intentional flexibility - to do as much as they do at runtime, they end up retaining more stuff per piece of data and paying a bigger cost to manipulate it. Luajit is an example of best-in-class technology for dynamic languages, and it's still not as fast as Go on most of the benchmark game. [0]
Where Go stands in these two concerns is roughly comparable to Java or C#: it uses GC, and it has an intentionally simplistic static type system which points you towards built-in data structures, which makes it easy to work with for average-case situations, but hard or impossible to "tune up" as needs grow more complex. And if you check the benchmark game, all three are in the same window of performance, with some variance that could mostly come down to implementation detail.
[0] http://benchmarksgame.alioth.debian.org/u32/compare.php?lang...
This is why I have hopes that Rust will make it easier to match the performance of micro-optimised C code in the future whilst still maintaining clarity and safety (the compiler knows more invariants about the code, so can be more aggressive in its optimisations).
Your hunch/gut feeling seems to be very off.