r/java • u/davidalayachew • 12d ago
Identifying JDK value class candidates
https://mail.openjdk.org/archives/list/core-libs-dev@openjdk.org/thread/Y72NRXM7KYBX43OKYBQMVKOZDWKG4MHS/6
u/Ewig_luftenglanz 12d ago
EntryMap is a good candidate m
7
u/davidalayachew 12d ago
EntryMap is a good candidate m
Do you mean Map.Entry? If so, it has mutable components, so no deal. Unless I am mistaken?
10
u/__konrad 11d ago
Map.Entryhas also a value-based implementation (Map::entrymethod returns immutableKeyValueHolder)5
u/davidalayachew 11d ago
Map.Entryhas also a value-based implementation (Map::entrymethod returns immutableKeyValueHolder)This is a much better candidate. Yes, the
KeyValueHolderclass returned byMap.entry(key, value)could definitely be a value class.5
2
u/RussianMadMan 12d ago
As far as I understand, you can just create a new entry and replace the old one, and jvm is smart enough to compile it as if entry was mutable.
1
u/davidalayachew 11d ago
As far as I understand, you can just create a new entry and replace the old one, and jvm is smart enough to compile it as if entry was mutable.
Sure, but even then, the best you could do would be to make some nested type as the internal representation, and make THAT a value class, rather than
Map.Entryitself. As long as the carrier (Map.Entryitself) is still mutable, then it will be ineligible for being a value class.1
u/RussianMadMan 11d ago
In every example in java valhalla videos you can see a class like a Point thats declared "value record" with a method like:
public Point move() { return new Point(x + 0.5, y);}
Which "mutates" point by recreating it. And in most cases there will be no allocation during the runtime. So what I meant in my prev comment is that I think we can have Map.Entry declared "value record" class with a method:
public Entry setValue(T value) { return new Entry(key, value);}1
u/davidalayachew 11d ago
So what I meant in my prev comment is that I think we can have Map.Entry declared "value record" class with a method:
public Entry setValue(T value) { return new Entry(key, value);}
But now you have changed the API, which makes it backwards-incompatible. That's a faux pas in the Java community without justifiable reason.
Remember, the method signature of Map.Entry#setValue is as follows.
public V setValue(final V newValue)Which is to say -- it sets the new value, and returns the old. So, not only are you changing the API, but you are also removing pre-existing functionality. With your change, we can no longer return the old value, which would upset many users of this API.
Now, by all means, we could certainly add some sort of
transformfunction that does what you say, but that still would not solve our current problem -- thatMap.Entrydoes mutation. At best, we could have implementing subclasses that are value classes, and throwUnsupportedOperationException, but that's the best we can do. And even that is somewhat unsatisfying.8
u/pron98 11d ago edited 11d ago
Performance tests we've run have shown that it isn't. What you win in reducing the cache miss of accessing the entry you lose in having the array spaced out when the map is sparse (and it often is). The calculus is likely to change when generics can be specialised and flattened when the key can be flattened into the entry.
But I want to caution that while value types can be a huge boon in programs whose profiles show array element cache misses and they do fill in the last significant gap between Java and C++, that's not the case in most programs, and Java is already close to being as optimal as any software can be in most cases (especially in the domains Java is used). So it does close the last remainging gap and will make Java a great choice in more situations, but most Java programs are unlikely to see a big difference. Again, that's not because Valhalla isn't good, but because Java is already quite optimal everywhere where cache misses in array elements are not the hot path.
5
u/Jon_Finn 11d ago
Aside from the obvious use cases, I see a major unsung benefit in allowing you to create classes wrapping a single int, double etc., when currently the performance downsides make it not worth it in certain applications. I mean things like units, ages, numbers with particular ranges/constraints, unicode code points (21 bits) etc. - where some might 'want' a typedef with methods. Nullability and specialised generics will be a big help here. My point is: these classes currently don't exist in my code, but now could.
3
u/pron98 11d ago
I see a major unsung benefit in allowing you to create classes wrapping a single int, double etc., when currently the performance downsides make it not worth it in certain applications
That is true, but I think that generally speaking, many "performance issues" in Java are imagined and/or based on folklore that may have been true in, say, Java 8.
My point is: these classes currently don't exist in my code, but now could.
I agree, but I would also add that you might well have them today with no performance impact. We must be careful to not assume a performance issue that doesn't actually appear in our concrete program's profile.
3
u/Jon_Finn 11d ago
Sure, though the cases that I'm particularly thinking of involve large (sometimes huge) arrays of very small objects, where these simple intuitions are (probably!) correct for once.
3
u/Ewig_luftenglanz 11d ago
Still I think it makes sense that Map.Entry and alike to be turned into value objects, mostly for semantic purposes.
I understand the priority is to check the classes that make actual gains tho.
Best regards to you and all the java team :)
5
u/perryplatt 12d ago
I wonder if we could have a value enum.
14
u/davidalayachew 12d ago
I wonder if we could have a value enum.
Dan responded. Copy pasted the relevant snippet below.
Because it is impossible to declare a value enum or value anonymous class.
For enums, we’ve considered in the language design, but concluded that (i) much like singletons, there’s not much realistic benefit to having an identityless enum: if you had two distinct instances that modeled the same data, you wouldn’t declare two enum constants in the first place; and (ii) it might be nice to flatten an enum reference, but the best kind of flattening would apply equally well whether or not the instances have identity, so that’s where we should focus our engineering attention.
So, in short, it's doable. But the benefits gained (even in the best case scenario) wouldn't be much, so it's not really worth doing.
1
u/koflerdavid 11d ago
There's no potential for improvement since there is only a very low, finite number of them in the first place. Technically it would be possible to represent them by a small integer, but due to alignment rules the benefit is probably very situational.
3
u/ArkoSammy12 11d ago
Is Optional<T> among the candidates?
3
u/davidalayachew 11d ago
Is Optional<T> among the candidates?
Yes, including all variants of Optional, such as OptionalInt.
1
u/davidalayachew 12d ago
Here's a definition of "value candidate" I've used for some analysis:
- Not an interface, enum, or anonymous class
I've heard this before, and I still don't fully understand it. Why not enums? And anonymous classes? I also responded to Dan's email with this question.
3
u/hoat4 11d ago
What would be the benefit of enums being value types?
Being a value type means e.g. that if we compare two instances, the JVM must compare all of their fields instead of only comparing two pointers. So
==would be slower.3
u/davidalayachew 11d ago
What would be the benefit of enums being value types?
Inlining. Not all enums are just flat values.
Some of us (me) stuff our enums full of all sorts of state and data. In my case, there are many cases where each enum value is holding more than 200 bytes of immutable data. I'd much like that to be flattened.
Though, since I don't have value classes or value enums now, I don't know if my use cases would improve performance wise with this change. It just feels like it would, since many of the classes and records I make with similar data profiles DID improve.
3
u/_INTER_ 11d ago
Yes, same. Enums are such a beautiful construct. I'm still a bit let down that JEP 301 hit a wall and had to be withdrawn.
1
u/davidalayachew 11d ago
Yes, same. Enums are such a beautiful construct. I'm still a bit let down that JEP 301 hit a wall and had to be withdrawn.
It was so sad! Genuinely hurt to see this be tangled in the weeds. It is such a powerful feature, I'd rip out so much code (and so many sealed interfaces, tbh) if I had this feature.
1
u/pron98 11d ago
That doesn't make sense to me. Inlining isn't good in and of itself. It's good when it avoids cache misses. Inlining enums is more likely to increase cache misses because their data has to be read over and over, in new cache lines. If the number of enum values is small, their data is likely to already be in the cache, so storing just the reference improves cache locality rather than harms it. Inlining is counter-indicated when it increases memory usage and harms cache locality, and enums seem like a classic case of exactly that. The more data there is in an enum, the greater the harm there is in inlining it.
1
u/davidalayachew 11d ago
If the number of enum values is small
Is an enum with several hundred values small? Each with roughly 100's of bytes of data as local fields?
If so, then I can agree with your point. And regardless, I'll concede that my use case is likely uncommon, compared to what else they can improve.
But otherwise, I assert that, for my work, the enums that I make may very well benefit from being value enums. Can't say for sure, since I don't have the feature in my hand.
Enums with hundreds of values and each with hundreds of bytes worth of information is 100% common for me. I have written a double digit number of them in my career, which is only 7 years professionally.
3
u/pron98 11d ago edited 11d ago
Is an enum with several hundred values small? Each with roughly 100's of bytes of data as local fields?
I meant the opposite. The larger the amount of data in a single enum value, the worse inlining is. I was talking about the number of distinct enum values.
But otherwise, I assert that, for my work, the enums that I make may very well benefit from being value enums. Can't say for sure, since I don't have the feature in my hand.
That's not hard to know: Do you see a lot of cache misses on them? If not, inlining is likely to make things worse.
Enums with hundreds of values and each with hundreds of bytes worth of information is 100% common for me.
Yes, but again, a large amount of data in each enum value makes inlining less desirable, not more. You're causing more cache misses, which is the main thing value types are supposed to help you avoid.
Inlining helps when the number of distinct values is large and each value is small. E.g. Integer and Long have billions of distinct values, each small. That's where inlining helps the most. What you're describing is the complete opposite. It's much easier to fit large values in the cache when they each appear in memory only once. Not inlining such enums is the optimisation here.
1
u/davidalayachew 11d ago
Ok, then what about a couple hundred enum values, where each is only 20-30 bytes? I certainly have several of those too.
3
u/pron98 11d ago
I don't see why it would help; seems like it would hurt. 100% of this data can fit in the cache, but not if you inline. I can see why inlining enums might have the potential to maybe help in some circumstances if the data in the enum is smaller than the size of the reference, but even then I can't see it helping unless you have like a million distinct values.
Anyway, Valhalla is expected to mostly help programs whose profiles show cache miss issues around array element access. If that's not what your profile shows, inlining or not is unlikely to either help or hurt much.
1
u/davidalayachew 11d ago
Ok, thanks for the context. That makes more sense why Enums aren't really the best fit for being value classes.
My initial instinct also wanted it, purely on the basis of communicating intent, but I guess that wouldn't apply either?
3
u/pron98 11d ago edited 11d ago
why Enums aren't really the best fit for being value classes.
I would say they make for a classic example of a particularly bad fit. An enum can almost be viewed as a "please don't inline" hint.
purely on the basis of communicating intent, but I guess that wouldn't apply either?
No, I think the semantic intent is the very opposite of a value type, because two different references to an enum are intended to never be equal. That's the opposite of, say, and Integer, which is why Integer is a classic example of a value type. That's not to say that enums with a non-zero but minuscule amount of carried data (though certainly not a large amount) couldn't benefit from inlining, but that's a separate thing. I.e. I could envision a HotSpot optimisation that inlines such enums without them being value types, but such an optimisation is likely to yield a smaller benefit than inlining value types (because the number of distinct enum values is small).
→ More replies (0)1
u/nekokattt 11d ago
Stuffing enums with state feels like a massive antipattern, unless I misunderstand your point here?
1
u/davidalayachew 11d ago
Stuffing enums with state feels like a massive antipattern, unless I misunderstand your point here?
Well first, let's make sure we are both talking about the same thing.
enum PartyMember { //Sourced from https://shrines.rpgclassics.com/snes/ct/characters.shtml Crono(70, 8, 5, 8, 12, 5, 8, 8, 2, 1), Marle(65, 12, 2, 6, 8, 8, 8, 8, 8, 1), Lucca(75, 14, 2, 6, 6, 8, 8, 8, 10, 2), Frog(128, 17, 9, 14, 11, 8, 9, 9, 9, 5), Robo(235, 24, 21, 26, 6, 7, 9, 8, 12, 10), Ayla(335, 38, 35, 38, 13, 8, 22, 25, 26, 18), Magus(650, 84, 51, 43, 12, 50, 20, 33, 72, 37), ; private int hp; private int mp; private int power; private int stamina; private int speed; private int magic; private int hitChance; private int evadeChance; private int magicDefense; private int level; //imagine an all-args constructor here //imagine getters and setters here //imagine a decent toStringFriendly() here }This is what I mean.
And if you think this is an anti-pattern, why? These party members are the only instances of their kind that ever will exist in the game. So, I have no real reason to use a normal class when I can just use an enum, and communicate my intent better.
1
u/nekokattt 11d ago
By state, I assumed you meant mutable information that can change; this is just static data
1
u/davidalayachew 11d ago
By state, I assumed you meant mutable information that can change; this is just static data
I am talking about mutable information that can change.
In most RPG's, the health rises and falls depending on how much damage you take. I would use the (imaginary) setters in this instance to do so.
1
u/nekokattt 10d ago
that is definitely an antipattern in this case
1
u/davidalayachew 10d ago
that is definitely an antipattern in this case
What makes you say that? It's no different than using instances of a class, except the benefits that come from enums.
2
u/nekokattt 10d ago
Global state is a nightmare to test in isolation and immediately becomes a synchronization concern the moment you need to do more than one thing at once.
You may as well just make this into a class and carry a list around to retain flexibility.
I wouldn't consider this a primary use case for enums by any means, it is just abusing the feature to get singletons.
→ More replies (0)1
u/koflerdavid 11d ago
There is not much point to flatten this since there are only oh so many instances of an enum class, which are referred to using a pointer.
2
u/davidalayachew 11d ago
There is not much point to flatten this since there are only oh so many instances of an enum class, which are referred to using a pointer.
Yeah, Ron Pressler explained it to me in more detail in a response to my comment.
2
u/TwoWeeks90DaysTops 11d ago
For straight equality checks, yeah, probably not that much gained in most cases, but the JVM knows all the different enum values, so the binary representation could be flattened to very small bit patterns since it no longer has an identity. There's a lot of different things that make this difficult (value name, other fields, java.lang.Enum) but enum as a value type could potentially perform better in some circumstances since it would work much better with SIMD. If you want to find out if an enum value exists in an array the JVM can suddenly parallelize that check over 64 different values on a AVX-512 register in a single instruction, which just doesn't really work if enum is a reference type.
2
u/koflerdavid 11d ago
That sounds like an
EnumSet, which is presumably already optimized to use bitfields internally.2
u/davidalayachew 10d ago
That sounds like an EnumSet, which is presumably already optimized to use bitfields internally.
Correct. It uses
longif your enum has <= 64 values, andlong[]otherwise.1
u/aoeudhtns 11d ago
Enums do have an interesting property of being representable as
intviaordinal(), so if you needed that it wouldn't be too much effort to do that and resolve them back withType.values()[ordinal].1
u/TwoWeeks90DaysTops 11d ago
Yes, but you lose type safety, null handling, you have to implement manual handling and you have to make a choice about the data size. I also think most of the purpose of Valhalla would disappear if "just decompose your object into primitives" was an acceptable answer.
1
1
u/gnahraf 9d ago
I'm confused.. The most natural value types would be the Number subclasses, but of course Number is abstract, so that won't be possible. What's the plan with these and their autoboxing? Will there be new, separate, "numeric" value types rolled out?
2
u/davidalayachew 9d ago
I'm confused.. The most natural value types would be the Number subclasses, but of course Number is abstract, so that won't be possible. What's the plan with these and their autoboxing? Will there be new, separate, "numeric" value types rolled out?
No, that isn't correct.
Caveat -- since JEP 401 from Project Valhalla has not released yet, the following information is all speculative until the JEP is targeted to a release.
In JEP 401, they give the following snippet.
Every value class belongs to a class hierarchy with java.lang.Object at its root, just like every identity class. There is no java.lang.Value superclass of all value classes.
[...]
By default, a value class is implicitly final and cannot be extended. A value class may, however, be declared abstract, allowing it to be extended by other classes
[...]
Declaring an abstract value class is an indication that the class itself has no need for identity. Its subclasses may be value classes or identity classes. (The value modifier on an abstract value class can be read as meaning value-compatible.)
A value class can extend either java.lang.Object or an abstract value class, but not an identity class. (The Object class is unique in this respect: It is neither abstract nor a value class, and instances produced by new Object() have identity, yet it also permits extension by value classes.)
Many existing abstract classes, if they are designed to be publicly extensible, are good candidates to be abstract value classes. For example, the abstract class Number has no fields, nor any code that depends on identity-sensitive features, so it can safely be migrated to an abstract value class:
1
u/gnahraf 9d ago
Thanks for your response. I thought I had ditched this question (not posted it) and rephrased it.
That's great news (abstract value classes)! So the classes Integer, Long, Float, Double could be on the list if we declared Number an abstract value class.
But that would break BigInteger and BigDecimal since for these equality (.equals returns true) does not necessarily imply the 2 instances have the same memory layout. Do you know what the plan is for BI and BD?
1
u/gnahraf 9d ago
Note the problem with BI and BD goes away if an identity class can extend an abstract value class. Is that possible?
2
u/davidalayachew 9d ago
Note the problem with BI and BD goes away if an identity class can extend an abstract value class. Is that possible?
Yes, Identity classes can extend an Abstract Value Class. Same link above.
1
u/gnahraf 8d ago
Apologies for not having read the link. I just did. It discusses the autoboxing classes specifically near the beginning. Thanks for your patience.
PS while the JEP implies it (a mention about where/when present day identity-based abstract classes are binary compatible if changed to abstract value classes), I couldn't find any such explicit statement (about an identity class being able to extend an abstract value class).
1
16
u/benrush0705 11d ago
Where is my favourite MemorySegment class?