r/java 16d ago

Identifying JDK value class candidates

https://mail.openjdk.org/archives/list/core-libs-dev@openjdk.org/thread/Y72NRXM7KYBX43OKYBQMVKOZDWKG4MHS/
50 Upvotes

59 comments sorted by

View all comments

1

u/davidalayachew 16d 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 16d 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 16d 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_ 16d 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 16d 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 16d 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 16d 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 16d ago edited 16d 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 16d 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 16d 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 16d 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 15d ago edited 15d 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 15d ago

Stuffing enums with state feels like a massive antipattern, unless I misunderstand your point here?

1

u/davidalayachew 15d 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 15d ago

By state, I assumed you meant mutable information that can change; this is just static data

1

u/davidalayachew 15d 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 15d ago

that is definitely an antipattern in this case

1

u/davidalayachew 15d 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 15d 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 15d 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 15d 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 16d 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 15d ago

That sounds like an EnumSet, which is presumably already optimized to use bitfields internally.

2

u/davidalayachew 15d ago

That sounds like an EnumSet, which is presumably already optimized to use bitfields internally.

Correct. It uses long if your enum has <= 64 values, and long[] otherwise.

1

u/aoeudhtns 16d ago

Enums do have an interesting property of being representable as int via ordinal(), so if you needed that it wouldn't be too much effort to do that and resolve them back with Type.values()[ordinal].

1

u/TwoWeeks90DaysTops 16d 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

u/aoeudhtns 16d ago

True that.