1 // Copyright 2019 The Chromium Authors 2 // Use of this source code is governed by a BSD-style license that can be 3 // found in the LICENSE file. 4 5 package org.chromium.base.library_loader; 6 7 import org.jni_zero.JNINamespace; 8 import org.jni_zero.NativeMethods; 9 10 import org.chromium.base.CommandLine; 11 import org.chromium.base.ContextUtils; 12 import org.chromium.base.SysUtils; 13 import org.chromium.base.TraceEvent; 14 import org.chromium.base.metrics.RecordHistogram; 15 import org.chromium.base.task.PostTask; 16 import org.chromium.base.task.TaskTraits; 17 18 import java.util.concurrent.atomic.AtomicBoolean; 19 20 /** 21 * Handles native library prefetch. 22 * 23 * See also base/android/library_loader/library_prefetcher_hooks.cc, which contains 24 * the native counterpart to this class. 25 */ 26 @JNINamespace("base::android") 27 public class LibraryPrefetcher { 28 29 private static final String TAG = "LibraryPrefetcher"; 30 // One-way switch that becomes true once {@link asyncPrefetchLibrariesToMemory} has been called. 31 private static final AtomicBoolean sPrefetchLibraryHasBeenCalled = new AtomicBoolean(); 32 33 /** 34 * Prefetches the native libraries in a background thread. 35 * 36 * Launches a task that, through a short-lived forked process, reads a 37 * part of each page of the native library. This is done to warm up the 38 * page cache, turning hard page faults into soft ones. 39 * 40 * This is done this way, as testing shows that fadvise(FADV_WILLNEED) is 41 * detrimental to the startup time. 42 */ asyncPrefetchLibrariesToMemory()43 public static void asyncPrefetchLibrariesToMemory() { 44 SysUtils.logPageFaultCountToTracing(); 45 46 final boolean coldStart = sPrefetchLibraryHasBeenCalled.compareAndSet(false, true); 47 // Collection should start close to the native library load, but doesn't have 48 // to be simultaneous with it. Also, don't prefetch in this case, as this would 49 // skew the results. 50 if (coldStart && CommandLine.getInstance().hasSwitch("log-native-library-residency")) { 51 // LibraryPrefetcherJni.get().periodicallyCollectResidency() sleeps, run it on another 52 // thread, and not on the thread pool. 53 new Thread(() -> LibraryPrefetcherJni.get().periodicallyCollectResidency()).start(); 54 return; 55 } 56 57 PostTask.postTask( 58 TaskTraits.USER_BLOCKING, 59 () -> { 60 int percentage = 61 LibraryPrefetcherJni.get().percentageOfResidentNativeLibraryCode(); 62 try (TraceEvent e = 63 TraceEvent.scoped( 64 "LibraryPrefetcher.asyncPrefetchLibrariesToMemory", 65 Integer.toString(percentage))) { 66 // Arbitrary percentage threshold. If most of the native library is already 67 // resident (likely with monochrome), don't bother creating a prefetch 68 // process. 69 boolean prefetch = coldStart && percentage < 90; 70 if (prefetch) LibraryPrefetcherJni.get().forkAndPrefetchNativeLibrary(); 71 if (percentage != -1) { 72 String histogram = 73 "LibraryLoader.PercentageOfResidentCodeBeforePrefetch" 74 + (coldStart ? ".ColdStartup" : ".WarmStartup"); 75 RecordHistogram.recordPercentageHistogram(histogram, percentage); 76 } 77 } 78 // Removes a dead flag, don't remove the removal code before M77 at least. 79 ContextUtils.getAppSharedPreferences() 80 .edit() 81 .remove("dont_prefetch_libraries") 82 .apply(); 83 }); 84 } 85 86 @NativeMethods 87 interface Natives { 88 // Finds the ranges corresponding to the native library pages, forks a new 89 // process to prefetch these pages and waits for it. The new process then 90 // terminates. This is blocking. forkAndPrefetchNativeLibrary()91 void forkAndPrefetchNativeLibrary(); 92 93 // Returns the percentage of the native library code page that are currently reseident in 94 // memory. percentageOfResidentNativeLibraryCode()95 int percentageOfResidentNativeLibraryCode(); 96 97 // Periodically logs native library residency from this thread. periodicallyCollectResidency()98 void periodicallyCollectResidency(); 99 } 100 } 101