Will something like `Arc<Mutex<T>>` be optimized?

Mutex and Arc both require alloc memory on heap, but Arc<Mutex<T>> seemed to have no need to alloc the memory twice. I mean, the heap allocation of Mutex may be redundant, at least in the case T has a fixed size.
Will Arc<Mutex<T>> be optimized? Or am I fundamentally wrong, the two allocations are both necessary?

My understanding is that Arc is a pointer type ( similar to Box but with reference counting semantics, and shareable across threads ), but Mutex is not. Mutex is just a wrapper. It's the same with Rc and RefCell. You can have a struct with several RefCells and Cells in it, and then an Rc to point at the whole thing. I guess you could also have a struct with several Mutexes in it and an Arc pointing at the whole thing, although whether that would be useful I am not sure.

In other words, your statement is just wrong " Mutex and Arc both require alloc memory on heap". They don't. Only Arc requires memory on the heap.

1 Like

I notice the book has a slightly strange explanation:

" As you might suspect, Mutex<T> is a smart pointer. More accurately, the call to lock returns a smart pointer called MutexGuard , wrapped in a LockResult that we handled with the call to unwrap ."

It starts with an inaccurate statement "is a smart pointer" then corrects itself.

Yes, you're right. I thought wrongly UnsafeCell requires to alloc memory on heap. :joy:
Thank you for answering such a simple question.

And, do you mean Mutex is not a smart pointer?

That's the part of hightly debated question right now.

Right. Mutex isn't a pointer at all. Arc is a pointer. MutexGuard is a pointer. Mutex itself is not.

1 Like

Interesting though. I have not ever thought about whether Mutex is a smart pointer. I always think it is.

If you look at the Deref documentation, it shows all the implementations. Arc is there, MutexGuard is there, Rc is there, so is Box, and so are Ref and RefMut. Mutex is not.

I would say both allocations are necessary.

The OS object used inside the mutex needs to have a stable object because of how it's implemented, which means it either needs to be stored as a static variable or on the heap. In practice, static variables aren't very useful because it means your mutexes are all fixed at compile time and Rust would need to have some mechanism for saying "this value can never move" (Pin won't work, it just says "the thing behind this pointer will never move").

An Arc<T> is fundamentally a pointer to some generic heap-allocated object. It makes no assumptions about the thing it points to, and therefore can't "reuse" existing allocations. Trying to reuse the Mutex's internal allocation wouldn't work anyway because that allocation is exactly the right size for the system object and there won't be any space left to place Arc's reference counts.

You can avoid the Arc allocation by not using an Arc in the first place. There's no reason why a Mutex can't be stored in a local variable on the stack or a static variable and you just pass around references.

Also, considering each mutex access will typically require a trip into the OS or some sort of sleep (which is slow), there doesn't really seem to be much benefit to avoiding an allocation other than saving the roughly 64 bytes that will be allocated by Arc to store the Mutex<T> and its bookkeeping.

Edit: I would also argue that specialising Arc<Mutex<T>> in this way is a bad idea. It means I now need to remember all the special cases for when my Arc<T>'s T may or may not be inlined, compared to just using a bespoke ArcMutex<T> type. It's one of the reasons I don't really like C++'s std::vector<bool> or use of short-string optimisation by default.

3 Likes

@Michael-F-Bryan
I agree with the most, but one point.

As @geebee22 said and according to the source code of std, Arc requires memory allocation on heap but Mutex doesn't, because UnsafeCell doesn't (I guess sys::MovableMutex should not require as well). So, Mutex should be able to stored on the stack, in my view.

So, if I'm right, there is no need to create a type likes ArcMutex<T> naturally.

On Linux it does because the kernel requires them to have a stable address: MovableMutex in std::sys::unix::mutex - Rust

1 Like

I see, if so there are no more questions. Thanks.

That's what I would have suggested to do / have / use, should the programmer feel that bad about the double indirection :grinning_face_with_smiling_eyes:

The issue, of course, would be implementing it.

  1. There is no cross-platform Rust (although unsafe) API to write a custom Mutex implementation against. This means going the extra mile of writing platform-specific impls that bind to system internals, which is cumbersome and quite error-prone. Although, granted, one could try to copy-paste stdlib's implementation.

    And at that point the only thing one has is an implementation of PinBoxedMutex<T> (because that's basically what stdlib's Mutex is, IIUC).

  2. You'd then have to embed one (or two) atomic counters inside alongside the pointee to basically reimplement Arc's logic as well :dizzy_face:

So, it's doable, but needless to say, the potential UB in such code would quite probably outweigh the plausibly negligible performance benefits of skipping the double indirection.

This topic was automatically closed 90 days after the last reply. We invite you to open a new topic if you have further questions or comments.