User model stacking: current status

Remi Forax forax at univ-mlv.fr
Mon May 9 17:34:01 UTC 2022


> From: "Brian Goetz" <brian.goetz at oracle.com>
> To: "daniel smith" <daniel.smith at oracle.com>
> Cc: "valhalla-spec-experts" <valhalla-spec-experts at openjdk.java.net>
> Sent: Sunday, May 8, 2022 6:32:09 PM
> Subject: Re: User model stacking: current status

> To track the progress of the spiral:

> - We originally came up with the B2/B3 division to carve off B2 as the "safe
> subset", where you get less flattening but nulls and more integrity. This
> provided a safe migration target for existing VBCs, as well as a reasonable
> target for creating new VBCs that want to be mostly class-like but enjoy some
> additional optimization (and shed accidental identity for safety reasons.)

> - When we put all the flesh on the bones of B2/B3, there were some undesirable
> consequences, such as (a) tearing was too subtle, and (b) both the semantics
> and cost model differences between B2/B3 were going to be hard to explain (and
> in some cases, users have bad choices between semantics and performance.)

> - A few weeks ago, we decided to more seriously consider separating atomicity
> out as an explicit thing on its own. This had the benefit of putting semantics
> first, and offered a clearer cost model: you could give up identity but keep
> null-default and integrity (B2), further give up nulls to get some more density
> (B3.val), and further further give up atomicity to get more flatness
> (non-atomic B3.) This was honest, but led people to complain "great, now there
> are four buckets."

> - We explored making non-atomicity a cross-cutting concern, so there are two new
> buckets (VBC and primitive-like), either of which can choose their atomicity
> constraints, and then within the primitive-like bucket, the .val and .ref
> projections differ only with respect to the consequences of nullity. This felt
> cleaner (more orthogonal), but the notion of a non-atomic B2 itself is kind of
> weird.

> So where this brings us is back to something that might feel like the
> four-bucket approach in the third bullet above, but with two big differences:
> atomicity is an explicit property of a class, rather than a property of
> reference-ness, and a B3.ref is not necessarily the same as a B2. This
> recognizes that the main distinction between B2 or B3 is *whether a class can
> tolerate its zero value.*

> More explicitly:

> - B1 remains unchanged

> - B2 is for "ordinary" value-based classes. Always atomic, always nullable,
> always reference; the only difference with B1 is that it has shed its identity,
> enabling routine stack-based flattening, and perhaps some heap flattening
> depending on VM sophistication and heroics. B2 is a good target for migrating
> many existing value-based classes.

> - B3 means that a class can tolerate its zero (uninitialized) value, and
> therefore gives rise to two types, which we'll call B3.ref and B3.val. The
> former is a reference type and is therefore nullable and null-default; the
> latter is a direct/immediate/value type whose default is zero.

> - B3 classes can further be marked non-atomic; this unlocks greater flattening
> in the heap at the cost of tearing under race, and is suitable for classes
> without cross-field invariants. Non-atomicity accrues equally to B3.ref and
> B3.val; a non-atomic B3.ref still tears (and therefore might expose its zero
> under race, as per friday's discussions.)

> Syntactically (reminder: NOT an invitation to discuss syntax at this point),
> this might look like:

> class B1 { } // identity, reference, atomic

> value-based class B2 { } // non-identity, reference, atomic

> value class B3 { } // non-identity, .ref and .val, both atomic

> non-atomic value class B3 { } // similar to B3, but both are non-atomic

> So, two new (but related) class modifiers, of which one has an additional
> modifier. (The spelling of all of these can be discussed after the user model
> is entirely nailed down.)

> So, there's a monotonic sequence of "give stuff up, get other stuff":

> - B2 gives up identity relative to B1, gains some flattening
> - B3 optionally gives up null-defaultness relative to B2, yielding two types,
> one of which sheds some footprint
> - non-atomic B3 gives up atomicity relative to B3, gaining more flatness, for
> both type projections
There is also something we should talk, using non-atomic value classes does not mean automatically better performance. 
It's something i've discovered trying to implement HashMap (more Map.of() in fact) using value classes. 
Updating a value class in the heap requires more writes, more memory traffic than just updating pointers so depending on the algorithm, you may see performance degradation compared to a pointer based implementation. 

So even if we provide non-atomic B3, performance can be worst than using atomic B3, sadly gaining more flatness does not necessarily translate into better performance. 

Rémi 

Side Note: using more pointers mean more pressure to the GC, but it's very hard to quantify that pressure so maybe overall the system is more performant but given that JMH tests usually does not take GCs into account, it's an effect that we do not see. 


More information about the valhalla-spec-observers mailing list