On Thu, 23 Apr 2026 07:53:30 GMT, Quan Anh Mai <[email protected]> wrote:

>> ## Preliminaries
>> 
>> ### 1. The inlining heuristic NodeCountLimitCutoff
>> 
>> In general, we don't want to inline a call if the graph is already too 
>> large. However, it is hard to decide whether the graph is large when we are 
>> still constructing the graph.
>> 
>> - There are still more bytecodes that need parsing, and more nodes that need 
>> generating.
>> - It is hard (maybe impossible) to reliably determine whether a node is dead 
>> during parsing.
>> 
>> Due to the issues above, the heuristic depends on the number of generated 
>> nodes, which is an upper bound of the number of live nodes, and the 
>> threshold is pretty conservative.
>> 
>> ### 2. Inlining a method
>> 
>> To inline a method, C2 needs to generate the structure for the callee to 
>> reside in. This includes the map for the exception path, the map for the 
>> merge of all normal paths, their memory states, etc. My experiment shows 
>> that, inlining a call generates around 20 more nodes than if the call is 
>> inlined in the source code.
>> 
>>     private int v() {
>>         return this,v;
>>     }
>> 
>>     int test1() {
>>         return this.v();
>>     }
>> 
>>     int test2() {
>>         return this.v;
>>     }
>> 
>> This means that, inlining a call consumes the budget of 
>> `NodeCountInliningCutoff`, which may prevent other calls from being inlined, 
>> even if other heuristics say that inlining is preferable. However, in 
>> practice, it is rarely an issue, because there is a difference of 3 orders 
>> of magnitude between the extra nodes generated by inlining, and the default 
>> value of `NodeCountInliningCutoff` (16000).
>> 
>> ### 3. Foreign memory access API
>> 
>> The aforementioned property that `NodeCountInliningCutoff` is 3 orders of 
>> magnitude larger than the number of extra nodes generated when inlining a 
>> call is broken due to how the FMA API is implemented. A memory access such 
>> as `j.l.f.MemorySegment::get` results in a huge call tree that needs 
>> inlining:
>> 
>>     @ 8   jdk.internal.foreign.AbstractMemorySegmentImpl::get (12 bytes)   
>> force inline by annotation   callee changed to  
>> io.github.merykitty.BenchmarkDraft::test1 (14 bytes)    -> TypeProfile 
>> (9083/9083 counts) = jdk/internal/foreign/NativeMemorySegmentImpl
>>       @ 1   
>> jdk.internal.foreign.layout.ValueLayouts$AbstractValueLayout::varHandle (24 
>> bytes)   force inline by annotation
>>       @ 8   java.lang.invoke.VarHandleGuards::guard_LJ_I (84 bytes)   force 
>> inline by annotation
>>         @ 3   java.lang.invoke.VarHandle::checkAccessModeThenIsDirect (29 
>> bytes)   force inline by annot...
>
> Quan Anh Mai has updated the pull request incrementally with one additional 
> commit since the last revision:
> 
>   add benchmark

(Somehow original comment got lost. Reposting...)

> I can say it is intensified by how FFM is implemented, if you look at the 
> inline tree of AbstractMemorySegment::get above, most of the calls are below 
> java.lang.invoke.VarHandleSegmentAsInts::get. So, while it is often an issue 
> with Method/VarHandles, it is a more severe one with the FFM API.

I wouldn't frame it as such. There are different users of `java.lang.invoke` 
API and FFM API is just one of them. FTR late inlining was introduced to fix 
the very same issue with inlining (`NodeCountInliningCutoff` overflow), but for 
code accessed through MHs. Back then Nashorn and JRuby were heavy users of MHs 
and they stressed the implementation in multiple ways. And, somewhat 
surprisingly, inlining of surrounding code wasn't a big deal for them.

> I am running some benchmarks that measuring the compiler performance, as well 
> as familiar benchmark suites Renaissance, Dacapo, Specjbb.

Good.

> For the risk of overinlining, I think the risk is really small, because 
> normally we will hit DesiredMethodLimit first, which is based on the total 
> bytecode size, and it has a much smaller threshold.

Personally, I'm not persuaded yet. And the difference is not that big: 
`DesiredMethodLimit = 8000` vs `NodeCountInliningCutoff = 18000` which 
translates to ~2,5 nodes per bytecode. Depending on bytecode distribution,  
either one can overflow first.

-------------

PR Comment: https://git.openjdk.org/jdk/pull/30874#issuecomment-4307198519

Reply via email to