CPU frequency scaling and process affinity on an Odroid XU4



The Odroid XU4 is a SBC designed by the Korean company Hardkernel. The heart of the SBC is the Samsung Exynos 5422 SoC, an octacore SoC based on the big.LITTLE concept. The four performance cores are ARM A15 cores (max. 2 GHz), whereas the four efficiency cores are A7 (max. 1,4 GHz). It also comes with 2 GB of RAM, so plenty of headroom to experiment with some stuff.

I recently started playing with Docker. With a new 'smart' TV in the house that requires network connectivity in some way or another, I set up a separate VLAN for the TV to isolate it from the rest of the network. While that will shield your network from the prying eyes of Google's Android TV, it will still be able itself to talk to the internet unhindered. Enter DNS filtering. While the primary intent is to limit tracking, ad blocking is a nice plus. I looked at both Pi-hole and AdGuard Home - both open source solutions. Most people seemed to agree the latter had an easier GUI, so I went with that.

The Odroid XU4 isn't running much - it's mostly local networked storage like a few git repositories that should be accessible throughout the LAN, so a power efficient always-on device like the XU4 is ideal for that. While the router itself (a MikroTik RB5009UG+S+IN) has plenty of horsepower to run a heavier DNS server, I did not want to wear out its NAND (or run it off USB storage), so I decided to put AdGuard on the XU4. To keep stuff tidy (and to learn some more about Docker) I decided to deploy it through Docker. Until now, though, the Odroid XU4 had been running on default powersaving settings - that is, the ondemand CPU governor, and a low base clock (200 MHz for the whole SoC). Armbian allows you to set that up easily through armbian-config.

CPU frequency management

First thing to tackle was the cpufrequtil governor. Ondemand always ramps up frequency with some delay, which means when processing power is needed, it takes time. Schedutil is a relatively recent addition to the CPU governor family, and since some OpenWrt targets already make use of it I figured it warranted a closer look (ondemand hasn't exactly been receiving high praise). So I switched to schedutil, again through armbian-config. For some (probably dated) background info on schedutil, see here.

After selecting a new governor, I decided to increase the minimum clock of the performance cluster. The increase in power consumption won't be huge, but the system should be a bit more responsive from the get-go. I switched the performance cores to a 600 MHz base clock, with a 1,6 GHz maximum clock. The efficiency cores are left at their 200 MHz base clock and set to a 1 GHz maximum clock.

Cores 0-3 are part of the lower clocked efficiency cluster. Cores 4-7 form the performance cluster.

# Efficiency cluster
echo "200000" > /sys/devices/system/cpu/cpufreq/policy0/scaling_min_freq
echo "1000000" > /sys/devices/system/cpu/cpufreq/policy0/scaling_max_freq

# Performance cluster
echo "600000" > /sys/devices/system/cpu/cpufreq/policy4/scaling_min_freq
echo "1600000" > /sys/devices/system/cpu/cpufreq/policy4/scaling_max_freq

Process affinity

Since we do not want the Docker daemon or child processes to run on an efficiency core, we need to manually assign it to the performance cluster. Enter taskset, part of the util-linux package on Debian. Like the scaling values above, I have added the code below into /etc/rc.local. Backgrounding it makes sure the whole operation won't make your boot process pause. The code below makes a list of all docker PIDs (dockerd, docker-proxy, ...) and locks them to the performance cluster (4-7).

(
  sleep 10
  for _PID in $(/usr/bin/pgrep docker); do
      /usr/bin/taskset -cp 4-7 $_PID
  done
) &

To check whether a process got assigned to the right cluster:

# pgrep dockerd
1105
# grep Cpus_allowed_list /proc/1105/status
Cpus_allowed_list:      4-7