Skip to main content
Profiling helps identify performance bottlenecks in native code, enabling you to optimize CPU usage, memory allocation, and overall application performance.

Profiling tools overview

The NDK and Android platform provide several profiling tools:
  • Simpleperf - CPU profiling tool for native code, part of the NDK
  • Android Studio Profiler - Visual profiling with native support
  • Perfetto/Systrace - System-wide performance tracing
  • Heapprofd - Native memory profiling
Start with Android Studio Profiler for quick insights, then use Simpleperf for detailed CPU analysis.

Preparing for profiling

Enable profiling in your build

In build.gradle:
For CMake builds:
Frame pointers slightly increase binary size but provide much better profiling data.

CPU profiling with Simpleperf

Simpleperf is a command-line profiling tool that uses the CPU’s performance monitoring unit (PMU).

Installing Simpleperf

Recording CPU profile

1

Push Simpleperf to device

2

Record profile data

3

Pull profile data

4

Generate report

Interpreting Simpleperf output

Text report shows function-level CPU usage:
  • Overhead - Percentage of CPU time spent in this function
  • Symbol - Function name (symbolicated if debug symbols available)
Focus optimization efforts on functions with high overhead percentages.

Advanced Simpleperf options

Profiling with Android Studio

CPU profiler

1

Open the Profiler

View > Tool Windows > Profiler
2

Start CPU recording

Click CPU timeline, then click Record. Choose:
  • Java/Kotlin Method Trace - For Java/Kotlin profiling
  • System Trace - For native and system profiling
  • Sampled (Native) - For native code sampling
3

Perform operations

Interact with your app to trigger the code you want to profile.
4

Stop and analyze

Click Stop. The profiler displays:
  • Flame chart - Visualize call stack over time
  • Top Down/Bottom Up - Function call hierarchy
  • Call Chart - Timeline of function calls

Memory profiler

Profile native memory allocations:
  1. Open Memory Profiler
  2. Click Record native allocations
  3. Perform operations
  4. Stop recording
  5. Analyze allocation call stacks
Native memory profiling requires Android 10+ (API level 29) and a profileable or debuggable app.

System-wide tracing with Perfetto

Perfetto (successor to systrace) provides system-wide performance traces.

Recording a trace

Using command line

Using System Tracing app

  1. Install System Tracing app from Play Store
  2. Open app and tap Record trace
  3. Select categories and duration
  4. Perform operations in your app
  5. Stop recording and share trace file

Analyzing traces

Open trace at ui.perfetto.dev:
  • View thread activity over time
  • Identify frame drops and jank
  • Analyze scheduling and CPU usage
  • Inspect native function calls
Use the search function to find specific events or thread names.

Adding custom trace points

Native tracing with ATrace

Add to CMakeLists.txt:

Scoped tracing helper

Identifying performance bottlenecks

CPU bottlenecks

Look for:
  • Functions with high overhead in Simpleperf
  • Long-running operations blocking UI thread
  • Inefficient algorithms (O(n²) when O(n log n) possible)

Memory bottlenecks

Look for:
  • Frequent allocations in hot paths
  • Memory leaks (growing memory usage)
  • Cache misses

I/O bottlenecks

Look for:
  • File operations on main thread
  • Synchronous network calls
  • Excessive logging
Never perform I/O operations in audio or rendering callbacks - they must complete in microseconds.

Optimization techniques

Use NEON SIMD instructions

Enable compiler optimizations

Reduce memory allocations

Cache-friendly data structures

Structure of arrays (SoA) often improves performance when processing large amounts of data.

Benchmarking

Measure performance consistently:

Automated benchmarking

Use Google Benchmark library:

Best practices

  • Profile on real devices - Emulator performance doesn’t match real hardware
  • Profile release builds - Debug builds can be 10x slower
  • Profile representative workloads - Test with realistic data and usage patterns
  • Use frame pointers - Enable for better stack traces in profiling
  • Focus on hot paths - Optimize code that runs frequently
  • Measure before and after - Verify optimizations actually improve performance
  • Consider battery impact - Balance performance with power consumption
  • Test on low-end devices - Ensure acceptable performance on minimum-spec devices
Premature optimization is the root of all evil. Profile first, then optimize the actual bottlenecks.

Additional resources