Rust is not fast
cananian.livejournal.com
cananian.livejournal.com
* The part about Rust having no shared memory is just wrong. Rust has a very rich set of concurrency primitives at this point: you can use immutable shared memory (Arc), mutexes (MutexArc), reader-writer locks (RWarc), and atomic variables (AtomicInt and friends). And if you're willing to drop down to unsafe code, you get the full set of LLVM concurrency primitives.
* The complaint about smart dereferencing strikes me as odd. Almost every new systems language—Objective-C, D, Go, and Rust (and also Nimrod I think?)—does the same thing that Rust does in that the "." operator also works on pointers. It's type-directed: if you don't know whether you're working with a pointer or not, just go look at the type of the value you're working with. If you don't know a value's type, then you have much bigger problems than knowing whether you'll get an extra memory access on ".".
Hiding the difference between a memory access and a cheaper operation is something compilers have done ever since the invention of register allocation.
* I don't know what the complaint about "bugs with small objects" is, but all word-sized objects were made into LLVM Values a while back, making us as efficient there as you can be. Unless I'm misunderstanding the complaint.
* Doing code duplication to accommodate different types of smart pointers doesn't work. The smart pointers have different semantics: that's why they exist in the first place. You can't just copy and paste your code.
* The GC in Rust does need work. But so does C++'s GC and nobody is saying "C++ is not fast" because of it.
About the only part I agree with is is "fork-join parallelism and work-stealing should be better supported". This is an area we'll need to flesh out more fully at some point. Note that there is now a much more advanced work-stealing scheduler. (It's not using the most efficient data structures for work stealing yet though.)
* Objective-C is from 1983, do you? It's not exactly new. ;)
* Nimrod is pretty fast and as the only language even supports soft-realtime applications out of the box. Besides that it feels the nicest to use and has a fat standard library with stuff like Redis. Sorry, I only played with that language, but already feel like a fanboy
* About GC. Yes, that's a problem of all newer languages, but whatever implementation you choose there are advantages and disadvantages and which one is the right one also depends a lot on the language itself. Sometimes you are better off with your big fancy algorithm and sometimes you really just want good old reference counting. But yeah, GC is the big great topic and I am sure it will be for the next few decades.
The dot-syntax for accessing object properties is much more recent though (2009?).
Declare a plain getter method:
-(id)foo;
Call it with dot syntax: id foo = obj.foo;
Works fine. Now declare it as a property: @property id foo;
Call it without dot syntax: id foo = [obj foo];
Works fine. They are completely unrelated and just introduced at the same time. You could take dot syntax out of the language without affecting properties in the least. You could take properties out of the language without affecting dot syntax in the least.(There is one exception to this, and that is the part where dot syntax understands and calls custom getter/setter names for properties. But that is the only place where they even touch, and it certainly doesn't mean they're somehow integrated.)
However that's a pretty arbitrary definition, since properties pre-date the introduction of that directive.
Using the wishy-washy definition doesn't help, in any case. Nobody would say that e.g. autorelease is a "property", but you can still call it with dot syntax.
You know that the dot syntax is a mechanism that matches with the conventions for property access, and no other access pattern.
You also know that its possible to call zero argument methods that aren't accessors using the dot syntax.
I don't believe that you think that this is the purpose for which the dot syntax was designed, and yet you bring up 'autorelease' to support your position.
As I said, based on your specific definition, you are correct, but using denigration, a false straw-man 'quotation', and dishonest examples doesn't make your definition fit the evidence.
As I said, and you sidestepped, properties pre-date the directive, therefore the directive cannot be the sole definition of what properties are.
Well, how the fuck can I argue against that? Sure, you must be right. Clearly I could not possibly disagree with you for any other reason.
Seriously, fuck off.
You then went on to argue that your point of view was the only reasonable one.
If you addressed the actual question instead of being aggressive and insulting, we might have had an interesting technical discussion.
It makes for easier reading of the codegen (reading C source is easier than reading machine code, even if it's generated C source) and before LLVM made it much easier to leverage C compiler optimizations & multiplatform support.
Rust has a richer set of safe memory management primitives than Nimrod does (Nimrod is reliant on non-thread-safe deferred reference counting with stack scans, while Rust has thread-safe unique ownership and thread-safe RC as an option), so I'm not sure I agree that Nimrod is the only language that can do soft real time. It's definitely a neat language though.
This catches me. Especially for soft-realtime stuff. Because I am looking for strongly, explicitly typed in-game logic language - which needs deterministic and symmetric coroutine.
I have to check it out.
Graphics part is already mostly written in C/C++. I won't use immature languages for low-level system due to library support and debugging ability
I am still reading the manual, but Nimrod doesn't seem to have coroutine support. Do you have any idea?
Ask the guys and gals in #Nimrod on freenode, they're super Super helpful -- I'm not well versed enough in parallelism and concurrency to answer your question, but they are!
C++ doesn't have a garbage collector, so I'm not sure what you are getting at here.
http://www.hpl.hp.com/personal/Hans_Boehm/gc/
Rust has a GC in the same sense that C++ has one, which is to say that its GC can be provided by libraries rather than being baked-in to the language.
So there's no longer even special syntax for a theoretical GC. Instead the plan is to greatly expand our support for custom pointer types in general, which will make all user-provided pointer types (implemented in whatever libraries our users come up with) first-class citizens. Our own stdlib will include several pointer types of this nature, including: `Rc` (reference counting), `Arc` (atomic (thread-safe) reference counting), `RWArc` (memory-safe Arc for mutable objects), and, yes, a `Gc` type.
And while it's true that we may provide hooks into the runtime to allow the `Gc` type to be implemented more efficiently, any user library will be able to use these hooks to implement its own GC. There won't be any implementation in the language itself.
This post is based on an experience with a version of Rust with the old runtime, with pervasive internal iterators, with managed pointers (which we haven't recommended using for years, and are in the process of chucking out), and is missing all the perf work we've achieved over the past six months. Suffice to say, any experience with Rust (especially in terms of performance) from that long ago is so dated by this point as to be completely unrepresentative.
That isn't to say that Rust is fast yet, because we refuse to say that until we're as fast as C++. But a headline like this seems needlessly sensational (perhaps consider "Performance pitfalls in the implementation of Rust 0.6").
First off -- yes, it is a rant. I actually had some more nuanced in-person conversations, but those aren't nearly as fun to post. So you get the version that was fun for me to write. Sorry.
That said, I'm glad to hear Rust is all wonderful now. It sounds like pcwalton agrees with most of my points and got them fixed. Kudos. I look forward to better GC, fork-join parallelism, and work-stealing.
It doesn't seem like anything significant was done with the type system, though. How does Rust handle "find_and_insert" now?
If folks are interested in actually critiquing code style, feel free to check out the rusty-turtle code at http://cananian.livejournal.com/68747.html. I disagree that I was trying to write Rust as if it were some different language -- my frustration was in part because I was trying to write things with Rusty ownership types, etc, and getting that to work properly was so frustrating (and the error messages so opaque). If I just stuck managed boxes around everything my life would have been simpler -- but then I would not have been programming in Rust, really.
I'm interested to hear more discussion on the primary point made in my blog: is Rust supposed to be 'safe and fast'? Or just 'safe'?
Nope, we're not saying that yet, Rust still needs a ton of work!
> How does Rust handle "find_and_insert" now?
Google doesn't return any obvious results for this string, what is it supposed to do?
> is Rust supposed to be 'safe and fast'? Or just 'safe'?
If Servo (written in Rust) isn't faster than Gecko (written in C++), then Mozilla won't continue funding Rust. Rust needs to be fast in a very existential way. :) And it will be! Initial benchmarks of Servo's performance are incredibly promising (pcwalton could go into more detail here), and there's still bushels of low-hanging fruit to be plucked.
Ouch, I didn't know the project had a deadline. By when does it need to be faster?
http://static.rust-lang.org/doc/0.8/std/hashmap/struct.HashM...
It appears to still have the mandatory copy. (And this is just one example of a pervasive issue with the standard library.)
fn find_or_insert_clone<'a, K: Clone, V>(map: &'a mut HashMap<K,V>,
key: &K, val: V) -> &'a mut V {
match map.find_mut(key) {
Some(x) => return x,
None => {}
}
// `key` is definitely missing
map.find_or_insert(key.clone(), val)
}Meta: I'm pretty disappointed with this sentiment. I would have loved to read those discussions instead of a rant. They seem far more intellectually honest.
One thing I like about Rust is that it makes doing allocation-free performance-critical calculations somewhat easier and safer. This is because I can preallocate my data before critical loops, inside the constructors of implementation objects, then hand out borrowed, non-mutable pointers to this data to clients from these objects. That way the clients don't have to pass in buffers to use, which is none of their business -- it would break encapsulation for them to even know what kind and how large of buffers to pass. The clients cannot detect that they are actually receiving a pre-allocated, shared buffer.
In other languages that would ordinarily be very dangerous without extra effort, because if they or some other code turned around and called routines that used that same pre-allocated buffer again, then the data would be silently written over, in an "action at a distance" way which would be a nightmare to maintain.
But Rust "freezes" the contents behind the immutable borrow, making sure that it can't be borrowed mutably again and thus cannot be changed, while the immutable borrow is in use, from anywhere in the program, all resolved at compile time.
This allows getting very aggressive with the pre-allocation strategy.
I actually think one of the most interesting parts about Rust is its shared memory story: if you use mutex-protected shared memory, the compiler enforces that you take the locks properly, eliminating data races.
I assume you don't mean the compiler prevents deadlocks, but I'd be very happy to be proved wrong.
http://winningraceconditions.blogspot.com/2012/10/what-is-da...
"[...] Rust's type system guarantees that concurrent tasks cannot share state but instead must use message-passing to communicate, which precludes the possibility of data races completely by enforcing happens-before relationships on all data accesses (or in the case of this post,[1] by enforcing mutual-exclusion relationships). Yet it's still possible to write nondeterministic programs in Rust (using select2, failure propagation, etc), and so race conditions are still possible."
...Though I'm not sure if this is all that pcwalton is referring to.
[1] http://winningraceconditions.blogspot.com/2012/09/rust-4-typ...
Yes, the post is brief since it was written from notes several months after I had been talking to the rust guys. I couldn't justify the time to spend making the examples more concrete (and wasn't exactly encouraged to do so at the time). Sorry, that is as far as I've carried the load, you've got to take it from here.
I dedicated a few days to hacking on rustc this summer, and while the tooling and community are very nice, the compilation times are really high. Git pull && make ? 20 min. Edit a file && make ? 5 min. IIRC the major was that metadata got serialized/unserialized all the time, but anyway it's one of the priorities. I'll hack again on it with pleasure when I'll have more time.
Well, due to the nature of self-hosting compilers, it's certainly greatly exacerbated by being written in Rust. :)
To wit, a self-hosting compiler must actually compile itself no less than three times to ensure that the generated artifacts "converge" upon a single point. This is as true for Rust as it is for GCC.
1. Old version compiles new version
2. New version, compiled by old version, compiles new version
3. New compiled by new-compiled-by-old compiles new version
I hadn't heard of this before and initially didn't see why #3 is special. But I guess the idea is, the output object code is a function of both the input source code and the compiler version. And those two are the same for #2 and #3. So you expect the output of both #2 and #3 to be identical, and if it's not, that's a bug in the compiler. Is that how it works?At least for me, I would use Rust if it supports (1) safe and nice symmetric coroutine with automatically growing stack (2) devices for correct programming - such as const-correctness. (3) a device guarantees safe communication between threads - like Go channel. (4) stable implementation. (5) full support for C interop.
AFAIK, Rust now has all except #4. So I am waiting it to be stabilized.
Actually I want something like C++ with symmetric coroutine, but D is too unstable for coroutine…