Skip to content

Commit

Permalink
Reduce garbage created by LtoBackendAction#computeBitcodeInputs.
Browse files Browse the repository at this point in the history
In a build with many `LtoBackendAction` this CL reduces:
* Garbage allocated by that method by 99.8%
* Total time spent doing full GC pauses by 97.3%
* Wall time by 74.3%

Don't pay too much attention to the exact numbers. I did only a single trial since each trial takes ~40m. Instead, just pay attention to the direction of the change :)

Shout-out to jhorvitz@ for pointing out that `BitcodeFiles` is shared among `LtoBackendAction` instances and so weakly caching the result is the biggest macro optimization we can do.

PiperOrigin-RevId: 718087528
Change-Id: I017528e60b1d23e430f1a5d06bcfa8061092057c
  • Loading branch information
haxorz authored and copybara-github committed Jan 21, 2025
1 parent c7f3b73 commit dd37d6e
Show file tree
Hide file tree
Showing 2 changed files with 40 additions and 9 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -13,21 +13,29 @@
// limitations under the License.
package com.google.devtools.build.lib.rules.cpp;

import static com.google.common.collect.ImmutableMap.toImmutableMap;

import com.google.common.collect.ImmutableMap;
import com.google.common.collect.ImmutableList;
import com.google.devtools.build.lib.actions.Artifact;
import com.google.devtools.build.lib.collect.compacthashmap.CompactHashMap;
import com.google.devtools.build.lib.collect.nestedset.NestedSet;
import com.google.devtools.build.lib.util.Fingerprint;
import com.google.devtools.build.lib.vfs.PathFragment;
import java.lang.ref.WeakReference;
import java.util.Map;
import javax.annotation.Nullable;

/** Wrapper around a map of bitcode files for purposes of caching its fingerprint. */
/**
* Wrapper around a map of bitcode files for purposes of caching its fingerprint.
*
* <p>Each instance is potentially shared by many {@link LtoBackendAction} instances.
*/
final class BitcodeFiles {

private final NestedSet<Artifact> files;
@Nullable private volatile byte[] fingerprint = null;

private volatile WeakReference<Map<PathFragment, Artifact>> filesArtifactPathMapReference =
new WeakReference<>(null);

BitcodeFiles(NestedSet<Artifact> files) {
this.files = files;
}
Expand All @@ -37,9 +45,33 @@ NestedSet<Artifact> getFiles() {
}

/** Helper function to get a map from path to artifact */
ImmutableMap<PathFragment, Artifact> getFilesArtifactPathMap() {
return getFiles().toList().stream()
.collect(toImmutableMap(Artifact::getExecPath, artifact -> artifact));
Map<PathFragment, Artifact> getFilesArtifactPathMap() {
// This method is called once per LtoBackendAction instance that shares this BitcodeFiles
// instance. Therefore we weakly cache the result.
//
// It's a garbage hotspot, so we deliberately use a presized CompactHashMap instead of
// streams and ImmutableMap. In a build with many LtoBackendAction instances, this approach
// reduced garbage allocated by this method by ~65%. The approach of caching the result further
// reduced garbage up to a total reduction of >99%.

Map<PathFragment, Artifact> result = filesArtifactPathMapReference.get();
if (result != null) {
return result;
}

synchronized (this) {
result = filesArtifactPathMapReference.get();
if (result != null) {
return result;
}
ImmutableList<Artifact> filesList = getFiles().toList();
result = CompactHashMap.createWithExpectedSize(filesList.size());
for (Artifact file : filesList) {
result.put(file.getExecPath(), file);
}
filesArtifactPathMapReference = new WeakReference<>(result);
return result;
}
}

void addToFingerprint(Fingerprint fp) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -149,8 +149,7 @@ private NestedSet<Artifact> computeBitcodeInputs(
HashSet<PathFragment> inputPaths, ActionExecutionContext actionExecutionContext)
throws ActionExecutionException {
NestedSetBuilder<Artifact> bitcodeInputs = NestedSetBuilder.stableOrder();
ImmutableMap<PathFragment, Artifact> execPathToArtifact =
bitcodeFiles.getFilesArtifactPathMap();
Map<PathFragment, Artifact> execPathToArtifact = bitcodeFiles.getFilesArtifactPathMap();
Set<PathFragment> missingInputs = new HashSet<>();
for (PathFragment inputPath : inputPaths) {
Optional<Artifact> maybeArtifact = getArtifactOrTreeArtifact(inputPath, execPathToArtifact);
Expand Down

0 comments on commit dd37d6e

Please sign in to comment.