Skip to content

Conversation

bamboo
Copy link
Member

@bamboo bamboo commented Jul 18, 2024

With this change, the configuration cache entry for assembleDebug on perf-android-large-2 is 74% smaller (356M against 1.3G) and it runs ~30% faster on a cache hit:

Screenshot 2024-07-18 at 10 46 21 Screenshot 2024-07-18 at 10 51 42

Gradle performance tests also show improvement.

Reviewing cheatsheet

Before merging the PR, comments starting with

  • ❌ ❓must be fixed
  • 🤔 💅 should be fixed
  • 💭 may be fixed
  • 🎉 celebrate happy things

@bamboo bamboo self-assigned this Jul 18, 2024
@bamboo

This comment has been minimized.

@bot-gradle

This comment has been minimized.

@bamboo
Copy link
Member Author

bamboo commented Jul 18, 2024

@bot-gradle test ACC without PTS

@bot-gradle
Copy link
Collaborator

I've triggered the following builds with parameters: -DenablePredictiveTestSelection=false for you. Click here to see all build failures.

@bamboo bamboo force-pushed the bamboo/cc/dedupe-strings branch from 52c9ece to 2f372c4 Compare July 18, 2024 17:40
@bamboo bamboo changed the title WIP Deduplicate strings stored to the configuration cache Jul 18, 2024
@bamboo bamboo changed the title Deduplicate strings stored to the configuration cache Deduplicate strings stored in the configuration cache Jul 18, 2024
@bamboo bamboo marked this pull request as ready for review July 18, 2024 17:41
@bamboo bamboo requested a review from a team July 18, 2024 17:41
@bamboo bamboo requested a review from a team as a code owner July 18, 2024 17:41
@bamboo bamboo requested review from abstratt and alllex and removed request for a team July 18, 2024 17:41
@bamboo bamboo added in:configuration-cache Configuration Caching a:performance-improvement Performance improvement potential labels Jul 18, 2024
@bamboo bamboo added this to the 8.10 RC1 milestone Jul 18, 2024
Copy link
Member

@mlopatkin mlopatkin left a comment

Choose a reason for hiding this comment

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

Awesome results! I've left a few questions before approving.

Copy link
Member

Choose a reason for hiding this comment

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

❓ This is also used for Dependency Engine cache data. It looks like this isn't cross-version, but can you confirm, just in case?

Copy link
Member Author

Choose a reason for hiding this comment

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

Yes, it seems to be only used for temporary caches here but I'm confirming with bt-dependency-management just in case.

Copy link
Member Author

Choose a reason for hiding this comment

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

Confirmed.

*/
Integer newIndex = strings.size() + 2;
strings.put(key, newIndex);
writeStringIndex(1);
Copy link
Member

Choose a reason for hiding this comment

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

❌ Can we have a constants for 0 and 1 instead of these magic numbers? E.g. NULL_STRING_INDEX = 0 and NEW_STRING = 1.

string = strings[idx];
int index = readStringIndex();
switch (index) {
case 0:
Copy link
Member

Choose a reason for hiding this comment

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

🤔 One potential trick: ATM we're wasting two first elements of the array. We can avoid this and replace switch with a single if, though I'm not sure if it affects performance much:

  1. Let's use 0 for new string and 1 for null
  2. Have null in the initial array: INITIAL_CAPACITY_MARKER = new String [] { null }.
  3. Now we can change the switch into
if (index == 0) return readNewString();
return strings[index - 1];

But I guess this goes to microbenchmarking territory.

Copy link
Member Author

Choose a reason for hiding this comment

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

Interesting idea! I went with a variation that avoids the subtraction on every read.

Copy link
Member Author

Choose a reason for hiding this comment

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

On a second thought, using the two constants on both encoder and decoder makes the connection clearer and it doesn't seem to be a clear performance advantage either way.

* stream.
*/
public class StringDeduplicatingKryoBackedDecoder extends AbstractDecoder implements Decoder, Closeable {
public static final int INITIAL_CAPACITY = 32;
Copy link
Member

Choose a reason for hiding this comment

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

❔ Looks like it can be private too.

* @see com.esotericsoftware.kryo.io.Output#writeString(java.lang.String)
*/
@SuppressWarnings('GrDeprecatedAPIUsage')
private static byte[] encodedBytesOf(String string) {
Copy link
Member

Choose a reason for hiding this comment

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

🤔 TBH, I think this test now knows too much of the encoding details and has become fragile.

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. It can still be useful and less fragile by having the asserts ignoring the last character but I'd deal with it later once we introduce compression.

}
def service = gradle.sharedServices.registerIfAbsent('stringChecker', StringDedupCheckerService) {}
allprojects {
Copy link
Member

Choose a reason for hiding this comment

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

❓ Is this test IP-incompatible? It can be a settings script that uses the new lifecycle API.
❓ With parallel project saving, will string deduplication work across project? Or is the goal to have it across projects?

Copy link
Member Author

@bamboo bamboo Jul 18, 2024

Choose a reason for hiding this comment

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

Is this test IP-incompatible? It can be a settings script that uses the new lifecycle API.

Right, good point.

With parallel project saving, will string deduplication work across project? Or is the goal to have it across projects?

Yes, that would be the idea. We discussed having all the strings handled by a separate String Pool worker, let's see if we can make that fly.

if (string === null) {
string = s
} else {
assert string === s
Copy link
Member

Choose a reason for hiding this comment

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

🎉 nice

@bamboo
Copy link
Member Author

bamboo commented Jul 18, 2024

@bot-gradle test RFN without PTS

@bot-gradle
Copy link
Collaborator

I've triggered the following builds with parameters: -DenablePredictiveTestSelection=false for you. Click here to see all build failures.

@bamboo bamboo force-pushed the bamboo/cc/dedupe-strings branch from 62d1ba8 to 2000402 Compare July 18, 2024 23:36
@bamboo
Copy link
Member Author

bamboo commented Jul 18, 2024

Thanks for the review, @mlopatkin, please take another look.

@pablobaxter
Copy link

I only did a size check of the config cache on the 6200+ project I am working on. Results are as follows:
The config cache with 8.5 (current version) is ~759.2MB.
The config cache with the 8.10 nightly is ~776.9MB.
The config cache with the local build (8.10) is ~232.2MB.

@bamboo bamboo requested a review from a team as a code owner July 19, 2024 17:21
bamboo added 4 commits July 19, 2024 14:32
For ~70% smaller configuration cache entries, faster loading times and
potentially reduced memory usage on cache hits.
- write string indexes as small ints for reduced file sizes (~10% smaller)
- avoid `null` check on every `readNullableString()` operation
bamboo added 2 commits July 19, 2024 14:32
So `getWritePosition` can be polymorphically exposed from `KryoBackedEncoder`
and `StringDeduplicatingKryoBackedEncoder`.

./gradlew :architecture-test:test -ParchunitRefreeze
And use it to restore `ConfigurationCacheEncryptionIntegrationTest` to its
former behavior.
@bamboo bamboo force-pushed the bamboo/cc/dedupe-strings branch from c37d777 to 7688645 Compare July 19, 2024 17:32
@bamboo bamboo added this pull request to the merge queue Jul 19, 2024
@bot-gradle
Copy link
Collaborator

WARN: Based on labels, this pull request addresses notable issue but no changes to release note found.

@bot-gradle
Copy link
Collaborator

The merge queue build has failed. Click here to see all failures.

Merged via the queue into master with commit 22b1905 Jul 19, 2024
@bamboo bamboo deleted the bamboo/cc/dedupe-strings branch July 19, 2024 20:03
@ov7a ov7a added the pending:release-notes Indicates that the issue requires a release notes entry label Jul 24, 2024
@joshfriend
Copy link

I did a configuration phase benchmark in our build to test the performance against 8.6:

configure_only {
    warm-ups = 3
    iterations = 20
    versions = ["8.6", "8.10-rc-1"]
    tasks = [
        ":apps:some-app:app:assembleDebug"
    ]
    gradle-args = [
        "--dry-run",
    ]
    clear-configuration-cache-state-before = BUILD
}

image

Then a CC load benchmark (same scenario but with clear-configuration-cache-state-before = BUILD removed:

image

CC size for this 3700 project build was 718MB in 8.6, and in 8.10-rc-1 it is 212MB!

@bamboo
Copy link
Member Author

bamboo commented Aug 19, 2024

Thanks for sharing the data, @joshfriend! That's great to see 👍

@ov7a ov7a removed the pending:release-notes Indicates that the issue requires a release notes entry label Sep 30, 2024
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

a:performance-improvement Performance improvement potential in:configuration-cache Configuration Caching

Projects

None yet

Development

Successfully merging this pull request may close these issues.

6 participants