[T-Mobile LG V10 General] warBeard_actual:
I believe there is a great deal of confusion or lack of technical explanation available here in the community, when we discuss the how’s, why’s and what’s behind the things we choose to modify in the Android OS in an attempt to squeeze better performance from a very complex operating system. Many of the things I tend to see presented to users are focused on very ineffective and ancient mentalities, pertinent to an older version of the operating system. Much of this is attempted through modifying build properties, and that’s usually about where it stops. My objective here is to describe some of the ins and outs of tuning a mobile operating system such as Android, and looking at it in a different light - not the skin you lay on top of it, but as advanced hardware and software, with many adjustable knobs you can turn for a desired result.

The key players here are, usually, without fail a couple of things alone:

Debloating – which, I suppose, is an effective way to reduce the operating system’s memory footprint. But I would then ask, why not also improve the operating system’s memory management functions?

“Build prop tweaks” – which is a file where you can apply very effective changes like the ones presented in my post_boot file (the only difference being when they are executed, and how they are written out), but most of the “tuning” done here focuses on principles that were only once true and, thereby, mostly irrelevant in today’s latest versions of Android. There are many things within the build.prop that can (and sometimes should) be altered to directly impact the performance of the DVM/JVM. However, this is almost always untouched. Every now and then, somebody will throw a kernel together with some added schedulers, or some merged sound drivers, etc., but there is really little to no change that would effect real time performance.

So, what about the virtual machine? What about the core operating system? – what Android actually is – Linux.

Many of you have been pretty blown back by how effective some simple modifications to just 1 shell file on your system have been at improving your experience as a user. Your PM’s, posts, and comments in my inbox/thread are telling enough about the direct impact on battery life. These are differences you can feel and see, quantify. Because the changes made within that file are directly impacting functional aspects of the hardware, throughput/latency, and most importantly, the device’s memory management (which is so complex, you could literally write a book about it… and books about it do exist – they are very long books).

So, how did we manage to make your device feel like it was reborn with just 1 file and not an entire ROM? That ROM you were on, suddenly was not so stock feeling, right? Not to say those ROMs were stock, they were, indeed, modified. But the core operating system was, for the most part, largely untouched. Maybe you had a little more free RAM because of the debloating but, really, that was about all of the effect that you saw/felt.

My aim here is to talk about, at a medium to in-depth level, what exactly went into that 1 file that turned the performance corner for your device. For the sake of keeping it to the important points, I’ll cover the 3 most important (as titled in my main thread): Your CPU, IO, and RAM (VM). Part 2 will cover IO, and Part 3 with cover the nuts and bolts of the RAM (VM).

Let’s look at a snippet of some code from the portion of the file where most of the CPU tuning is achieved, we’ll use cluster two’s example (bear in mind, the methodology here was used for cluster 1 as well [your smaller cores were treated the same]):

# configure governor settings for big cluster
echo "interactive" > /sys/devices/system/cpu/cpu4/cpufreq/scaling_governor
echo 1 > /sys/devices/system/cpu/cpu4/cpufreq/interactive/use_sched_load
echo "10000 1536000:40000" > /sys/devices/system/cpu/cpu4/cpufreq/interactive/above_hispeed_delay
echo 20 > /sys/devices/system/cpu/cpu4/cpufreq/interactive/go_hispeed_load
echo 10000 > /sys/devices/system/cpu/cpu4/cpufreq/interactive/timer_rate
echo 633600 > /sys/devices/system/cpu/cpu4/cpufreq/interactive/hispeed_freq
echo 1 > /sys/devices/system/cpu/cpu4/cpufreq/interactive/io_is_busy
echo "40 864000:60 1248000:80 1536000:90" > /sys/devices/system/cpu/cpu4/cpufreq/interactive/target_loads
echo 30000 > /sys/devices/system/cpu/cpu4/cpufreq/interactive/min_sample_time
echo 0 > /sys/devices/system/cpu/cpu4/cpufreq/interactive/max_freq_hysteresis
echo 70 > /sys/devices/system/cpu/cpu4/cpufreq/interactive/gpu_target_load

So what did I do here?

Well, let’s start by explaining the governor, and then its modules.

Interactive: the interactive governor, in short, it works based on timers and load (or tasks). Based on load when the timers are ticked and the CPU is polled, the governor decides how to respond to that load, with consideration taken from its tunables. Because of this, interactive can be extremely exact when handling CPU load effectively. If these tunables are dialed in properly, according to usage and hardware capability, what you achieve is maximum throughput for an operation, at a nominal frequency for that specific task, with no effective delay experienced in the UI. Most of the activity seen in an Android ecosystem is short, bursty usage, with the occasional sustained load intensive operations (gaming, web browsing, HD video playback and recording, etc.). Because of this unique user-interaction with the device, the default settings for interactive are, usually, a little too aggressive for a nominal experience – nominal meaning not “over-performing” to complete the task and wasting precious CPU cycles on a system that is not always near an outlet. The interactive tunables:

use_sched_load: when this value is set to 1, the timer windows (polling intervals) for all cores are synchronized. The default is 0. I set this to 1 because it allows evaluation of current system-wide load, rather than core specific. A small, but very important change for the GTS (global task scheduler).

above_hispeed_delay: when the cpu is at or above hispeed_freq, wait this long before increasing frequency. The values called out here will always take priority, no matter how busy the system is. Notice how I tuned this particular setting to allow an unbiased ramp up until 1.53 GHz, which then calls for .4 seconds delay before allowing an increase. I did this to handle the short bursts quickly and efficiently as needed, without impacting target_load (the module, in this way, allows the governor free range and roam according to load, then, is forced to wait if it wants to utilize those super-fast but power-costly speeds up top). However, sustained load (like gaming, or loading web pages) would likely tax the CPU for more than .4 seconds. The default setting here was 20000. You can represent this expression as a single value, followed by a CPU speed and delay for that speed, which is what I did at the 1.53 GHz range. I usually design this around differences in voltage usage per frequency when my objective is more to save power, while sacrificing more performance.

go_hispeed_load: when the CPU is polled and overall load is determined to be above this value (which represents a percentage) immediately increase CPU speed to the speed set in hispeed_freq. Default value here was 99. I changed it to 20. You’ll understand why in a second.

timer_rate: intervals to check CPU load across the system (keep in mind use_sched_load). Default was 20000. I changed it to 10000 to check more often, and reduce the stack up delay the timer rate causes with other tunables.

hispeed_freq: counterpart to go_hispeed_load. Immediately jump to this frequency when that load is achieved. Default here, in Linux, is whatever the max frequency is for the core. So, it would have been 1.8 GHz when load is 99%. I changed this value to the next speed above minimum for both a53 and a57 clusters. The reason I did this was to respond appropriately to tiny bits of thread usage here and there, which minimizes the probability that the CPU will start overstepping. There are a lot of small tasks constantly running, which could allow the 384 MHz frequency to be overwhelmed by some consistent low taxing operation. The trick with this method of approach is to stay just ahead of the activity, ever so slightly, to increase efficiency, while removing latency for those smaller tasks. There is no hit in power by doing this. This principle of approach (on broad and overall scale, even) is how I use interactive to our advantage. I remove its subjective behavior by telling it exactly where to be for a set amount of time based on activity alone. There are no other variables. “When CPU load is xxxx, you will operate within these windows (speeds) alone.”

Keep in mind, with some of this, I am just giving you default values and examples… The original file that LG or Qualcomm or whoever placed in there had done their own weird crap with this stuff that didn’t make any sense whatsoever. Timer intervals were not divisibles of others, there was little logic and reason behind it.

io_is_busy: when this value is set to 1, the interactive governor evaluates IO activity, and attempts to calculate it as expected CPU load. The default value is 0. I always set this to 1, to allow the system to get a more accurate representation of anticipated CPU usage. Again, that “staying ahead of the curve” idea is stressed here in this simple but effective change.

target_loads: a general, objective tuneable. Default is 90. This tells the governor to try to keep the CPU load below this value by increasing frequency until <90 is achieved. This can also be represented as a dynamic expression, which is what I did. In short, mine says “do not increase CPU speeds above 864 MHz unless CPU load is over 60%... do not increase CPU speeds above 1.24 GHz unless CPU load is over 80%” and so on… So, you can see how we are starting to better address the “activity vs. response” computing conundrum a little more precisely. Rather than throw some arbitrary number like 90 out there, I specifically utilize a frequency window with a percentage of system-wide usage or activity. This is ideal, but takes careful dialing in, as hardware is always different. Some processors are a little more efficient, so lower speeds are ok for a given load when compared to another processor. Understanding the capability of your hardware to handle your usage patterns appropriately, is absolutely critical to get this part right – the objective is not to overwork, or underwork, but to do just the right amount of work. Turning small knobs here and there, then watching how much time your CPU spends at a given speed, and comparing that with real time performance characteristics you observe, etc… maybe there is a little more stuttering in that game you play after this last adjustment? OK, make it slightly more aggressive, or let the processor hang out a bit more at those high/moderately high speeds.

min_sample_time: this is an interval which tells the CPU to “wait this long” before scaling back down when you are not at idle. This is to make sure the CPU doesn’t scale down too quickly, only to then have to spin right back up again for the same task. The default here was 80000, which is way too aggressive IMO. Your processor, stock, would hang for nearly a second at each step on its way down. 3/10th of a second is plenty of time for consistent high load, and just right for short, bursty bits of activity. The trick here is balancing response, effectiveness, acceptable drain on power, with consideration to nominal throughput for an execution.

max_freq_hysteresis: this only comes into play when the maximum frequency is hit. This tells the governor to keep the core at the maximum speed for this long, represented in tenths of a second, PLUS min_sample_time. Default value was 3, if I remember correctly. Which means that every time your CPU hit max, it was hanging there for 1.1 seconds arbitrarily, regardless of load.

gpu_target_load: the GPU will scale up if CPU load is above this value. Default is 90. This module attempts to anticipate GPU activity based upon CPU activity. It works in parallel with the GPU’s own governor algorithms and each cluster of cores has its own tunable for this controller.

Standby in the near future for the write up on IO and RAM management.