Ownership on its own doesn't incur any performance overhead. If what you mean is the overhead incurred by using Rc and Arc, it's just something you have to be aware of.
> Ownership on its own doesn't incur any performance overhead.
That means barely anything.
The computational cost is paid at compile time for deciding who owns the object, but the consequences are still felt at runtime.
As I understand it, no parallel thread of execution can do anything to an object while I believe I need to write to it, by any mechanism presently in Rust (or planned?). So if new computational complexity creeps into the middle of my operation then the availability of that object begins to drop like a rock. Correct?
> it's just something you have to be aware of.
Take any feature in any language, and this solution doesn't scale. The general case is not, "I made the code slow and I should have been aware of what I was doing." The general case is, "someone who wasn't aware of what they were doing has broken something and now we need to find it when we don't even know who did it." and, "how do we explain to people how to do better in the future instead of just scolding them?" I have been living in this very space in another language for the better part of a month, and I expect to be here again later this year.
I have tools do find memory leaks, and performance bottlenecks. Is anyone thinking of analogous solutions for constrained parallelism in Rust?
I'm... unsure what you mean. As far as I know Rust doesn't require any extra runtime overhead for parallelism that wouldn't be required in C++. Rust ensures you use locking mechanisms at compile time, but you still need those in C++, you just "have to be aware of it" in that case.
I suppose Rust does require a value shared across threads to be reference-counted, where that might technically be optional in C++. But then you get to do manual deallocations across threads, which just sounds like a nightmare. And each thread only has to increment the counter once, so the overhead should be minuscule compared to the overhead of creating a thread in the first place. The only exception would be if you have bad lock coordination, but again, that problem is no different in Rust than it is in C++.
Much like in C, you could use several other schemes such as a Mutex (or RwLock). There are crates which implement more complicated memory management primitives -- the most obvious example being crossbeam[1].
Crossbeam even has an implementation of an epoch-based memory manager[2] which (in very loose terms) amortises the cost of reference counting each object by tracking the epochs of each thread and freeing objects once all threads have moved past the "death time" of an object.
Anything that you can do in any other language. Rust doesn't force you to do anything special, only prove that what you're doing has defined behavior. No tools are forbidden.
In no language is it sound to mutate the same data in parallel or to read it while it is being mutated. Synchronization primitives are needed by all languages to deal with this fundamental truth.
Rust just reflects this truth in the type system. That said, you can build whatever abstraction you want to deal with it. You can use immutable data structures, mutexes, lock/wait free mechanisms, whatever.
Hell you can throw caution to the wind and use a raw pointer if you wanted to. That's how a lot of the higher level constructs work under the hood, they just wrap the logic required to maintain soundness under the API. That's not just a Rust thing, it's true in all languages. It's just that Rust makes it a bit easier to express soundness because of how it reflects multiple aliasing in the type system.
Note that Rust's "Cow" type is NOT an analogue of copy-on-write C++ strings.
The correct analogue in Rust are the Rc and Arc types, and the "make_mut" method that clones the data if and only if there is more than a reference.
Also the claim that "Rust’s COW implements the Send and Sync traits, which due to Rust safety requirements, means the implementation is designed to be used in multi-threaded applications" is technically true, but highly misleading, since Cow of course doesn't have interior mutability, and so can't be modified if shared between multiple threads (unless put in a Mutex or equivalent container) and it's Send and Sync because all "simple" types do, and not because it's "designed" to be so.
Overall, the author doesn't seem very knowledgeable about Rust.
I don't get what point is the author is trying to make.
If it is to show that Rust outperforms C++ when implementing COW, I think he is mistaken. COW is intrinsically performance limited in multithreaded environments, this is not a language defect.
The examples shown are definitely not apples-to-apples comparisons. The author compares C++ runtime-evaluated COW to a compile time evaluated strategy in Rust. At least, that is my take on this. I'm interested in learning Rust, but I feel either I am not seeing the whole picture, or Rust is only an interesting option if you are not already a seasoned C++ engineer. (with up-to-date practices on how to use the language)
16 comments
[ 2.6 ms ] story [ 45.9 ms ] threadThat means barely anything.
The computational cost is paid at compile time for deciding who owns the object, but the consequences are still felt at runtime.
As I understand it, no parallel thread of execution can do anything to an object while I believe I need to write to it, by any mechanism presently in Rust (or planned?). So if new computational complexity creeps into the middle of my operation then the availability of that object begins to drop like a rock. Correct?
> it's just something you have to be aware of.
Take any feature in any language, and this solution doesn't scale. The general case is not, "I made the code slow and I should have been aware of what I was doing." The general case is, "someone who wasn't aware of what they were doing has broken something and now we need to find it when we don't even know who did it." and, "how do we explain to people how to do better in the future instead of just scolding them?" I have been living in this very space in another language for the better part of a month, and I expect to be here again later this year.
I have tools do find memory leaks, and performance bottlenecks. Is anyone thinking of analogous solutions for constrained parallelism in Rust?
I'm... unsure what you mean. As far as I know Rust doesn't require any extra runtime overhead for parallelism that wouldn't be required in C++. Rust ensures you use locking mechanisms at compile time, but you still need those in C++, you just "have to be aware of it" in that case.
I suppose Rust does require a value shared across threads to be reference-counted, where that might technically be optional in C++. But then you get to do manual deallocations across threads, which just sounds like a nightmare. And each thread only has to increment the counter once, so the overhead should be minuscule compared to the overhead of creating a thread in the first place. The only exception would be if you have bad lock coordination, but again, that problem is no different in Rust than it is in C++.
It does not require it; that is one of many possible strategies, depending on what you're doing.
Crossbeam even has an implementation of an epoch-based memory manager[2] which (in very loose terms) amortises the cost of reference counting each object by tracking the epochs of each thread and freeing objects once all threads have moved past the "death time" of an object.
[1]: https://docs.rs/crossbeam/ [2]: https://docs.rs/crossbeam/0.7.3/crossbeam/epoch/index.html
I don't know anything about the epoch-based manager, but it does sound interesting.
In no language is it sound to mutate the same data in parallel or to read it while it is being mutated. Synchronization primitives are needed by all languages to deal with this fundamental truth.
Rust just reflects this truth in the type system. That said, you can build whatever abstraction you want to deal with it. You can use immutable data structures, mutexes, lock/wait free mechanisms, whatever.
Hell you can throw caution to the wind and use a raw pointer if you wanted to. That's how a lot of the higher level constructs work under the hood, they just wrap the logic required to maintain soundness under the API. That's not just a Rust thing, it's true in all languages. It's just that Rust makes it a bit easier to express soundness because of how it reflects multiple aliasing in the type system.
The correct analogue in Rust are the Rc and Arc types, and the "make_mut" method that clones the data if and only if there is more than a reference.
Also the claim that "Rust’s COW implements the Send and Sync traits, which due to Rust safety requirements, means the implementation is designed to be used in multi-threaded applications" is technically true, but highly misleading, since Cow of course doesn't have interior mutability, and so can't be modified if shared between multiple threads (unless put in a Mutex or equivalent container) and it's Send and Sync because all "simple" types do, and not because it's "designed" to be so.
Overall, the author doesn't seem very knowledgeable about Rust.