On Tuesday, 10 April 2018 at 06:47:53 UTC, Jonathan M Davis wrote:
As it stands, it's impossible to have thread-local memory
pools. It's quite legal to construct an object as shared or
thread-local and cast it to the other. In fact, it's _highly_
likely that that's how any shared object of any complexity is
going to be constructed. Similarly, it's extremely common to
allocate an object as mutable and then cast it to immutable
(either using assumeUnique or by using a pure function where
the compiler does the cast implicitly for you if it can
guarantee that the return value is unique), and immutable
objects are implicitly shared.
(Honest question:) Do people really cast from local to
shared/immutable and expect it to work?
(when ever I cast something more complex then a size_t I almost
expect it to blow up... or break sometime in the future)
That said, I can understanding building a shared object from
parts of local data... though I try to keep my thread barriers as
thin as possible myself. (meaning I tend to copy stuff to the
shared and have as few shared's as possible)
At minimum, there would have to be runtime hooks to do
something like move an object between pools when it is cast to
shared or immutable (or back) in order to ensure that an object
was in the right pool, but if that requires copying the object
rather than just moving the memory block, then it can't be
done, because every pointer or reference pointing to that
object would have to be rewritten (which isn't supported by the
language).
A hook for local to cast(shared) could work... but would require
a DIP I guess. I was hoping to make a more incremental
improvement the the GC.
Also, it would be a disaster for shared, because the typical
way to use shared is to protect the shared object with a mutex,
cast away shared so that it can be operated on as thread-local
within that section of code, and then before the mutex is
released, all thread-local references then need to be gone. e.g.
synchronized(mutex)
{
auto threadLocal = cast(MyType)mySharedObject;
// do something with threadLocal...
// threadLocal leaves scope and is gone without being cast
back
}
// all references to the shared object should now be shared
Yeah thats why I was still scanning all thread stacks and pages
when marking global data.
So a shared -> local is a no op but the other way needs thought.
You really _don't_ want the shared object to move between pools
because of that cast (since it would hurt performance), and in
such a
situation, you don't usually cast back to shared. Rather, you
have a shared
reference, cast it to get a thread-local reference, and then
let the
thread-local reference leave scope. So, the same object
temporarily has both
a thread-local and a shared reference to it, and if it were
moved to the
thread-local pool with the cast, it would never be moved back
when the
thread-local references left scope and the mutex was released.
Having synchronized classes as described in TDPL would make the
above code cleaner in the cases where a synchronized class
would work, but the basic concept is the same. It would still
be doing a cast underneath the hood, and it would still have
the same problems. It just wouldn't involve explicit casting.
shared's design inherently requires casting away shared, so it
just plain isn't going to play well with anything that doesn't
play well with such casts - such as having thread-local heaps.
I would think a shared class would never be marked as a
THREAD_LOCAL as it has a shared member.
Also, IIRC, at one point, Daniel Murphy explained to me some
problem with classes with regards to the virtual table or the
TypeInfo that inherently wouldn't work with trying to move it
between threads. Unfortunately, I don't remember the details
now, but I do remember that there's _something_ there that
wouldn't work with thread-local heaps. And if anyone were to
seriously try it, I expect that he could probably come up with
the reasons again.
Regardless, I think that it's clear that in order to do
anything with thread-local pools, we'd have to lock down the
type system even further to disallow casts to or from shared or
immutable, and that would really be a big problem given the
inherent restrictions on those types and how shared is intended
to be used. So, while it's a common idea as to how the GC could
be improved, and it would be great if we could do it, I think
that it goes right along with all of the other ideas that
require stuff like read and write barriers everywhere and thus
will never be in D's GC.
- Jonathan M Davis
Yeah I thought it would have issues, thanks for your feedback!
I'll see if I can come up with a better idea that doesn't break
as much stuff.