RFR: Refactor AOTCodeCache layout to store preload entries separately

Ashutosh Mehra asmehra at openjdk.org
Thu Sep 4 13:49:02 UTC 2025


On Thu, 4 Sep 2025 13:27:38 GMT, Andrew Dinn <adinn at openjdk.org> wrote:

>> Currently the mechanism to lay out final AOTCodeCache entries in `AOTCodeCache::finish_write()` is a bit convoluted.
>> Code data is initially written in a temporary buffer and then assembled in the final buffer in `AOTCodeCache::finish_write()`.
>> 
>> In the temporary buffer AOTCodeEntry structs are added from the end of the buffer, and the payload (the actual compiled code) is added from the start of the buffer. That means the temporary buffer holds AOTCodeEntry in reverse order.
>> 
>> ACE=AOTCodeEntry
>> 
>> | payload | ... | ACE[n] | ACE[n-1] | ... | ACE[0] |
>> 
>> 
>> When assembling the final buffer, AOTCodeEntry structs are first copied in the temporary buffer to make the order correct:
>> 
>> 
>> | payload | ...| ACE[0] | ACE[1] | ... | ACE[n] |... | ACE[n] | ACE[n-1] | ... | ACE[0] |
>> 
>> 
>> and then the whole memory block is copied into the final buffer.
>> This means the size of the temporary buffer needs to be a bit more than required.
>> 
>> Another issue is the search table created in `finish_write`. This table includes entries marked for preload. However, preload entries are never looked up; they get loaded at the start of the JVM in `preload_aot_code()`. Since the preload and other code entries are mixed together,  we also need a separate table to identify the preload entries.
>> 
>> This PR is an attempt to fix above issues. It does final assembly in following steps:
>> 1. Process AOTCodeEntry structs in the temporary buffer in reverse order and write the ones marked for preload in the final buffer
>> 2. Now the payload for the preload entries is marked
>> 3. Next, add the AOTCodeEntry structs for non-preload code to the final buffer
>> 4. Then add the payload for these entries
>> 5. Finally add the search table
>> 
>> 
>> | ACE[0] | ... | ACE[m] | payload | ACE[0] | ... | ACE[n] | payload | search_table |
>> 
>> 
>> This layout separates the preload entries from rest of the code and these entries can then be processed sequentially when the cache is loaded. There is no need for a separate table to identify the preload entries.
>> 
>> I have added the new functionality in separate methods suffixed with `_new` (eg `finish_write_new` and `preload_aot_code_new`) and they are guarded by `UseNewCode` flag.
>> 
>> **Performance impact:**
>> 
>> Startup numbers for spring-boot-getting-started:
>> 
>> Run,Old CDS + AOT,New CDS + AOT
>> 1,263,275
>> 2,265,278
>> 3,266,272
>> 4,277,271
>> 5,265,265
>> 6,264,261
>> 7,266,263
>> 8,258,266
>> 9,275,268
>> 10,277,263
>> Geomean,267.53,268.15
>> St...
>
> src/hotspot/share/code/aotCodeCache.cpp line 1294:
> 
>> 1292:     current += entries_size;
>> 1293: 
>> 1294:     //log_stats_on_exit();
> 
> Not sure if this matters since I assume this method is going to be deleted but why comment this out?

you are right, this version of `finish_write()` would go away. I was just lazy to update the code to pass the parameter that `log_stats_on_exit()` needs :)

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

PR Review Comment: https://git.openjdk.org/leyden/pull/95#discussion_r2322232683


More information about the leyden-dev mailing list