{"id":470565,"date":"2025-10-03T07:51:16","date_gmt":"2025-10-03T07:51:16","guid":{"rendered":"https:\/\/www.europesays.com\/uk\/470565\/"},"modified":"2025-10-03T07:51:16","modified_gmt":"2025-10-03T07:51:16","slug":"accelerating-our-android-apps-with-baseline-profiles","status":"publish","type":"post","link":"https:\/\/www.europesays.com\/uk\/470565\/","title":{"rendered":"Accelerating our Android apps with Baseline Profiles"},"content":{"rendered":"<p>Key Takeways:<\/p>\n<ul>\n<li>With billions of Android app users, we\u2019re always looking to improve the Meta app experience, and in this post, we explore the ways we\u2019ve leveraged Android\u2019s Baseline Profiles to significantly improve their performance.<\/li>\n<li>We discuss the performance challenges we\u2019ve faced as Meta\u2019s apps, how the needs of users have become more complex over time, and the infrastructure we\u2019ve created to solve them.<\/li>\n<li>We share our insights on creating Baseline Profiles with user data and the tuning we\u2019ve used to make them even more effective. Altogether, Baseline Profiles have improved performance for various critical metrics by up to 40% across Meta\u2019s apps.<\/li>\n<\/ul>\n<p>Application performance is critical for a good user experience. Slow startups, dropped frames and poor responsiveness are all key drivers of user frustration and, ultimately, attrition.<\/p>\n<p>Performance consciousness during application development, and use of appropriate data structures, algorithms, caching strategies, and so on, are fundamental parts of mitigating these issues. However, it is equally important to understand the underlying representations of compiled application code, and the manner in which it is loaded and executed, such that build tools and runtimes can be configured and tuned optimally.<\/p>\n<p>Over the past few years at Meta, we have developed infrastructure for profile-guided compiler and runtime optimizations targeting our Android applications. A major component of this infrastructure is the Android Runtime\u2019s Baseline Profiles feature, which we have leveraged extensively to significantly improve the performance of our Android applications.<\/p>\n<p>In this post, we\u2019ll describe some performance considerations related to the Android Runtime (ART), explore some related performance challenges we have faced in our apps, and explain how we utilized Baseline Profiles to overcome them.<\/p>\n<p>ART Performance Considerations<\/p>\n<p>On Android, the preferred, and thus dominant, languages for user application development are Kotlin and Java. Kotlin\/Java code is compiled to Dalvik bytecode (\u201cDex code\u201d) and packaged into \u201c.dex\u201d files, which are organized into classes and methods reflecting their original sources.<\/p>\n<p>Before any dex code associated with a method can be executed by the Android Runtime, its parent class must be loaded by the runtime. This happens when a class is first accessed during application execution, and involves locating the class\u2019 metadata, registering it with ART, initializing static data, and anything else required to interact with the class.<\/p>\n<p>Once its parent class is loaded, its methods may be executed. Dex code is, of course, not machine code that can be directly executed on hardware, and thus the Android Runtime must perform this translation. By default, at runtime, methods in dex code will simultaneously be executed via interpretation and profiled to determine if they are hot. Once a method is determined to be hot, it is compiled to machine code via ART\u2019s just-in-time compiler, and the compiled version is executed thereafter. (Executing machine code is generally significantly faster than interpretation.)<\/p>\n<p>Both class loads and the interpretation\/profiling stage of dex method execution have runtime cost, which often result in temporary, but user-perceptible, performance degradation.\u00a0 Furthermore, classes must be re-loaded following every app cold start. <a href=\"https:\/\/developer.android.com\/topic\/performance\/vitals\/launch-time#cold\" target=\"_blank\" rel=\"noopener\">Cold starts<\/a> happen when the system starts the app for the first time. After a cold start, the app is in memory, and subsequent starts are much faster. (Note that this is somewhat mitigated by \u201cruntime app images\u201d on Android 14+.) ART does have a means of persisting compiled methods across cold starts (this is simplified\u2014they are not strictly \u201cpersisted,\u201d and require a background dexopt run between cold starts), but they must be re-profiled and re-compiled following an app version update.<\/p>\n<p>Meta\u2019s Mobile App Challenges<\/p>\n<p>Meta\u2019s mobile applications are the primary point of access for most of our users, the majority of which use Android. Our mobile apps face several challenges in balancing shipping velocity with our performance goals. Startup performance, in particular, is especially important, as it can have a disproportionate impact on user experience.<\/p>\n<p>Maintaining a minimal set of classes loaded on startup is a key focus for startup performance. As our apps continue to add new features, such as Instagram Reels or Messenger\u2019s End-to-End Encryption, the startup classes set grows as well. Besides user-visible features, critical functionality such as crash reporting, login authentication, and performance logging are all involved in startup. Facebook and Instagram, for example, each load more than 20,000 classes on startup, and several thousand more for feed scrolling.<\/p>\n<p>We also care about improving performance for user journeys after startup. These user journeys measure key parts of the user experience, such as scrolling the user\u2019s feed, or the time it takes to fetch and render a photo. Additionally, these journeys typically specify both the user\u2019s behavior, such as scrolling or navigating, as well as where it\u2019s happening. For example, a user scrolling their feed is considered separately from scrolling their inbox, and navigating to a profile is considered separately from navigating to a feed. We prioritize optimizing different user journeys for each app, and regularly revisit whether new ones should be added, or existing ones removed.<\/p>\n<p>Optimizing user journeys requires understanding exactly what classes get loaded. For this, we collect profiles of class load sequences from many different users, and look for what they have in common. We\u2019ve found that these profiles can look dramatically different across different users, even for the same user journey. Moreover, the exact same user can still have a different profile of class loads on another day, with different code paths taken due to <a href=\"https:\/\/engineering.fb.com\/2012\/08\/08\/uncategorized\/building-and-testing-at-facebook\/\" target=\"_blank\" rel=\"noopener\">experimentation<\/a>, and can be different again the next week, as both the code and user behavior changes. Our monorepo sees thousands of commits each day. There is no easy one-size-fits-all solution here.<\/p>\n<p>In total, we have a large, growing, and dynamic set of code to manage. We need a solution that can intelligently adapt to frequent code changes between each release, can quickly generate compiled code and profiles, and can benefit both startup and other user journeys.<\/p>\n<p>ART Install-Time Optimizations<\/p>\n<p>Since Android 9, ART has offered the following install-time optimizations:<\/p>\n<ul>\n<li>AOT (\u201cAhead of Time\u201d) compilation of specified methods<\/li>\n<li>Creation of an \u201capp image\u201d with specified classes<\/li>\n<\/ul>\n<p>AOT compilation means that specified methods will be compiled to machine code by ART before the app runs for the first time. This eliminates the overhead involved in interpreting and profiling the method\u2019s initial execution.<\/p>\n<p>An <a href=\"https:\/\/www.youtube.com\/watch?v=fwMM6g7wpQ8&amp;t=2145s\" target=\"_blank\" rel=\"noopener\">app image<\/a> is a file containing a partial representation of the in-memory ART data structures, which would be created or populated by class loads for specified classes. When an app is started with an app image, it is mapped into the process\u2019 heap, and\u00a0 any necessary fixups are applied. The end result is that many classes may be effectively loaded extremely quickly at startup, and any later runtime cost associated with loading these classes is eliminated.<\/p>\n<p>These optimizations can be triggered by supplying a special profile to ART at app install time.\u00a0 There are two main mechanisms for this: Cloud Profiles and Baseline Profiles.<\/p>\n<p>Cloud Profiles<\/p>\n<p>Cloud Profiles are aggregations of profiling data from many different users collected by Google Play during the initial rollout of an app version. After the Cloud Profile has been created, all subsequent users installing that app version via Google Play will receive that Cloud Profile, which will be used by ART for AOT compilation and app image creation.<\/p>\n<p>Cloud Profiles have <a href=\"https:\/\/developer.android.com\/topic\/performance\/baselineprofiles\/overview#cloud-profiles\" target=\"_blank\" rel=\"noopener\">several downsides<\/a>, however:<\/p>\n<ul>\n<li>Earlier users in the rollout do not benefit at all from Cloud Profiles, as they\u2019re the ones providing the profiling data.<\/li>\n<li>App developers do not have any way to observe or control the classes and methods in the profile.<\/li>\n<li>They are generated in a way that is strongly skewed towards early startup improvement.<\/li>\n<li>They are only available via Google Play\u2014applications installed through other means such as different app stores or sideloading can\u2019t use them.<\/li>\n<\/ul>\n<p>Baseline Profiles<\/p>\n<p><a href=\"https:\/\/developer.android.com\/topic\/performance\/baselineprofiles\/overview\" target=\"_blank\" rel=\"noopener\">Baseline Profiles<\/a> are similar to Cloud Profiles, as they also trigger ART install-time optimizations, with a few key differences. Whereas Cloud Profiles are generated by Google Play through collecting and aggregating data from early users of an app version, Baseline Profiles are generated and provided by application developers. Developers can simply package their Baseline Profile inside their corresponding APK or AAB. When both Cloud Profiles and Baseline Profiles are available, they can be used in <a href=\"https:\/\/developer.android.com\/topic\/performance\/baselineprofiles\/overview#compilation-behaviors\" target=\"_blank\" rel=\"noopener\">tandem<\/a>.<\/p>\n<p><img loading=\"lazy\" decoding=\"async\" class=\"alignnone size-full wp-image-22895\" src=\"https:\/\/www.europesays.com\/uk\/wp-content\/uploads\/2025\/10\/image1.png\" alt=\"\" width=\"1600\" height=\"561\"  \/>Diagram showing the flow for Baseline and Cloud Profiles in Google Play. \u201cImproving App Performance with Baseline Profiles\u201d by Kateryna Semenova, Rahul Ravikumar, and Chris Craik, 28 Jan. 2022. <a href=\"https:\/\/android-developers.googleblog.com\/2022\/01\/improving-app-performance-with-baseline.html\" target=\"_blank\" rel=\"noopener\">Android Developers Blog.<\/a><\/p>\n<p>Baseline Profiles give full control of install time optimizations to application developers, and are available to users immediately. This allows developers to control install-time optimizations in a way which is much more tuned to the specific needs of their app than Cloud Profiles, including the ability to optimize for scenarios beyond startup.<\/p>\n<p>Google offers some mechanisms of generating baseline profiles from benchmarks (e.g. Macrobenchmark). However, they can also be generated by directly specifying classes and methods in a <a href=\"https:\/\/developer.android.com\/topic\/performance\/baselineprofiles\/manually-create-measure#rule_syntax\" target=\"_blank\" rel=\"noopener\">well-specified format<\/a> to a tool called <a href=\"https:\/\/android.googlesource.com\/platform\/tools\/base\/+\/refs\/heads\/mirror-goog-studio-master-dev\/profgen\/\" target=\"_blank\" rel=\"noopener\">profgen<\/a>, which offers flexibility.<\/p>\n<p>Next, we will look at how Baseline Profiles have been a very beneficial technology for the performance of Meta\u2019s apps, solving many of the challenges with ART.<\/p>\n<p>How We\u2019ve Created Baseline Profiles at Meta<\/p>\n<p>Earlier, we described challenges we have faced with our Android applications\u2019 performance. In particular, we mentioned how our apps\u2019 startups can load tens of thousands of classes on each cold start, and how our weekly shipping wipes all compiled code on every update.<\/p>\n<p>We have long been aware of and focused on these challenges, particularly related to cold start. In the past, we have seen major performance gains via ordering classes within the underlying dex file according to their typical load position during startup, due to improved locality of reference. We call this \u201cInterdex Ordering,\u201d which is done via InterdexPass in <a href=\"https:\/\/engineering.fb.com\/2016\/04\/12\/android\/open-sourcing-redex-making-android-apps-smaller-and-faster\/\" target=\"_blank\" rel=\"noopener\">Redex<\/a>, our bytecode optimizer.\u00a0 (Google\u2019s analog of this in R8 is called \u201cstartup profiles.\u201d). ART\u2019s install-time optimizations complement and improve upon this optimization by entirely eliminating the loading cost for some of these classes, and ensuring that their hot methods are compiled before the first run of the app version.<\/p>\n<p>Previously, we mentioned how developers do not directly control the Cloud Profile\u2019s contents. This particularly impacted Meta, as once a startup exceeds five seconds, the Android Runtime automatically considers the startup to be complete. This caused the Cloud Profile to insufficiently mark which classes were necessary for startup. While Cloud Profiles have undoubtedly helped here, the control and flexibility of Baseline Profiles have allowed us to fully realize the potential of these optimizations and measure large performance wins.<\/p>\n<p>To create our Baseline Profiles, we use data from a variety of sources, which we process and aggregate together, based on configurations that are subject to continuous experimentation and tuning.<\/p>\n<p>Collecting Profile Data<\/p>\n<p>In our initial Baseline Profiles experiments, we simply used the static profiles for the AndroidX libraries that are shipped alongside them. Today, we have a sophisticated set of collection technologies we use together to produce profiles for our apps.<\/p>\n<p>Benchmarks are one approach to collecting profile data. At Meta, we leverage some local benchmarks in Baseline Profile creation for some of our apps, using internal tooling we have written to collect class and method usage information. However, for apps like Facebook and Instagram, benchmarks are not sufficiently representative of production behaviour. For more complex apps like these, we additionally collect class and method usage data from users to obtain a more complete picture.<\/p>\n<p>To collect class usage data from users, we make use of a custom <a href=\"https:\/\/developer.android.com\/reference\/java\/lang\/ClassLoader\" target=\"_blank\" rel=\"noopener\">ClassLoader<\/a>, in which we insert code that logs which classes are being loaded, which is then periodically uploaded. As this collection has a performance cost, it is only conditionally enabled with a very low sample rate. The collected class load logs are then aggregated together to derive appearance frequencies, and classes exceeding a certain frequency threshold are included in the Baseline Profile for the next release.<\/p>\n<p>There is no hook that allows us to log method usage as easily. However, we include some specialized telemetry into our apps that allow us to granularly identify clusters of methods that are typically called by users. We then sample and aggregate this data similarly to class data.<\/p>\n<p>All of this data is then combined into a \u201cHuman Readable Profile\u201d and fed to profgen, which generates the final Baseline Profile. Below is an example Human Readable Profile:<\/p>\n<p><img loading=\"lazy\" decoding=\"async\" class=\"alignnone size-full wp-image-23013\" src=\"https:\/\/www.europesays.com\/uk\/wp-content\/uploads\/2025\/10\/image2_81e361.png\" alt=\"\" width=\"1400\" height=\"322\"  \/><\/p>\n<p>\u00a0<\/p>\n<p>Breaking it down we can see:<\/p>\n<ul>\n<li style=\"font-weight: 400;\" aria-level=\"1\">\u201c#\u201d are used for comment lines.<\/li>\n<li style=\"font-weight: 400;\" aria-level=\"1\">Classes can be directly specified by their descriptor.<\/li>\n<li style=\"font-weight: 400;\" aria-level=\"1\">Methods can be directly specified with optional flags.<\/li>\n<li style=\"font-weight: 400;\" aria-level=\"1\">Wildcards can be used to match all classes or methods matching a given prefix.<\/li>\n<\/ul>\n<p>Tuning and Experimentation<\/p>\n<p>Cold start was the first scenario we wanted to optimize with Baseline Profiles. We started conservatively, with high frequency thresholds for including classes and methods into the profiles, requiring a class\/method to appear in more than 80% to 90% of all collected user traces. Our concern was that shipping a Baseline Profile that was too large could actually negatively impact performance. Compiled machine code is generally 10 times larger than its original interpreted code. This size difference incurs an increased I\/O cost, with more page faults or cache misses.<\/p>\n<p>Over time, we have experimented with different inclusion thresholds, and have expanded beyond cold start to other user interactions. At present, we include classes and methods which appear in &gt;= 20% of cold start user traces for most apps. Interactions we have optimized with Baseline Profiles include newsfeed scrolling in Facebook and Instagram, navigation from thread lists to thread views in Messenger and Instagram\u2019s direct messages inbox, and general latency when navigating between app surfaces.<\/p>\n<p>We have occasionally observed startup and other regressions when running experiments that\u00a0 increase the baseline profile size, typically with indications that memory pressure has increased.\u00a0 However, with targeted and carefully measured additions, we have managed to grow our profiles quite a bit larger than we expected to be possible. At present, we have several tens of thousands of entries in the Baseline Profiles for all of our apps.<\/p>\n<p>The Impact of Baseline Profiles at Meta<\/p>\n<p>Over the past few years, we have implemented Baseline Profiles across all of our major Android apps, and observed consistently positive results from doing so.\u00a0 As we have integrated and improved upon our Baseline Profiles over time, we have measured high-percentage improvements to app start, scroll performance, navigation latency between surfaces, and several other critical performance metrics, ranging from 3% all the way up to 40%.<\/p>\n<p>Baseline Profiles have provided a powerful lever for our teams to meaningfully improve our users\u2019 experience year-on-year. Our continual investment and experimentation with Baseline Profiles have proven to be well worth it. For all Android developers, whether you already use Baseline Profiles or have yet to start, we encourage you to take some of our lessons here and apply them for yourself.<\/p>\n","protected":false},"excerpt":{"rendered":"Key Takeways: With billions of Android app users, we\u2019re always looking to improve the Meta app experience, and&hellip;\n","protected":false},"author":2,"featured_media":470566,"comment_status":"","ping_status":"","sticky":false,"template":"","format":"standard","meta":{"footnotes":""},"categories":[3159],"tags":[547,53,16,15],"class_list":{"0":"post-470565","1":"post","2":"type-post","3":"status-publish","4":"format-standard","5":"has-post-thumbnail","7":"category-mobile","8":"tag-mobile","9":"tag-technology","10":"tag-uk","11":"tag-united-kingdom"},"share_on_mastodon":{"url":"https:\/\/pubeurope.com\/@uk\/115309149162800014","error":""},"_links":{"self":[{"href":"https:\/\/www.europesays.com\/uk\/wp-json\/wp\/v2\/posts\/470565","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/www.europesays.com\/uk\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/www.europesays.com\/uk\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/www.europesays.com\/uk\/wp-json\/wp\/v2\/users\/2"}],"replies":[{"embeddable":true,"href":"https:\/\/www.europesays.com\/uk\/wp-json\/wp\/v2\/comments?post=470565"}],"version-history":[{"count":0,"href":"https:\/\/www.europesays.com\/uk\/wp-json\/wp\/v2\/posts\/470565\/revisions"}],"wp:featuredmedia":[{"embeddable":true,"href":"https:\/\/www.europesays.com\/uk\/wp-json\/wp\/v2\/media\/470566"}],"wp:attachment":[{"href":"https:\/\/www.europesays.com\/uk\/wp-json\/wp\/v2\/media?parent=470565"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/www.europesays.com\/uk\/wp-json\/wp\/v2\/categories?post=470565"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/www.europesays.com\/uk\/wp-json\/wp\/v2\/tags?post=470565"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}