Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

8338544: Dedicated Array class descriptor implementation #20665

Open
wants to merge 12 commits into
base: master
Choose a base branch
from

Conversation

liach
Copy link
Member

@liach liach commented Aug 21, 2024

@cl4es discovered that Stack Map generation in ClassFile API uses componentType and arrayType for aaload aastore instructions, which are currently quite slow. We can split out array class descriptors from class or interfaces to support faster arrayType and componentType operations.

Tentative, as I currently have no way to measure the actual impact of this patch on the startup performance; however, this made the ClassDesc implementations much cleaner.


Progress

  • Change must be properly reviewed (1 review required, with at least 1 Reviewer)
  • Change must not contain extraneous whitespace
  • Commit message must refer to an issue
  • Change requires CSR request JDK-8340963 to be approved

Issues

  • JDK-8338544: Dedicated Array class descriptor implementation (Sub-task - P4)
  • JDK-8340963: Make some ClassDesc methods no longer default (CSR)

Reviewers

Reviewing

Using git

Checkout this PR locally:
$ git fetch https://git.openjdk.org/jdk.git pull/20665/head:pull/20665
$ git checkout pull/20665

Update a local copy of the PR:
$ git checkout pull/20665
$ git pull https://git.openjdk.org/jdk.git pull/20665/head

Using Skara CLI tools

Checkout this PR locally:
$ git pr checkout 20665

View PR using the GUI difftool:
$ git pr show -t 20665

Using diff file

Download this PR as a diff file:
https://git.openjdk.org/jdk/pull/20665.diff

Webrev

Link to Webrev Comment

@bridgekeeper
Copy link

bridgekeeper bot commented Aug 21, 2024

👋 Welcome back liach! A progress list of the required criteria for merging this PR into master will be added to the body of your pull request. There are additional pull request commands available for use with this pull request.

@openjdk
Copy link

openjdk bot commented Aug 21, 2024

❗ This change is not yet ready to be integrated.
See the Progress checklist in the description for automated requirements.

@openjdk
Copy link

openjdk bot commented Aug 21, 2024

@liach The following label will be automatically applied to this pull request:

  • core-libs

When this pull request is ready to be reviewed, an "RFR" email will be sent to the corresponding mailing list. If you would like to change these labels, use the /label pull request command.

@openjdk openjdk bot added the core-libs core-libs-dev@openjdk.org label Aug 21, 2024
Copy link

@ExE-Boss ExE-Boss left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Apparently, changing method default‑ness requires a CSR (even for fully sealed hierarchies), but I can’t find the PR where I was informed of this.


Using return this instanceof <type>ClassDescImpl might allow C2 to better avoid megamorphic virtual invocations (see GH‑17488), but this needs to be benchmarked:

@liach
Copy link
Member Author

liach commented Aug 27, 2024

I know this requires a CSR; it's just not created due to this still being a draft.

The main purpose of this is still for speeding up stack map generation; the results are not confirmed yet, so this patch is on hold.

For megamorphic call site thing, the regular workload would be like:

if (c.isArray()) doArrayStuff(c);
if (c.isClassOrInterface()) doClassOrInterfaceStuff(c);

I think polymorphism is fine here, as after the check we usually go straight to perform type-specific operations like componentType(); or sometimes a method expects the input ClassDesc to be always primitive/class or interface/array, which won't lead to profile pollutions. This concern might be more valid for descriptorString I suppose.

@liach
Copy link
Member Author

liach commented Sep 25, 2024

Confirmed this is performance-wise neutral for startup. Ready for review.

@liach liach marked this pull request as ready for review September 25, 2024 18:48
@openjdk openjdk bot added csr Pull request needs approved CSR before integration rfr Pull request is ready for review labels Sep 25, 2024
@mlbridge
Copy link

mlbridge bot commented Sep 25, 2024

Webrevs

Copy link
Member

@cl4es cl4es left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM. Always a bit queasy to add a third to a nice pair of classes, but if this ever shows up as a problem on benchmarks we can revisit and think of alternatives (such as adding a rank field in each impl).

private static final ClassDesc CD_Object_array = ReferenceClassDescImpl.ofValidated("[Ljava/lang/Object;");
private static final ClassDesc CD_MethodHandle_array = ReferenceClassDescImpl.ofValidated("[Ljava/lang/invoke/MethodHandle;");
private static final ClassDesc CD_MethodHandle_array2 = ReferenceClassDescImpl.ofValidated("[[Ljava/lang/invoke/MethodHandle;");
private static final ClassDesc CD_Object_array = CD_Object.arrayType();
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I guess CD_Object.arrayType() shows up often enough now - even once in java.lang.constant.ConstantDescs - that we might as well pin it down as a constant somewhere (ConstantDescs is a candidate location, but that will take a CSR).

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This patch already has a CSR for trivial signature changes. The real difficulty lies in how we should name our new array class descriptors, Object_array or ObjectArray or what else?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

That said, can you leave a quick review on CSR https://bugs.openjdk.org/browse/JDK-8340963 too?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think I will do this in another patch that adds it to ConstantDescs - there's a place in ConstantDescs that could have used it, but if I put it in ConstantUtils I fear we are at risk of circular initialization dependencies.

Copy link
Member

@JornVernee JornVernee left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nice cleanup! I'm just not really sure about the new subtype relationship (see inline comment)

Comment on lines 102 to 116
public String descriptorString() {
var desc = cachedDescriptorString;
if (desc != null)
return desc;

return cachedDescriptorString = computeDescriptor();
}

private String computeDescriptor() {
var componentDesc = element.descriptorString();
StringBuilder sb = new StringBuilder(rank + componentDesc.length());
sb.repeat('[', rank);
sb.append(componentDesc);
return sb.toString();
}
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is there really that much benefit in lazily computing the descriptor? ReferenceClassDescImpl doesn't do this either... Maybe we can keep things simple and initialize the descriptor in the constructor?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This laziness is actually the main reason I had this implementation: In stack map generation, we need to compute the array descriptors of many types yet not using them in the end; the string computation involved a lot of allocations, especially with frequent arrayType() and componentType() calls.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ok, thanks. That's useful information.

// Class.forName is slow on class or interface arrays
Class<?> clazz = element.resolveConstantDesc(lookup);
for (int i = 0; i < rank; i++)
clazz = clazz.arrayType();
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looking at the implementation of arrayType, it reflectively creates an array and then returns its class. Just makes me think we need a better we to look up an array class directly in the JDK :D

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Indeed; and it's more alarming that Class.forName("[[[[[Ljava.lang.Object;") is slower than Object.class.arrayType().arrayType().arrayType().arrayType().arrayType().

@liach
Copy link
Member Author

liach commented Oct 8, 2024

@JornVernee I have updated the implementation class name and the factories for clarity.

@liach liach requested review from JornVernee and cl4es October 8, 2024 01:35
Copy link
Member

@cl4es cl4es left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looks reasonable to me.

An alternative could be to put an int rank in both PrimitiveClassDescImpl and ReferenceClassDescImpl. Would lead to slightly gnarlier code in places, but keep the number of classes down. Worth considering if any throughput benchmark sees fallout from going from 2 to 3 types.

The internalNameToDesc changes could perhaps better have been done in a separate PR but no point teasing it apart now.

Copy link
Member

@mlchung mlchung left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looks good with minor suggestion. I agree having a separate implementation class for array type and class/interface allows a cleaner subclass implementation.

Comment on lines 78 to 81
if (rank == MAX_ARRAY_TYPE_DESC_DIMENSIONS)
throw new IllegalStateException(
"Cannot create an array type descriptor with more than "
+ MAX_ARRAY_TYPE_DESC_DIMENSIONS + " dimensions");
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
if (rank == MAX_ARRAY_TYPE_DESC_DIMENSIONS)
throw new IllegalStateException(
"Cannot create an array type descriptor with more than "
+ MAX_ARRAY_TYPE_DESC_DIMENSIONS + " dimensions");
ConstantUtils.validateArrayDepth(rank + 1);

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Unfortunately, this has to throw ISE while validateArrayDepth throws IAE.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I have extracted this to a new utility method, with a switch on the thrown exception type.

@liach
Copy link
Member Author

liach commented Oct 8, 2024

Thanks for the reviews; can anyone review the associated CSR at https://bugs.openjdk.org/browse/JDK-8340963 as well?

Comment on lines 309 to 313
public static void ensureRankPositive(int rank) {
if (rank <= 0) {
throw new IllegalArgumentException("rank " + rank + " is not a positive value");
}
}
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggest to inline the rank argument check in the caller method which makes the check explicit to the reader.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

So like if (rank <= 0) throw ConstantUtils.rankNotPositive(rank); at individual use sites?

Copy link
Member

@mlchung mlchung Oct 8, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I meant no need to have a utility method. Just do this:

Suggested change
public static void ensureRankPositive(int rank) {
if (rank <= 0) {
throw new IllegalArgumentException("rank " + rank + " is not a positive value");
}
}
public static void validateArrayDepth(int rank) {
if (rank <= 0) {
throw new IllegalArgumentException("rank " + rank + " is not a positive value");
}
validateMaxArrayDepth(rank, true);
}

Same change in ArrayClassDescImpl::arrayType(int)

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done. The negative rank check is pulled out separately in ArrayClassDescImpl::arrayType(int).

}

/**
* Returns a human-readable name for the type described by this descriptor.
*
* @implSpec
* <p>The default implementation returns the simple name
* <p>The implementations return the simple name
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
* <p>The implementations return the simple name
* <p>The implementation returns the simple name

I think this can be singular as it's not required to have more than 1 implementation class.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hmm, in fact, should we still have this @implSpec if we make this default?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think this can be dropped as ClassDesc as well. It can say "This method returns....". See what CSR review would say.

Copy link
Member

@JornVernee JornVernee left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Latest version looks good

@openjdk
Copy link

openjdk bot commented Oct 18, 2024

@liach this pull request can not be integrated into master due to one or more merge conflicts. To resolve these merge conflicts and update this pull request you can run the following commands in the local repository for your personal fork:

git checkout feature/array-cd
git fetch https://git.openjdk.org/jdk.git master
git merge FETCH_HEAD
# resolve conflicts and follow the instructions given by git merge
git commit -m "Merge master"
git push

@openjdk openjdk bot added the merge-conflict Pull request has merge conflict with target branch label Oct 18, 2024
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
core-libs core-libs-dev@openjdk.org csr Pull request needs approved CSR before integration merge-conflict Pull request has merge conflict with target branch rfr Pull request is ready for review
Development

Successfully merging this pull request may close these issues.

5 participants