Tag Archives: Best Practices

Faster Rust Toolchains for Android

Posted by Chris Wailes - Senior Software Engineer

The performance, safety, and developer productivity provided by Rust has led to rapid adoption in the Android Platform. Since slower build times are a concern when using Rust, particularly within a massive project like Android, we've worked to ship the fastest version of the Rust toolchain that we can. To do this we leverage multiple forms of profiling and optimization, as well as tuning C/C++, linker, and Rust flags. Much of what I’m about to describe is similar to the build process for the official releases of the Rust toolchain, but tailored for the specific needs of the Android codebase. I hope that this post will be generally informative and, if you are a maintainer of a Rust toolchain, may make your life easier.

Android’s Compilers

While Android is certainly not unique in its need for a performant cross-compiling toolchain this fact, combined with the large number of daily Android build invocations, means that we must carefully balance tradeoffs between the time it takes to build a toolchain, the toolchain’s size, and the produced compiler’s performance.

Our Build Process

To be clear, the optimizations listed below are also present in the versions of rustc that are obtained using rustup. What differentiates the Android toolchain from the official releases, besides the provenance, are the cross-compilation targets available and the codebase used for profiling. All performance numbers listed below are the time it takes to build the Rust components of an Android image and may not be reflective of the speedup when compiling other codebases with our toolchain.

Codegen Units (CGU1)

When Rust compiles a crate it will break it into some number of code generation units. Each independent chunk of code is generated and optimized concurrently and then later re-combined. This approach allows LLVM to process each code generation unit separately and improves compile time but can reduce the performance of the generated code. Some of this performance can be recovered via the use of Link Time Optimization (LTO), but this isn’t guaranteed to achieve the same performance as if the crate were compiled in a single codegen unit.

To expose as many opportunities for optimization as possible and ensure reproducible builds we add the -C codegen-units=1 option to the RUSTFLAGS environment variable. This reduces the size of the toolchain by ~5.5% while increasing performance by ~1.8%.

Be aware that setting this option will slow down the time it takes to build the toolchain by ~2x (measured on our workstations).

GC Sections

Many projects, including the Rust toolchain, have functions, classes, or even entire namespaces that are not needed in certain contexts. The safest and easiest option is to leave these code objects in the final product. This will increase code size and may decrease performance (due to caching and layout issues), but it should never produce a miscompiled or mislinked binary.

It is possible, however, to ask the linker to remove code objects that aren’t transitively referenced from the main()function using the --gc-sections linker argument. The linker can only operate on a section-basis, so, if any object in a section is referenced, the entire section must be retained. This is why it is also common to pass the -ffunction-sections and -fdata-sections options to the compiler or code generation backend. This will ensure that each code object is given an independent section, thus allowing the linker’s garbage collection pass to collect objects individually.

This is one of the first optimizations we implemented and, at the time, it produced significant size savings (on the order of 100s of MiBs). However, most of these gains have been subsumed by those made from setting -C codegen-units=1 when they are used in combination and there is now no difference between the two produced toolchains in size or performance. However, due to the extra overhead, we do not always use CGU1 when building the toolchain. When testing for correctness the final speed of the compiler is less important and, as such, we allow the toolchain to be built with the default number of codegen units. In these situations we still run section GC during linking as it yields some performance and size benefits at a very low cost.

Link-Time Optimization (LTO)

A compiler can only optimize the functions and data it can see. Building a library or executable from independent object files or libraries can speed up compilation but at the cost of optimizations that depend on information that’s only available when the final binary is assembled. Link-Time Optimization gives the compiler another opportunity to analyze and modify the binary during linking.

For the Android Rust toolchain we perform thin LTO on both the C++ code in LLVM and the Rust code that makes up the Rust compiler and tools. Because the IR emitted by our clang might be a different version than the IR emitted by rustc we can’t perform cross-language LTO or statically link against libLLVM. The performance gains from using an LTO optimized shared library are greater than those from using a non-LTO optimized static library however, so we’ve opted to use shared linking.

Using CGU1, GC sections, and LTO produces a speedup of ~7.7% and size improvement of ~5.4% over the baseline. This works out to a speedup of ~6% over the previous stage in the pipeline due solely to LTO.

Profile-Guided Optimization (PGO)

Command line arguments, environment variables, and the contents of files can all influence how a program executes. Some blocks of code might be used frequently while other branches and functions may only be used when an error occurs. By profiling an application as it executes we can collect data on how often these code blocks are executed. This data can then be used to guide optimizations when recompiling the program.

We use instrumented binaries to collect profiles from both building the Rust toolchain itself and from building the Rust components of Android images for x86_64, aarch64, and riscv64. These four profiles are then combined and the toolchain is recompiled with profile-guided optimizations.

As a result, the toolchain achieves a ~19.8% speedup and 5.3% reduction in size over the baseline compiler. This is a 13.2% speedup over the previous stage in the compiler.

BOLT: Binary Optimization and Layout Tool

Even with LTO enabled the linker is still in control of the layout of the final binary. Because it isn’t being guided by any profiling information the linker might accidentally place a function that is frequently called (hot) next to a function that is rarely called (cold). When the hot function is later called all functions on the same memory page will be loaded. The cold functions are now taking up space that could be allocated to other hot functions, thus forcing the additional pages that do contain these functions to be loaded.

BOLT mitigates this problem by using an additional set of layout-focused profiling information to re-organize functions and data. For the purposes of speeding up rustc we profiled libLLVM, libstd, and librustc_driver, which are the compiler’s main dependencies. These libraries are then BOLT optimized using the following options:

--peepholes=all
--data=<path-to-profile>
--reorder-blocks=ext-tsp
–-reorder-functions=hfsort
--split-functions
--split-all-cold
--split-eh
--dyno-stats

Any additional libraries matching lib/*.so are optimized without profiles using only --peepholes=all.

Applying BOLT to our toolchain produces a speedup over the baseline compiler of ~24.7% at a size increase of ~10.9%. This is a speedup of ~6.1% over the PGOed compiler without BOLT.

If you are interested in using BOLT in your own project/build I offer these two bits of advice: 1) you’ll need to emit additional relocation information into your binaries using the -Wl,--emit-relocs linker argument and 2) use the same input library when invoking BOLT to produce the instrumented and the optimized versions.

Conclusion

Graph of normalized size and duration comparison between Toolchain size and Android Rust build time

Optimizations

Speedup vs Baseline
Monolithic 1.8%
Mono + GC Sections

1.9%
Mono + GC + LTO 7.7%
Mono + GC + LTO + PGO 19.8%

Mono + GC + LTO + PGO + BOLT

24.7%

By compiling as a single code generation unit, garbage collecting our data objects, performing both link-time and profile-guided optimizations, and leveraging the BOLT tool we were able to speed up the time it takes to compile the Rust components of Android by 24.8%. For every 50k Android builds per day run in our CI infrastructure we save ~10K hours of serial execution.

Our industry is not one to stand still and there will surely be another tool and another set of profiles in need of collecting in the near future. Until then we’ll continue making incremental improvements in search of additional performance. Happy coding!

All treats, no tricks: 6 solutions to common developers challenges

Posted by Google for Developers

For many, Halloween is the perfect excuse to dress up and celebrate the things that haunt us. Google for Developers is embracing the spirit of the season by diving into the spine-chilling challenges that spook software developers and engineers. Read on to uncover these lurking terrors and discover the tricks – and treats – to conquer them.


The code cemetery

Resilient code requires regular updates, and when it comes to solving bugs, it’s much easier to find them when there are fewer lines of code. When faced with legacy or lengthy code, consider simplifying and refreshing it to make it more manageable – because no one likes an ancient or overly complex codebase. Here are some best practices.

Start small: Don't try to update your entire codebase at once. Instead, start by updating small, isolated parts of the codebase to minimize the risk of introducing new bugs.

Use a version control system: Track your changes and easily revert to a previous version if necessary.

Consider a refactoring tool: This can help you to make changes to your code without breaking it.

Test thoroughly: Make sure to test your changes thoroughly before deploying them to production. This includes testing the changes in isolation, as well as testing them in conjunction with the rest of the codebase. See more tips about testing motivation below.

Document your changes: Include new tooling, updated APIs, and any changes so other developers understand what you have done and why.


Testing terrors

When you want to build and ship quickly, it’s tempting to avoid writing tests for your code because they might slow you down in the short term. But beware, untested code will come back to haunt you later. Testing is a best practice that can save you time, money, and angst in the long run. Even if you know you should run tests, it doesn’t mean you want to. Use these tips to help make writing tests easier.

Test gamification: Turn test writing into a game. Challenge yourself to write tests faster than your coworker can say "code coverage."

Pair programming: Write tests together with a colleague. It's like having a workout buddy – more fun and motivating.

Set up test automation: Automate tests wherever possible– it's better AND more efficient.


A monster problem: not being able to choose your tech stack

Many developers have strong preferences when it comes to products, but sometimes legacy technology or organizational needs can limit choices. This can be deflating, especially if it prevents you from using the latest tools. If you’re faced with a similar situation, it’s worth expressing your recommendations to your team. Here’s how:

Lobby for change: If the current tech stack really isn't working out, advocate for a change. This may require documentation over a series of events, but you can use that to build your case.

Pitch the benefits: If you’re ready to share your preferences, explain how your tech stack of choice benefits the project, similarly to how optimized code improves performance.

Showcase expertise: Demonstrate your knowledge in your preferred stack, whether it’s through a Proof of Concept or a presentation.

Upskill: If you have to dive into a top-down tech stack that you are not familiar with, consider it a learning opportunity. It’s like exploring a new coding language.

Compromise is key: First, recognize that all of the points above are still well-worth aiming for, but sometimes, you do have to compromise. Think of it as working with legacy code - not ideal, but doable. So if you aren’t able to influence in your favor, don’t be dismayed.


Not a trick: ship your code smarter

The only thing worse than spending the end of the week fixing buggy code isexcept for spending the weekend fixing buggy code when you had other plans. Between less time to react to problems, taking up personal time, and fewer people available to help troubleshoot – shipping code when you don’t have the proper resources in place to help is risky at best. Here are a handful of best practices to help you build a better schedule and avoid the Saturday and Sunday Scaries.

Consider business hours and user impact: Schedule deployments during off-peak times when fewer users will be impacted. For B2B companies, Friday afternoons can minimize disruption for customers, but for smaller companies, Friday deployments might mean spending your weekend fixing critical issues. Pick a schedule that works for you.

Automate testing: Implement automated testing in your development process to catch issues early.

Make sure your staging environment is right: Thoroughly test changes in a staging environment that mirrors production.

Be rollback-ready: Have a rollback plan ready to revert quickly if problems arise.

Monitoring and alerts: Set up monitoring and alerts to catch issues 24/7.

Communication: Ensure clear communication among team members regarding deployment schedules and procedures.

Scheduled deployments: If you’re a team who doesn’t regularly ship at the end of the week, consider READ-ONLY Fridays. Or if necessary, schedule Friday deployments for the morning or early afternoon.

Weekend on-call: Consider a weekend on-call rotation to address critical issues.

Post-deployment review: Analyze and learn from each deployment's challenges to improve processes.

Plan thoroughly: Ensure deployment processes are well-documented and communication is clear across teams and stakeholders.

Evaluate risks: Assess potential business and user impact to determine deployment frequency and timing.


A nightmare come true: getting hacked

Realizing you've been hacked is a heart-stopping event, but even the most tech-savvy developers are vulnerable to attacks. Before it happens to you, remember to implement these best practices.

Keep your systems and software up-to-date: Think of it as patching vulnerabilities in your code.

Use strong passwords: Just like strong encryption, use robust passwords.

Use two-factor authentication: Always add a second layer of security.

Beware of phishermen: Don't take the bait. Be as cautious with suspicious emails as you are with untested code.

Perform security audits: Regularly audit your systems for vulnerabilities, like running code reviews but for your cybersecurity.

Backup plan: Just like version control, maintain backups. They're your safety net in case things go full horror-movie.


The horror: third party data breaches

Data breaches are arguably the most terrifying yet plausible threat to developer happiness. No company wants to be associated with them, let alone the dev who chose the service or API to work with. Here are some tips for minimizing issues with third party vendors to help you avoid this scenario.

Perform due diligence on third-party vendors: Before working with a third-party vendor, carefully review their security practices and policies. Ask about security certifications, vulnerability management practices, and their incident response plan.

Require vendors to comply with security requirements: Create or add your input in a written contract with each third-party vendor that outlines the security requirements that the vendor must meet. This contract should include requirements for data encryption, access control, and incident reporting.

Monitor vendor activity: Ensure vendors comply with the security requirements in the contract by reviewing audit logs and conducting security assessments. Only grant access to data that a vendor needs to perform their job duties to help to minimize the impact of a data breach if the vendor is compromised.

Implement strong security controls: Within your own systems, protect data from unauthorized access through firewalls, intrusion detection systems, and data encryption.

Be wary of third-party APIs: Vet all security risks. Carefully review the API documentation to understand the permissions that are required and to ensure the API uses strong security practices.

Use secure coding practices: Use input validation, escaping output, and strong cryptography.

Keep software up to date: Always update with the latest security patch to help to protect against known vulnerabilities.


Creepin' it real

It’s easy to get spooked knowing what can go wrong, but by implementing these best practices, the chance of your work going awry goes down significantly.

What other spine-chilling developer challenges have you experienced? Share them with the community.

Make the passkey endpoints well-known URL part of your passkey implementation

Posted by Amy Zeppenfeld – Developer Relations Engineer

Passkeys are leading the charge towards a more secure future without passwords. Passkeys are a new type of cryptographic credential that leverages FIDO2 and WebAuthn to provide an authentication mechanism that is phishing-resistant, user friendly, simple to implement, and more secure than password-based authentication. Most major operating systems and browsers now feature full passkey support. Passkeys are expected to replace passwords as the predominant authentication mechanism in the not-too-distant future, and developers are advised to begin implementing passkey-enabled authentication solutions today.

As you implement passkeys in your app or web service, take a moment to implement a passkey endpoints well-known URL.

This is a standardized way to advertise your support for passkeys and optimize user experience. This well-known URL will allow third party services like password managers, passkey providers, and other security tools to direct users to enroll and manage their passkeys for any site that supports them. You can use app-links or deep linking with the passkey-endpoints well-known URL to allow these pages to open directly in your app.

Password management tool usage has been steadily rising, and we expect most providers will integrate passkey management as well. You can allow third party tools and services to direct your users to your dedicated passkey management page by implementing the passkey-endpoints well-known URL.

The best part is that in most cases you can implement this feature in two hours or less! All you need to do is host a simple schema on your site. Check out the example below:

  1. For a web service at https://example.com, the well-known URLwould be https://example.com/.well-known/passkey-endpoints
  2. When the URL is queried, the response should use the following schema:
{ "enroll": "https://example.com/account/manage/passkeys/create", "manage": "https://example.com/account/manage/passkeys" }

Note: You can decide the exact value of the URLs for both enroll and manage based on your website’s own configuration.

If you have a mobile app, we strongly recommend utilizing deep linking to have these URLs open the corresponding screen for each activity directly in your app to “enroll” or “manage” passkeys. This will keep your users focused and on track to enroll into passkeys.

And that’s it!

Further details and examples can be found in the passkey endpoints well-known URL explainer.

Improving user safety in OAuth flows through new OAuth Custom URI scheme restrictions

Posted by Vikrant Rana, Product Manager

OAuth 2.0 Custom URI schemes are known to be vulnerable to app impersonation attacks. As part of Google’s continuous commitment to user safety and finding ways to make it safer to use third-party applications that access Google user data, we will be restricting the use of custom URI scheme methods. They’ll be disallowed for new Chrome extensions and will no longer be supported for Android apps by default.

Disallowing Custom URI scheme redirect method for new Chrome Extensions

To protect users from malicious actors who might impersonate Chrome extensions and steal their credentials, we no longer allow new extensions to use OAuth custom URI scheme methods. Instead, implement OAuth using Chrome Identity API, a more secure way to deliver OAuth 2.0 response to your app.

What do developers need to do?

New Chrome extensions will be required to use the Chrome Identity API method for authorization. While existing OAuth client configurations are not affected by this change, we strongly encourage you to migrate them to the Chrome Identity API method. In the future, we may disallow Custom URI scheme methods and require all extensions to use the Chrome Identity API method.

Disabling Custom URI scheme redirect method for Android clients by default

By default, new Android apps will no longer be allowed to use Custom URI schemes to make authorization requests. Instead, consider using Google Identity Services for Android SDK to deliver the OAuth 2.0 response directly to your app.

What do developers need to do?

We strongly recommend switching existing apps to use the Google Identity Services for Android SDK. If you're creating a new app and the recommended alternative doesn’t work for your needs, you can enable the Custom URI scheme method for your app in the “Advanced Settings” section of the client configuration page on the Google API Console.

User-facing error message

Users may see an “invalid request” error message if they try to use an app that is making unauthorized requests using the Custom URI scheme method. They can learn more about this error by clicking on the "Learn more" link in the error message.

Image of user facing error message
User-facing error example

Developer-facing error message

Developers will be able to see additional error information when testing user flows for their applications. They can get more information about the error by clicking on the “see error details” link, including its root cause and links to instructions on how to resolve the error.

Image of developer facing error message
Developer-facing error example

Related content

Google Cloud projects: Tips and best practices

By Peter Jacobsen, Google Technical Writer

Least privilege

Always apply the principle of least privilege when you provide access to Google Cloud resources. The best practice is to grant only the most limited predefined roles or custom roles that meet your needs.

For more information, see Least privilege.

Google Cloud billing alerts

Set up Google Cloud billing alerts for your projects at specified intervals for early warning of usage patterns, and to help reduce costs.

For more information, see Create, edit, or delete budgets and budget alerts.

API quotas

API quotas protect the Google infrastructure from excessive API requests. Traffic is blocked when the level of requests reaches the daily API quota level or a per-user rate limit.

To avoid disruptions due to an API quota level that's too low, set the quota for your app or API appropriately. Note that the lead time for the increase of quotas is one month.

For more information, see API Quotas.

Checklist for production-ready enterprise workloads

Use this checklist to set up scalable, production-ready enterprise workloads. Note that the checklist assumes that you're an administrator with control over your company's Google Cloud resources.

For more information, see Google Cloud setup checklist.

Google Workspace domain ownership of projects

Google Workspace domain ownership of your group's project lets you tie it into a Google Workspace account, rather than have it tied to a personal account.

For more information, see Best practices for planning accounts and organizations.

Identity-Aware Proxy (IAP)

IAP lets you hide your website until you’re ready for people to see it. IAP establishes a central authorization layer for apps accessed by HTTPS, so you can adopt an app-level access-control model rather than use network-level firewalls. When IAP protects an app or resource, only users who have the correct Identity and Access Management (IAM) role can access it through the proxy.

For more information, see Identity-Aware Proxy overview.

Cloud Build

Cloud Build can import source code from a variety of repositories or cloud storage spaces, execute a build to your specifications, and produce artifacts, such as Docker containers or Java archives. You can configure builds to fetch dependencies and run unit tests, static analyzes, and integration tests.

For more information, see Cloud Build.

Useful Google Cloud tools and services

Google Cloud has many tools and services that can help you create and keep your projects in sync, such as:

  • Cloud Build: executes your builds on Google Cloud infrastructure.
  • Google Cloud Deploy: deploys releases continuously to Google Kubernetes Engine.
  • Container Registry: provides a single place for your team to manage Docker images and control access.
  • Artifact Registry: provides a single place for your organization to manage container images and language packages, such as Maven and npm.
  • Cloud Source Repositories: provides a single place for your team to store, manage, and track code.
  • Cloud Deployment Manager: automates the creation and management of Google Cloud resources.

Google Groups for management across projects

Google Groups can help you manage teams across projects, which includes the setup of the group access through IAM. Groups such as project teams, departments, or classmates can communicate and collaborate with Google Groups. If you want to invite a group to an event or share documents with a group, you can send a single email to everyone in the group.

For more information about how to set up a group, see Google Groups.

Watch for Google suggestions

Google provides many useful tips and suggestions for best practices within the context of your work. For example, if you go to a project that you haven't used in a while, you may get a warning like this one:

If you click the link, you see a page that tells you how to apply role recommendations to help you enforce the principle of least privilege to ensure that principals have only the permissions that they actually need. Google offers many suggestions for best practices such as this one, so watch for them as you work.

Here's an example of a useful in-console recommendation that you might see from your billing page:

If you click Learn more, you arrive at a Cloud billing checklist, which is part of a longer billing-specific checklist that you might find useful.

Here's another example found on the API & Services page:

If you click Edit settings, you arrive on a page where you can change the settings.

Android Q Scoped Storage: Best Practices and Updates

Posted by Jeff Sharkey, Software Engineer, and Seb Grubb, Product Manager

Application Sandboxing is a core part of Android’s design, isolating apps from each other. In Android Q, taking the same fundamental principle from Application Sandboxing, we introduced Scoped Storage.

Since the Beta 1 release, you’ve given us a lot of valuable feedback on these changes -- thank you for helping shape Android! Because of your feedback, we've evolved the feature during the course of Android Q Beta. In this post, we'll share options for declaring your app’s support for Scoped Storage on Android Q devices, and best practices for questions we've heard from the community.

Updates to help you adopt Scoped Storage

We expect that Scoped Storage should have minimal impact to apps following current storage best practices. However, we also heard from you that Scoped Storage can be an elaborate change for some apps and you could use more time to assess the impact. Being developers ourselves, we understand you may need some additional time to ensure your app’s compatibility with this change. We want to help.

In the upcoming Beta 3 release, apps that target Android 9 Pie (API level 28) or lower will see no change, by default, to how storage works from previous Android versions. As you update your existing app to work with Scoped Storage, you’ll be able to use a new manifest attribute to enable the new behavior for your app on Android Q devices, even if your app is targeting API level 28 or lower.

The implementation details of these changes will be available with the Beta 3 release, but we wanted to share this update with you early, so you can better prepare your app for Android Q devices. Scoped Storage will be required in next year’s major platform release for all apps, independent of target SDK level, so we recommend you add support to your app well in advance. Please continue letting us know your feedback and how we can better align Scoped Storage with your app’s use cases. You can give us input through this survey, or file bugs and feature requests here.

Best practices for common feedback areas

Your feedback is incredibly valuable and has helped us shape these design decisions. We also want to take a moment to share some best practices for common questions we’ve heard:

  • Storing shared media files. For apps that handle files that users expect to be sharable with other apps (such as photos) and be retained after the app has been uninstalled, use the MediaStore API. There are specific collections for common media files: Audio, Video, and Images. For other file types, you can store them in the new Downloads collection. To access files from the Downloads collection, apps must use the system picker.
  • Storing app-internal files. If your app is designed to handle files not meant to be shared with other apps, store them in your package-specific directories. This helps keep files organized and limit file clutter as the OS will manage cleanup when the app is uninstalled. Calls to Context.getExternalFilesDir() will continue to work.
  • Working with permissions and file ownership. For MediaStore, no permissions are necessary for apps that only access their own files. Your app will need to request permission to access media contributed by other apps. However, if your app is uninstalled and then reinstalled later, you’ll need to request permission from the user in order to be able to access media your app previously contributed.
  • Working with native code or libraries. The recommended pattern is to begin your media file discovery in your Java-based or Kotlin-based code, then pass the file's associated file descriptor into your native code.
  • Working with many files efficiently. If you need to perform bulk file operations in a single transaction, consider using ContentProvider.applyBatch(). Learn more about ContentProvider batch processing here.
  • Integrating with the system file picker.
    • Documents apps, such as a word processor, can use the ACTION_OPEN_DOCUMENT or ACTION_GET_CONTENT action to open a system file picker. You can learn more about the differences here.
    • File management apps typically work with collections of apps in a directory hierarchy. Use ACTION_OPEN_DOCUMENT_TREE to let the user pick a directory subtree. The app can further manipulate files available in the returned directory. Through this support, users can access files from any installed DocumentsProvider instance, which can be supported by any cloud-based or locally-backed storage solutions.

We’ve also provided a detailed Scoped Storage developer guide with additional information.

What’s ahead

It’s been amazing to see the community engagement on Android Q Beta so far. As we finalize the release in the next several months, please continue testing and keep the feedback coming. Join us at Google I/O 2019 for more details on Scoped Storage and other Android Q features. We’re giving a ”What’s new on Shared Storage” talk on May 8, and you’ll be able to find the livestream and recorded video on the Google I/O site.