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

Implement Truffle compiler control based on HotSpot's CompileBroker compilation activity #10135

Closed

Conversation

simonis
Copy link
Contributor

@simonis simonis commented Nov 21, 2024

Truffle compilations run in "hosted" mode, i.e. the Truffle runtimes triggers compilations independently of HotSpot's CompileBroker. But the results of Truffle compilations are still stored as ordinary nmethods in HotSpot's code cache (with the help of the JVMCI method jdk.vm.ci.hotspot.HotSpotCodeCacheProvider::installCode()). The regular JIT compilers are controlled by the CompileBroker which is aware of the code cache occupancy. If the code cache runs full, the CompileBroker temporary pauses any subsequent JIT compilations until the code cache gets swept (if running with -XX:+UseCodeCacheFlushing -XX:+MethodFlushing which is the default) or completely shuts down the JIT compilers if running with -XX:+UseCodeCacheFlushing.

Truffle compiled methods can contribute significantly to the overall code cache occupancy and they can trigger JIT compilation stalls if they fill the code cache up. But the Truffle framework itself is neither aware of the current code cache occupancy, nor of the compilation activity of the CompileBroker. If Truffle tries to install a compiled method through JVMCI and the code cache is full, it will silently fail. Currently Truffle interprets such failures as transient errors and basically ignores it. Whenever the corresponding method gets hot again (usually immediately at the next invocation), Truffle will recompile it again just to fail again in the nmethod installation step, if the code cache is still full.

When the code cache is tight, this can lead to situations, where Truffle is unnecessarily and repeatedly compiling methods which can't be installed in the code cache but produce a significant CPU load. Instead, Truffle should poll HotSpot's CompileBroker compilation activity and paus compilations for the time the CompileBroker is pausing JIT compilations (or completely shutdown Truffle compilations if the CompileBroker shut down the JIT compilers).

The corresponding JVMCI change is tracked under JDK-8344727: [JVMCI] Export the CompileBroker compilation activity mode for Truffle compiler control.

This PR fixes the problem by checking HotSpot's compilation activity mode in OptimizedTruffleRuntime::submitForCompilation() before actually submitting a compilation task to a compile queue. If the compilation activity mode is RUN_COMPILATION the task is submitted as before without any changes. However, if the compilation activity mode is STOP_COMPILATION (i.e. the CompileBroker has temporarily stopped JIT compilations, we flush the current compile queue (because compiled methods can not be installed anyway) and return null. We also start a timer which can be configured with the new StoppedCompilationRetryDelay parameter (defaults to 1000ms). After StoppedCompilationRetryDelay we submit a new compilation task, even if the compilation activity mode is not RUN_COMPILATION. This can help to trigger a code cache cleanup in situations when there are no JIT compilations, because code cache sweeping is only triggered when new nmethods are installed. Finally, when the compilation activity mode is SHUTDOWN_COMPILATION, we simply shutdown the compilation queue and issue a warning to inform users about the code cache shortage.

I've manually tested the change by running an octane benchmark with a very small code cache. Before the change, we could see the following results:

RayTrace: 484
----
Score (version 9): 484

[engine] Truffle runtime statistics for engine 1
    Compilations                : 2255
      Success                   : 549
      Temporary Bailouts        : 1703
        jdk.vm.ci.code.BailoutException: Code installation failed: code cache is full: 1702
      Permanent Bailouts        : 0
      Failed                    : 0
      Interrupted               : 3
    Invalidated                 : 0
    Queues                      : 2480
    Dequeues                    : 30
        Target inlined into only caller: 30
    Splits                      : 156
    Compilation Accuracy        : 1.000000
    Queue Accuracy              : 0.987903
    Compilation Utilization     : 2.948298
    Remaining Compilation Queue : 195
    Time to queue               : count=2480, sum=187025989722, min=   60029, average= 75413705.53, max=181007679, maxTarget=cross
    Time waiting in queue       : count=2254, sum=1385581671, min=      54, average=   614721.24, max=156182217, maxTarget=Function.prototype.apply <split-322>

    JVMCI CompileBroker Time:
       Compile:          0,000 s
       Install Code:     0,000 s (installs: 0, CodeBlob total size: 0, CodeBlob code size: 0)

    JVMCI Hosted Time:
       Install Code:    27,227 s (installs: 565, CodeBlob total size: 5916736, CodeBlob code size: 1713432)

  nmethod code size         :  6834896 bytes
  nmethod total size        : 14834520 bytes
CodeCache: size=3896Kb used=3458Kb max_used=3586Kb free=437Kb
 bounds [0x00007ffff436d000, 0x00007ffff473b000, 0x00007ffff473b000]
 total_blobs=1378 nmethods=566 adapters=714
 compilation: disabled (not enough contiguous free space left)
              stopped_count=26, restarted_count=25
 full_count=3200

real	3m11,398s
user	11m19,363s
sys	0m9,387s

As you can see, out of 2255 compilations, 1702 have been to no purpose, because they failed in the final installation step because of a full code cache (i.e. BailoutException: Code installation failed: code cache is full: 1702). The other interesting observation is that although the benchmark terminated after 3min11s wall clock time, it actually consumed 11m19s cpu time, because the Truffle compiler threads where continuously doing useless work.

With my changes applied, the picture looks as follows:

RayTrace: 531
----
Score (version 9): 531


[engine] Truffle runtime statistics for engine 1
    Compilations                : 128
      Success                   : 111
      Temporary Bailouts        : 17
        jdk.vm.ci.code.BailoutException: Code installation failed: code cache is full: 16
        org.graalvm.compiler.core.common.CancellationBailoutException: Compilation cancelled.: 1
      Permanent Bailouts        : 0
      Failed                    : 0
      Interrupted               : 0
    Invalidated                 : 0
    Queues                      : 410
    Dequeues                    : 283
        Compilation temporary disabled due to full code cache.: 277
        Target inlined into only caller: 6
    Splits                      : 156
    Compilation Accuracy        : 1.000000
    Queue Accuracy              : 0.309756
    Compilation Utilization     : 0.069104
    Remaining Compilation Queue : 0
    Time to queue               : count= 410, sum=24979066360, min=   88714, average= 60924552.10, max=186708180, maxTarget=blend
    Time waiting in queue       : count= 128, sum=  22955820, min=       6, average=   179342.35, max=  724639, maxTarget=:anonymous <split-344>

    JVMCI CompileBroker Time:
       Compile:          0,000 s
       Install Code:     0,000 s (installs: 0, CodeBlob total size: 0, CodeBlob code size: 0)

    JVMCI Hosted Time:
       Install Code:     0,637 s (installs: 124, CodeBlob total size: 1156760, CodeBlob code size: 321496)

  nmethod code size         :  2460456 bytes
  nmethod total size        :  4833272 bytes
CodeCache: size=3896Kb used=3464Kb max_used=3537Kb free=431Kb
 bounds [0x00007ffff436d000, 0x00007ffff473b000, 0x00007ffff473b000]
 total_blobs=1557 nmethods=745 adapters=714
 compilation: disabled (not enough contiguous free space left)
              stopped_count=5, restarted_count=4
 full_count=33

real	3m14,756s
user	3m22,013s
sys	0m1,044s

As you can see, we compile considerably fewer methods and we only have 16 compilation failures due to a full code cache. At the same time we can see that from the 410 methods which have been enqueued for compilation, 277 have been dequeued because of Compilation temporary disabled due to full code cache. Again, the wall clock time of the benchmark was 3m14s but this time, the overall cpu time was just slightly higher with 3m22s because we haven't done such a huge amount of unnecessary compilations any more. Also notice how HotSpot's code cache full_count, which is incremented every time when a nmethod can't be installed because of a full code cache, dropped from 3200 to 33.

These example were run with -XX:+UseCodeCacheFlushing -XX:+MethodFlushing. If the JVM is configured with -XX:-UseCodeCacheFlushing, the benefits of this change are even higher.

Fixes #10133

@oracle-contributor-agreement oracle-contributor-agreement bot added the OCA Verified All contributors have signed the Oracle Contributor Agreement. label Nov 21, 2024
"Increase the code cache size using '-XX:ReservedCodeCacheSize=' and/or run with '-XX:+UseCodeCacheFlushing -XX:+MethodFlushing'.");
}
try {
queue.shutdownAndAwaitTermination(100 /* milliseconds */);
Copy link
Member

Choose a reason for hiding this comment

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

Can you describe a bit what you are trying to achieve here? why would it help to block the entire interpreter thread if this happens?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I must confess, that this is something I don't really understand very well. My actual intention is to stop/tear-down the Truffle compiler and all associated compiler threads once compilation has been shut down in HotSpot, but I didn't found a good way to do it.
Could you please advice what would be the best reaction if HotSpot is shutting down compilations because of a full code cache without any hope for recovery? I.e. what can we do to run full speed interpreted without any profiling/compilation overhead from that point on?

Copy link
Member

Choose a reason for hiding this comment

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

The fastest way to disable a compilation is using OptimizedCallTarget.compilationFailed.
Every time we see shutdown with a call target we could disable compilation.
Best idea I have.
I put up an example what i would do in another comment (no idea how to link draft comments here)

@simonis
Copy link
Contributor Author

simonis commented Nov 26, 2024

Hi @chumer, @dougxc,

Thanks a lot for your quick and very helpful review. I've just pushed a new version of my changes which hopefully addresses your requests (except for the right reaction on compilation shutdowns for which I've asked for additional help).

I've now moved the compilation mode handling one lever up into OptimizedCallTarget as suggseted by @chumer and refactored the code into a new function which makes it a little bit cleaner.

Please let me know what you think?

Thank you and best regards,
Volker

@simonis
Copy link
Contributor Author

simonis commented Nov 29, 2024

@chumer, could you please have a look at the updated version?

"Increase the code cache size using '-XX:ReservedCodeCacheSize=' and/or run with '-XX:+UseCodeCacheFlushing -XX:+MethodFlushing'.");
}
try {
queue.shutdownAndAwaitTermination(100 /* milliseconds */);
Copy link
Member

Choose a reason for hiding this comment

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

The fastest way to disable a compilation is using OptimizedCallTarget.compilationFailed.
Every time we see shutdown with a call target we could disable compilation.
Best idea I have.
I put up an example what i would do in another comment (no idea how to link draft comments here)

@chumer
Copy link
Member

chumer commented Nov 29, 2024

@simonis Thank you for the changes!. did another turn. looks like its going to be the last one 👍
I know such features are notoriously hard to test. Do you have an idea how we could do at least a smoke test for this?

@simonis
Copy link
Contributor Author

simonis commented Nov 29, 2024

@simonis Thank you for the changes!. did another turn. looks like its going to be the last one 👍 I know such features are notoriously hard to test. Do you have an idea how we could do at least a smoke test for this?

I can imagine something like the following:

  • start the JVM with a very small code cache
  • In a loop
    • trigger the compilation of a Truffle method
    • call jcmd Compiler.codecache on the current process
    • from the output, we can determine how many times compilation was dis-/enabled and how many nmethod installations have failed because of a full code cache.

It would be a little tricky to exclude all the JIT compilations and only allow truffle compilations so the results don't get influenced by JIT compilations.

Is this something acceptable as Truffle test? Do you already have similar tests (i.e. simple tests which repeatedly trigger the compilation of a single method, tests which call external tools and parse their output). I'm just asking because the HotSpot JTreg tests have frameworks and tooling for such kind of tests. I'm not familiar with the Truffle tests and don't want to re-invent the wheel so I'd be happy if you could point me to some tests which I could use a template to start with.

@simonis simonis requested a review from chumer December 2, 2024 08:25
@simonis
Copy link
Contributor Author

simonis commented Dec 9, 2024

@chumer, could you please have a look at the updated version?

Copy link
Member

@chumer chumer 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. Thanks a lot for this contribution @simonis !
I will take it from here to integrate.

@chumer
Copy link
Member

chumer commented Dec 20, 2024

Integrated: #10358

@chumer chumer closed this Dec 20, 2024
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
OCA Verified All contributors have signed the Oracle Contributor Agreement.
Projects
None yet
Development

Successfully merging this pull request may close these issues.

Implement Truffle compiler control based on HotSpot's CompileBroker compilation activity
3 participants