9 ms·
I don't like the Things Move example. I'm not sure how true the general statement is (I'd never thought of it that way, but it isn't like how GC moves things ar
by jnordwick 9y ago
I don't like the Things Move example. I'm not sure how true the general statement is (I'd never thought of it that way, but it isn't like how GC moves things around, and I'm not sure things are even more than in C++ -- I thought rust reduced unnecessary moves because would kill cache performance), but the example isn't entirely correct from my perspective.
Return values that fit into a register will be returned in a register, and his example is an 8 byte struct, so that returns in a register. Return values larger than a register will add an implicit first argument that is a pointer to memory where the return value should be written to. In that sense, it is very similar to C++ in that you are initializing into an allocated buffer.
As for "Refcounts are not Dirty", I would greatly disagree. Using refcounts to get around an overly aggressive borrow checker seems to be an ugly developing pattern in rust, and I feel they are giving away the performance many are fighting for by adding all these little inefficiencies to idiomatic rust. Add some refcounting here, add a Box or other indirection there, a chained monadic interface that can't short circuit and has to continually do error/null checks, etc... Soon it is death by a thousand papercuts. People fight hard for that extra 5% in performance only to have it taken away from them in interface and language issues.
Edit: Forgot about handles. Ugh. Completely unacceptable when you want to grow your tree data structure and you have to do a realloc and basically copy every node. If your tree is complete, then you are copying the whole tree every time you start a new level. The conclusions sound more like ugly hacks, than what you would properly design.
- the_mitsuhiko 9y agoIf a value returned from a function actually moves or not is currently up for Rust to optimize. It's not something you can depend on. About the refcounts: since the counting is explicit (calls to clone()) they at least in my experience don't really show up. Most of the refcounted objects I deal with bump the refcounts once when some task spawns and decrements it when it ends. I have yet to see refcounts to change in hot code paths. //EDIT for your edit: > Edit: Forgot about handles. Ugh. Completely unacceptable when you want to grow your tree data structure and you have to do a realloc and basically copy every node. Sure, but that's not my point anyways. At any point you can fall down to writing unsafe code and building a safe abstraction on top of it. This is to help developers not run into walls. I don't think that handles are the best thing invented but I don't think "well you can't do that in Rust until we some time in the future" and not provide an alternative is a particularly good suggestion.
- gmueckl 9y agoBut if they do... oh boy, what fun can be had by spamming ooeratins on the same memory location from multiple cores! If this isn't thrilling enough, try doing that with atomic operationsto be extra thread safe. I had some really fun experiences with this in C++. The best one was a factor 100 slowdown by activating compiler optimizations.
- berkut 9y agoAre Rust ref-counts atomic? (I'd assume so). Atomic ref-counts can actually have a surprising amount of overhead in many multithreaded scenarios, so much so that going back to raw pointers over shared pointers in C++ for some use cases of passing pointers around can provide a huge win.
- the_mitsuhiko 9y agoArc<T> is atomic, Rc<T> is not.
- therein 9y agoThere is Rc and Arc, the latter being atomic. If you want to pass them between threads, you'll want an Arc, and in fact you will want an Arc<Mutex<Data>> so your data becomes: let data = Arc::new(Mutex::new(Data::new())); And if you want a mutable static global that's accessed across threads, you'll need something like: lazy_static! { // 0 -> None // 1 -> Circles // 2 -> Bezier static ref DRAW_ON_TOUCH: Arc<Mutex<u32>> = Arc::new(Mutex::new(0)); } I love Rust but it is making easy things harder than they need to be.
- gmueckl 9y agoHm, this distinction between non-atomic and atomic ref counting sounds like a scat-gun sized footgun waiting to go off when it is least expected. Is there any protection for non-atomic ref counting in a multithreaded environment in Rust?
- the_mitsuhiko 9y ago
- therein 9y agoThe more I code in Rust, the more I reluctantly agree with this statement of yours. I wish you weren't right but I think you have a point. Something needs to be done about this before too much legacy is accumulated with this pattern. > Using refcounts to get around an overly aggressive borrow checker seems to be an ugly developing pattern in rust, and I feel they are giving away the performance many are fighting for by adding all these little inefficiencies to idiomatic rust. Add some refcounting here, add a Box or other indirection there, a chained monadic interface that can't short circuit and has to continually do error/null checks, etc... Soon it is death by a thousand papercuts. People fight hard for that extra 5% in performance only to have it taken away from them in interface and language issues.
- pcwalton 9y ago> Something needs to be done about this before too much legacy is accumulated with this pattern. Do you have any suggestions?
- therein 9y agoI wish I did. Rust has a very interesting learning curve to say the least. It is a long fight with the borrow checker and it beats you until it teaches you its ways. Then you realize they were good practices all along. And then comes a point where you realize that you need to write _some_ low level code that is unsafe. You read a lot about unsafe Rust and it almost allows you to almost work as if you are working with C. You can transmute a reference to native pointer type and then transmute it back as a mutable reference with a static lifetime if you want. But at that point you have lost the safeties and now you have to know exactly how to craft your transmute trickery such that you know there will be little to no overhead. Perhaps it all comes down to the borrow checker being less strict about things. I can't tell exactly in which ways, perhaps about the lifetimes of references. Something doesn't feel as natural to reason about working in Rust as it does in C but I can't put my finger on it. One thing I can say though is I wish I didn't need to use lazy static for non-primitive static globals. But overall I really like the general direction that the language is heading. Love the way dependencies are managed although TOML wasn't the best format choice in my mind. crates.io, docs.rs and the entire community and the ecosystem are great and unique too.
- pcwalton 9y ago> Using refcounts to get around an overly aggressive borrow checker seems to be an ugly developing pattern in rust, and I feel they are giving away the performance many are fighting for by adding all these little inefficiencies to idiomatic rust. Add some refcounting here The performance hit isn't as it bad as it seems, because you only adjust the reference counts when you actually take an extra reference. This is different from shared_ptr in C++, because C++ automatically calls the copy constructor and so it's easy to end up with lots of reference count traffic. Observe that, if we assume that the time spent in malloc() and free() dominates the time spent to adjust one reference count (which is a safe assumption), then the additional time overhead of Rc with a single owner is effectively zero. > add a Box or other indirection there Why do you ever need to Box unnecessarily in Rust? This is more of an issue with C++, where shared_ptr has an extra indirection and interior pointers and "new" encourage heap allocation. I actually think that Rust in practice has the opposite problem: people are afraid to Box when they shouldn't be, causing unnecessary memcpy traffic. > a chained monadic interface that can't short circuit and has to continually do error/null checks How is this more overhead than in C? > Edit: Forgot about handles. Ugh. Completely unacceptable when you want to grow your tree data structure and you have to do a realloc and basically copy every node. If your tree is complete, then you are copying the whole tree every time you start a new level. The conclusions sound more like ugly hacks, than what you would properly design. Yeah, I would like to make a crate with an interface similar to petgraph but that actually mallocs every node separately. This should be readily doable. I think the reason why nobody has made this crate yet is that copying the nodes on growth isn't as big of a concern in practice as it might initially seem, because the time spent in the growth case is amortized and made up for by extremely fast allocation of new nodes.
- gok 9y ago> The performance hit isn't as it bad as it seems, because you only adjust the reference counts when you actually take an extra reference. This is different from shared_ptr in C++, because C++ automatically calls the copy constructor and so it's easy to end up with lots of reference count traffic. Well only if you pass shared_ptr by value, which isn’t really idiomatic. But yeah the default is dumb in C++ and smart in Rust.
- Manishearth 9y ago> Using refcounts to get around an overly aggressive borrow checker seems to be an ugly developing pattern in rust Every single large, "modern", C++ codebase I've worked with uses way more refcounts than is typical in Rust. (and it's worse because C++ refcounting patterns involve more refcount churn due to how they're designed on the copy constructor, and shared_ptr uses atomics even when unnecessary. Though these large codebases tend to use a slightly different design that lets them decide atomicity per-type)