This is the second article in the “Systrace Thread CPU State Analysis Tips” series. It analyzes the causes of the “Running” state in Systrace and provides optimization strategies for when Running segments are excessively long.
The goal of this series is to use Systrace to view the Android system from a different perspective and to learn the Framework through visualization. While reading Framework source code can be difficult to remember, seeing the flow in Systrace can lead to deeper understanding. You can find the complete Systrace Basics and Action Series here.
Technical review — 2026-10-14: This 2022 article retains its six investigation categories, with corrected Running, ART compilation, and CPU-frequency interpretation and current tooling references. Scheduling and compilation vary by Android version, kernel, and device; color or a keyword alone does not establish the cause.
Table of Contents
- Systrace Thread CPU State Analysis Tips - Runnable
- Systrace Thread CPU State Analysis Tips - Running
- Systrace Thread CPU State Analysis Tips - Sleep and Uninterruptible Sleep
Long Running Duration
Visualization
Historical Systrace usually shows a Running thread in green: it is scheduled on a logical CPU. For new recordings, use Perfetto scheduling tracks and inspect the state and events rather than color alone.
Distinguish an instrumented function’s elapsed time (wall time), the thread’s scheduled running time, and its Runnable wait for a CPU. A long function interval can include locks, Binder, I/O, or scheduling waits. Conversely, Running does not mean every cycle retires useful instructions: cache misses, memory stalls, and interrupts affect its interpretation.
Select the interval relevant to the user scenario, clip scheduling slices at its boundaries, and avoid adding nested function durations twice. This article includes no complete trace or test program; the six causes below need verification rather than describing a newly reproduced device issue.
Cause 1: High Code Complexity and Execution Time
Start with the amount of work: repeated calculations, algorithmic complexity, loop counts, allocations, and copying can all increase running time. Platform libraries and system calls may also be hotspots, but a function name alone does not implicate a vendor modification.
Use a release-like build and compare the same workload, inputs, and device conditions with Android Studio Profiler or simpleperf, alongside scheduling data. Sample weight is neither invocation count nor the exact duration of each call. Account for missing symbols, unwinding limitations, and capture overhead.
To instrument known code, use Trace.beginSection() / Trace.endSection(), available from API 18. Synchronous sections must be paired on the same thread and correctly nested; use try/finally so exceptions also end the section. These sections measure elapsed time. Do not span arbitrary cross-thread asynchronous work with a synchronous begin/end pair.
Cause 2: Interpreted Code Execution
A Compiling label does not establish that the business code is being interpreted. In the Android 12.0.0_r3 ART JIT compiler, JIT compiling, Compiling OSR, Compiling optimized, and Compiling baseline surround compilation work. That work can consume CPU, but inspect the owning thread, stack, and compilation state before attributing the application’s delay. JIT “baseline” here is also distinct from an app’s Baseline Profile file.
Since Android 7, ART combines AOT, JIT, and interpretation. A newly installed app may already benefit from installation-time compilation or profiles; it is not necessarily entirely interpreted. The result of dex2oat also depends on its filter and profile, rather than proving every method is AOT-compiled.
A current investigation can:
- Specify whether it measures first installation, cold startup, or warmed execution, and control the build, inputs, and thermal conditions.
- Generate and correctly deliver Baseline Profiles for critical paths to reduce interpretation/JIT costs on first use. Startup Profiles instead inform DEX layout at build time; the two serve different purposes.
- Explicitly compare Macrobenchmark
CompilationModesettings.Noneskips precompilation but still permits JIT during execution.Fullis not guaranteed to beatPartial, because code size and instruction-cache pressure can change the result. One faster run after manual compilation does not establish a production benefit.
Frequent JNI transitions, data conversion, or reflection can also cost time, but measure the actual hot path first. Follow the official JNI guidance to reduce unnecessary crossings and marshalling rather than treating the presence of a language feature as the diagnosis.
Cause 3: Thread Running on Small Cores
CPU-bound work may take longer on a lower-capacity core, but little cores are not reserved for non-UX work. Placement balances load, energy, allowed CPUs, and thermal policy. Memory- or I/O-bound work may not improve much after moving to a big core.
Correlate the real topology, scheduling tracks, and frequency over the same window before blaming placement. Ordinary apps should not treat affinity or SchedBoost as universally available guarantees. A narrow affinity mask can increase Runnable latency; see the Runnable article.
For sustained workloads with reportable target and actual durations, evaluate PerformanceHintManager, available from Android 12 / API 31. It supplies hints rather than directly selecting a core, and results depend on device support and policy.
Cause 4: Big Cores Running at Low Frequencies
Low frequency can reflect workload changes, energy policy, or thermal limits. One frequency value does not prove scaling is insufficiently aggressive. Align frequency events with the relevant CPU and running interval, and check what the signal represents: a requested frequency or sampled value need not equal the actual clock throughout the interval. A CPUFreq policy may also be shared by multiple cores. See the Linux CPUFreq documentation.
Optimization suggestions:
- Reduce unnecessary work and compare the same workload before and after placement or frequency changes.
- Use supported public performance-hint APIs where appropriate instead of depending on private frequency-locking controls.
- Governor, frequency-limit, and platform boost tuning belongs to system-level investigation. Controlled experiments can isolate a cause, but locked-frequency results do not represent ordinary user conditions. Platform changes also need power, temperature, and sustained-performance evaluation.
Cause 5: Thermal Throttling: Core Shutdown and Frequency Capping
Thermal management may limit frequency, power, or other resources; the mechanism varies by device and does not always involve taking cores offline. Establish whether thermal limiting occurred in the problem window before identifying its cause. A long Running interval or low frequency alone is insufficient.
External factors include ambient heat and sunlight. Internal sources may include CPU, GPU, modem, camera, or charging activity. Compare the same workload, starting temperature, charging state, and run duration rather than mixing cold-device and hot-device results. Remove unnecessary foreground or background work first; for necessary work, consider suitable changes to concurrency, resolution, or quality.
Apps can use PowerManager thermal status from API 29 and thermal headroom from API 30 to assist adaptation. Devices may incompletely support them or return unavailable results; these are not generic Celsius readings. See PowerManager and the Thermal API guide.
Platform tuning must respect the device’s own cooling, power, and temperature constraints. Competitor comparisons can suggest questions, but do not justify relaxing protection thresholds or treating a higher short benchmark score as better sustained experience.
Cause 6: Weak CPU Compute Power
At the same clock speed, different microarchitectures, caches, memory systems, and instruction paths can perform differently. MIPS (Million Instructions Per Second) is not a universal performance ranking across architectures or programs; a higher instruction rate can simply mean more work was executed. Compare task completion time, CPU time, energy, and correctness.
Two optimization paths:
- Build and compilation: app developers can inspect release/R8 settings, native compiler optimization, and profiles. Compare controlled changes using toolchain documentation rather than piling on flags. A vendor compiler’s claims do not replace measurements of this app.
- Code and data access: locate hotspots through sampling, then use hardware counters where device support and permissions allow, to investigate cycles, instructions, and cache behavior.
Check PMU event definitions, measurement scope, and denominators; account for counter multiplexing and comparability across core types. cache-misses need not denote the same cache level on every device, and a high miss rate alone does not prove the main bottleneck. See simpleperf events and statistics.
- If locality is the bottleneck, reduce unnecessary copies or improve data layout and access order, then measure again.
- “Too many complex instructions” is not a diagnosis: algorithms, branches, vectorization, dependencies, and memory stalls can all matter.
- Business-cache hit rate and CPU hardware-cache miss rate are different metrics. Bound caches and define invalidation; investigate allocation churn with memory and GC evidence so that avoiding repeated allocations does not create excessive retention.
About Me && Blog
Below is my personal intro and related links. I look forward to exchanging ideas with fellow professionals. “When three walk together, one can always be my teacher!”
- Blogger Intro: Includes personal WeChat and WeChat group links.
- Blog Content Navigation: A guide for my blog content.
- Curated Excellent Blog Articles - Android Performance Optimization Must-Knows: Welcome to recommend projects/articles.
- Android Performance Optimization Knowledge Planet: The paid community stopped operating in 2026 and no longer accepts new members; this link preserves its history.
One walks faster alone, but a group walks further together.
