背景

为了进一步优化APP功用,最近针对怎么进步运用对CPU的资源运用、以及在多线程环境下怎么进步要害线程的履行优先级做了技能调研。本文是对技能调研进程的阶段性总结,将分别介绍普通运用怎么调控App频率、怎么将指定线程绑定到特定CPU、怎么经过进步线程优先级取得更多CPU时刻片的履行。

CPU调频

概念

一般更高的cpu频率代表了更快的运转速度,一个设备或许包含多个cpu,以我现在运用的Mi 11 Pro为例,它的CPU为8核分别为,1 x 2.84GHz (ARM 最新Cortex X1 中心)+3 x 2.4GHz (Cortex A78)+4 x 1.8GHz (Cortex A55) 。 这儿列出的CPU频率为CPU物理理论上的最大频率,在实践运转进程中cpu的频率规模为governor动态操控的。现在的Androd设备普遍选用 schedutil gover进行调频操控,它会依据运转进程的CPU负载进行调频,不过默许的调频存在一些限制,比方调频之间的距离需>10ms, 而且依据schedutil的升频核算公式,并不保证能直接升频到最高频率。

Android平台下的cpu利用率优化实现

在实践运用中,假如咱们现已知道接下来需求履行高cpu负载使命,经过提早主动升频来进步功用,就能减少卡顿或许进步使命的履行耗时。

Android 体系能够经过 echo [频率] > /sys/devices/system/cpu/cpu*/cpufreq/scaling_setspeed 来修正方针CPU的频率,但这需求root权限才干履行。关于普通的运用程序,经过调研发现,高通供给了一套针对高通芯片的功用操控SDK Performance , 利用这个套机制能够完成CPU频率等资源的办理。

高通供给的这套SDK分为多个模块, 关于详细的介绍能够参阅文章:/post/714119…

总归咱们需求知道,在Java层 /android/util/BoostFramework.java类封装了一些根本的API供给给framework层调用。

因为这是高通CPU平台特定的特定完成,在aosp开源代码中无法直接查找到该类,不过经过一番查找,从其他开源体系中我找到了这个类的完成.

Android平台下的cpu利用率优化实现

完成

经过阅读BoostFramework的源码,能够发现其完成主要是对 QPerformance.jar 和UxPerformance.jar中的API进行了一层反射调用包装。那么一样的,咱们也能够经过封装对 BoostFrameWork类的调用供给我提频才能。

不过这些函数似乎并不是默许公开的内容,直接经过google查找 并没有找到关于BoostFramwork或许高通Performance API的相关信息。 最终仍是经过其他各种要害字检索,总算找到了部分有用信息

Android平台下的cpu利用率优化实现

Android平台下的cpu利用率优化实现

经过对应API文档及运用示例得知perfLocakAcquire 该函数接受 2个参数,第一个参数为持续时刻、第二个参数为一个int数组,表明详细的操作,数组中的内容为 k-v 结构形式,比方 [config1,value,config2,value] . 该函数履行时会回来一个 PerfLock句柄,后续经过调用 perfLockReleaseHandler 能够提早撤销之前的操作。

这儿简略罗列一些装备项对应的值

/**
         * 是否允许CPU进入深度低功耗模式, 对应 /dev/cpu_dma_latency, 默许空,不允许则设置为1
         */
const val MPCTLV3_ALL_CPUS_PWR_CLPS_DIS = 0x40400000
/**
         * 对应操控小核最小频率
         */
const val MPCTLV3_MIN_FREQ_CLUSTER_LITTLE_CORE_0 = 0x40800100
/**
         * 对应操控小核最大频率
         */
const val MPCTLV3_MAX_FREQ_CLUSTER_LITTLE_CORE_0 = 0x40804100
/**
         * 对应操控大核最小频率
         */
const val MPCTLV3_MIN_FREQ_CLUSTER_BIG_CORE_0 = 0x40800000
/**
         * 对应操控大核最大频率
         */
const val MPCTLV3_MAX_FREQ_CLUSTER_BIG_CORE_0 = 0x40804000
/**
         * 对应操控超大核最小频率
         */
const val MPCTLV3_MIN_FREQ_CLUSTER_PLUS_CORE_0 = 0x40800200;
/**
         * 对应操控超大核最小频率
         */
const val MPCTLV3_MAX_FREQ_CLUSTER_PLUS_CORE_0 = 0x40804200
/**
         * 不太清楚,似乎是调度加快
         */
const val MPCTLV3_SCHED_BOOST = 0x40C00000;

完好的装备项能够拜见github:github.com/Knight-ZXW/…

Android平台下的cpu利用率优化实现

别的,怎么确定咱们的设备包含高通的这套功用调控SDK呢? 能够经过检查你的Android设备存储途径/system/framework/途径,假如包含了 QPerformance.jar 及 QXPerformance.jar 就表明接入了SDK。

Android平台下的cpu利用率优化实现

依据上面的知识点,最终该东西类完好的完成代码如下

  1. 首要在init 函数中反射并获取 “android.util.BoostFramework”类的相应函数
  2. 供给 boostCpu 函数,该函数传入一个参数,表明进步cpu频率持续多久,该函数内部调用perfLockAcquire 函数 将一切CPU频率进步到最高值
  3. 供给 stopBoost 函数,该函数会将前面调用的boostCpu 作用提早撤销
package com.knightboost.optimize.cpuboost
import android.content.Context
import java.lang.reflect.Method
import java.util.concurrent.CopyOnWriteArrayList
class QcmCpuPerformance : CpuPerformance {
    companion object {
        const val TAG = "QcmCpuPerformance";
        /**
         * 是否允许CPU进入深度低功耗模式, 对应 /dev/cpu_dma_latency, 默许空,不允许则设置为1
         */
        const val MPCTLV3_ALL_CPUS_PWR_CLPS_DIS = 0x40400000
        /**
         * 设置小核最小频率,十六进制
         */
        const val MPCTLV3_MIN_FREQ_CLUSTER_LITTLE_CORE_0 = 0x40800100
        /**
         * 设置小核最大频率, 十六进制
         */
        const val MPCTLV3_MAX_FREQ_CLUSTER_LITTLE_CORE_0 = 0x40804100
        /**
         * 设置大核最小频率,十六进制
         */
        const val MPCTLV3_MIN_FREQ_CLUSTER_BIG_CORE_0 = 0x40800000
        /**
         * 设置大核最大频率,十六进制
         */
        const val MPCTLV3_MAX_FREQ_CLUSTER_BIG_CORE_0 = 0x40804000
        const val MPCTLV3_MIN_FREQ_CLUSTER_PLUS_CORE_0 = 0x40800200;
        const val MPCTLV3_MAX_FREQ_CLUSTER_PLUS_CORE_0 = 0x40804200
        /**
         * 调度优化?  启动 值为01
         */
        const val MPCTLV3_SCHED_BOOST = 0x40C00000;
    }
    var initSuccess = false
    lateinit var acquireFunc: Method
    lateinit var mPerfHintFunc: Method
    lateinit var releaseFunc: Method
    lateinit var frameworkInstance: Any
    var boostHandlers = CopyOnWriteArrayList<Int>()
    /**
     * 装备: 恳求将一切CPU中心频率拉满,并制止进入深入低功耗模式
     */
    private var CONFIGS_FREQUENCY_HIGH = intArrayOf(
        MPCTLV3_SCHED_BOOST, 1,
        MPCTLV3_ALL_CPUS_PWR_CLPS_DIS, 1,
        MPCTLV3_MAX_FREQ_CLUSTER_BIG_CORE_0, 0xFFF,
        MPCTLV3_MAX_FREQ_CLUSTER_LITTLE_CORE_0, 0xFFF,
        MPCTLV3_MIN_FREQ_CLUSTER_BIG_CORE_0, 0xFFF,
        MPCTLV3_MIN_FREQ_CLUSTER_LITTLE_CORE_0, 0xFFF,
        MPCTLV3_MIN_FREQ_CLUSTER_PLUS_CORE_0, 0xFFF,
        MPCTLV3_MAX_FREQ_CLUSTER_PLUS_CORE_0, 0xFFF,
    )
    var DISABLE_POWER_COLLAPSE = intArrayOf(MPCTLV3_ALL_CPUS_PWR_CLPS_DIS, 1)
    /**
     * 初始化CpuBoost 中心功用
     */
    override fun init(context: Context): Boolean {
        try {
            val boostFrameworkClass = Class.forName("android.util.BoostFramework")
            val constructor = boostFrameworkClass.getConstructor(Context::class.java)
                ?: return false
            frameworkInstance = constructor.newInstance(context)
            acquireFunc = boostFrameworkClass.getDeclaredMethod(
                "perfLockAcquire", Integer.TYPE, IntArray::class.java
            )
            mPerfHintFunc = boostFrameworkClass.getMethod(
                "perfHint", Int::class.javaPrimitiveType, String::class.java, Int::class.javaPrimitiveType, Int::class.javaPrimitiveType
            )
            releaseFunc = boostFrameworkClass.getDeclaredMethod(
                "perfLockReleaseHandler", Integer.TYPE
            )
            initSuccess = true
            return true
        } catch (e: Exception) {
            initSuccess = false
            CpuBoostManager.boostErrorLog(TAG, "init failed", e)
            return false
        }
    }
    /**
     * 进步一切中心CPU频率到最高频率
     */
    override fun boostCpu(duration: Int): Boolean {
        if (!initSuccess) return false
        return try {
            perfLockAcquire(duration, DISABLE_POWER_COLLAPSE)
            perfLockAcquire(duration, CONFIGS_FREQUENCY_HIGH)
            return true
        } catch (e: Exception) {
            CpuBoostManager.boostErrorLog(TAG, "boostCpuFailed", e)
            false
        }
    }
    /**
     *   Toggle off all optimizations requested Immediately.
     *   Use this function if you want to release before the time duration ends.
     *
     *   这个函数并不强制调用,只用于提早撤销一切已装备的加快作用。
     */
    override fun stopBoost() {
        val handlers = boostHandlers.toTypedArray()
        for (handler in handlers) {
            try {
                releaseFunc.invoke(frameworkInstance, handler)
            } catch (e: Exception) {
                e.printStackTrace()
            }
        }
    }
    /**
     * Toggle on all optimizations requested.
     * @param duration: The maximum amount of time required to hold the lock.
     *       Only a positive integer value in milliseconds will be accepted.
     *       You may explicitly call perfLockRelease before the timer expires.
     * @param list Enter all optimizations required. Only the optimizations in the
     *       table below are supported. You can only choose one optimization
     *       from each of the numbered sections in the table. Incorrect or
     *       unsupported optimizations will be ignored.
     *
     *       NOTE: Enter the optimizations required in the order they appear in the table.
     */
    private fun perfLockAcquire(duration: Int, list: IntArray): Int {
        val handler = acquireFunc.invoke(frameworkInstance, duration, list) as Int;
        if (handler > 0) {
            boostHandlers.add(handler)
        }
        return handler
    }
}

验证

经过读取/sys/devices/system/cpu/cpu$cpuIndex/cpufreq/下的文件,能够获取对应cpu所能运转的最小、最大、以及当时的频率。

在提频前,当时设备的cpu频率信息如下

Android平台下的cpu利用率优化实现

能够发现提频前,0-3 这些小核中,3个运转在最大调频频率,1个运转在最小调频频率,4-6中核都运转在最小频率,7号大核直接摸鱼运转在最小频率。

在提频后,运转数据如下:

Android平台下的cpu利用率优化实现

能够看出,进行提频后,一切中心都运转在最大频率上,整机频率相比之前进步30%, 当然在实践运转进程中,提频前的作业频率并不会这么低,这儿的数据是从CPU简直闲暇状态到直接满频的状况。

线程CPU亲和性

概念

依据wikipedia上的解释,经过设置CPU亲和功用够操控线程在哪些CPU上运转。

经过CPU亲和性的概念能够进步线程的运转功率,比方因为cpu存在缓存机制,经过cpu亲和性(CPU Affinity)让同一个线程被重新调度时,尽量调度到同一个处理器上,这样就能够能够防止不必要的 Cache Miss。 另一种状况,比方关于一组相同的使命,它们需求拜访的内存大部分是相同的,假如操控这组使命调度在相同的CPU上,也能够同享相同的cache,然后进步程序的拜访功率。

CPU亲和性分位2种,分别为软亲和性和硬亲和性。

  • 软亲和性: linux体系会竟或许将一个进程保持在指定的CPU上运转,但不严格保证,当所指定的CPU非常繁忙时,它能够迁移到其他闲暇CPU上履行
  • 硬亲和性:linux体系允许指定某个进程运转在特定的一个或一组CPU上,而且只能运转在这些特定的CPU上。

鄙人文中,咱们评论的亲和性操控将只涉及到硬亲和性。

亲和性操控

API

在linux体系中,能够经过taskset指令或许程序中调用 sched_setaffinity 指定线程的cpu亲和性。

taskset的详细用法为 taskset [-ap] [mask] [PID]

Android平台下的cpu利用率优化实现

这儿的mask指的是CPU掩码,CPU掩码描述了详细哪些CPU,以8核CPU为例,

二进制 00000011 (十进制值为3), 表明CPU序号1 和2, 当调用指令 tasket -p 3 2001 表明序号为2001的进程将只会运转在 cpu 1 或2 上。 也就是说CPU掩码依据对应二进制方位及其0或1的值,表明某个线程的CPU相关亲和性。

当我测验在 Android设备上直接调用 taskset指令,体系提示无权限。

Android平台下的cpu利用率优化实现

为了进一步了解 taskset程序的完成,为后续咱们自己完成CPU操控供给参阅,这儿研讨了一下其完成代码。该东西的完成源码在 util-linux项目中。

上面提示的 failed to get xx's affinity其实是在调用 sched_getaffinity 函数时就失败了。 这儿我的设备未Root,因而猜想原因为 sched_setaffinity 、sched_getaffinity 底层涉及的体系调用只要当时进程才有权限操控其本身的affinity属性。

Android平台下的cpu利用率优化实现

经过其源码完成能够发现该东西完成就是套了层皮,底层完成仍是调用的 sched_setaffinity函数。

运用层操控完成

有了上述背景,在native层编写一个cpu亲和性操控的函数就比较简略了,主要涉及到sched.h头文件的几个函数, 以下为最终完成示例代码

#include <jni.h>
#include "unistd.h"
#include "sched.h"
#include "android/log.h"
Java_com_knightboost_optimize_cpuboost_ThreadCpuAffinityManager_setCpuAffinity(JNIEnv *env,
                                                                            jclass clazz,
                                                                            jint tid,
                                                                            jintArray cpu_set) {
  if (tid <= 0) {
    tid = gettid();
  }
  // 获取当时CPU中心数
  int cpu_count = sysconf(_SC_NPROCESSORS_CONF);
  jsize size = env->GetArrayLength(cpu_set);
  jint bind_cpus[size];
  env->GetIntArrayRegion(cpu_set, 0, size, bind_cpus);
  cpu_set_t mask;
  CPU_ZERO(&mask);
  for (jint cpu : bind_cpus) {
    if (cpu > 0 && cpu < cpu_count) {
      CPU_SET(cpu, &mask); //设置对应cpu方位的值为1
    } else {
      __android_log_print(ANDROID_LOG_ERROR,
                          "TCpuAffinity",
                          "try bind illegal cpu index %d",cpu);
    }
  }
  int code = sched_setaffinity(tid, sizeof(mask), &mask);
  if (code == 0) {
    // return success
    return JNI_TRUE;
  } else {
    __android_log_print(ANDROID_LOG_ERROR,
                        "TCpuAffinity",
                        "setCpuAffinity() failed code %d",code);
    // return failed
    return JNI_FALSE;
  }
}

该函数中,首要获取了当时的CPU中心数,接下来创立一个 cpu_set_t mask变量,调用宏函数 CPU_SET 将对应方位的二进制值设置为1, 最终调用 sched_setaffinity 设置相应线程的cpu亲和性。

在实践运用场景中,咱们能够将某个线程需求履行繁重使命时,将它绑定到大核上,当使命履行结束时,再复原原始的cpu亲和性值或许将其cpu亲和性值重置为一切CPU。

验证

到现在所讲的都仍是理论阶段,那么咱们怎么确认修正线程的CPU亲和性之后,这个线程的确被迁移到方针CPU上履行了呢?

在之前写过的一篇CPU相关的文章《Android 高版别收集体系CPU运用率的方法》中,咱们提及了 stat文件记录了线程当时指向状态的相关信息。依据linux手册, 第 39 处的值就表明了该线程最终运转的CPU。

Android平台下的cpu利用率优化实现

因而经过读取该文件,咱们就能够获取线程所运转在哪个CPU上:

/**
     * 获取方针线程最终运转在哪个CPU
     */
    fun getLastRunOnCpu(tid:Int):Int{
        var path = "/proc/${android.os.Process.myPid()}/task/${tid}/stat"
        try {
            val content = File(path).readText()
            var arrays = StringUtil.splitWorker(content,' ')
            var cpu = arrays[38]
            return cpu.toInt()
        }catch (e:Exception){
            // this task  may already have ended
            return -1;
        }
    }

这儿咱们需求获取Java线程对应操作体系的线程id(tid),关于 tid 的获取能够参阅之前的文章:/post/713869…

咱们经过获取Java Thread方针的 nativePeer值,这个地址对应了Android native层的Thread方针指针地址,再依据tls_32bit_sized_values结构的tid属性偏移值,进行类型强转,然后获取体系线程id。

在Demo中,在修正方针线程CPU后,咱们能够持续打印这个值,以验证绑核是否成功。

这儿我测验将方针线程的 affinity修正为大核(CPU序号7),打印成果如下

Android平台下的cpu利用率优化实现

能够看到,在履行修正前,方针线程的cpu亲和性为0~7中心,且最近1秒根本运转在cpu中心2上,在修正CPU亲和性为 cpu7后, 方针线程只会运转在cpu7 上。 这验证了功用的确收效了。

线程优先级

概念

除了CPU频率、线程CPU亲和性,线程的优先级也会影响线程对cpu的运用,线程优先级更高意味该线程有更高的概率取得CPU的履行,分配到更多的CPU时刻片。

完成

在Android平台下,能够经过Process.setThreadPriority(int tid, int priority) ,这适用于无法获取方针线程的Thread方针,只知道方针线程tid的状况。

Android平台下的cpu利用率优化实现

当然,假如能够获取到Thread方针,也能够经过 Thred方针的 setPriority(int newPriority)设置。

Android平台下的cpu利用率优化实现

需求注意的是,这2个函数优先级int值的定义和规模是不同的,第一个函数是Android体系供给的Java接口,它的取值规模为-1920 对应linux的 nice值, 而第二个函数是Java jdk供给的,它的优先级规模为110。

别的,Process.setThreadPriority(int tid, int priority) 这儿的tid 需求的是实践的操作体系线程ID,而不是Java中Thread的id。

另一方面,Thread.setPriority(int newPriority) 函数设置的优先级并没有达到最大值,咱们能够测验下运用Thread方针的设置优先级函数为最高值(Thread.MAX_PRIORITY) 之后的nice值 ,并和 Process.setThreadPriority进行比较,测验代码如下:

Thread{
    var currentThread = Thread.currentThread()
    var tid = ArtThread.getTid(currentThread)
    Log.e("priorityTest","当时线程 $tid" +
            " java优先级 ${currentThread.priority} nice值 ${ThreadUtil.getNice(tid)}")
    currentThread.priority=Thread.MAX_PRIORITY;
    Log.e("priorityTest","运用 Thread.setPriority 设置最高优级10 后  nice值 ${ThreadUtil.getNice(tid)}")
    Process.setThreadPriority(tid,-20)
    Log.e("priorityTest","运用 Process.setThreadPriority 设置最高优级-20 后  nice值 ${ThreadUtil.getNice(tid)}")
}.start()

测验成果如下:

Android平台下的cpu利用率优化实现

由此可见,假如期望最大程度进步线程优先级的话,仍是需求运用Process的相关函数。

那么这儿为什么Android体系下经过Thread.setPriority 设置的最高优先级nice值为什么为-8呢?经过盯梢native层代码途径发现,这儿Java线程优先级的1~10 对应的nice值运用了一个数组存储了对应的优先级,其间的最高优先级10对应的 ANDROID_PRIORITY_URGENT_DISPLAY 对应的nice值就为-8

Android平台下的cpu利用率优化实现

Android体系关于什么状况下运用什么nice值 完好定义如下:

Android平台下的cpu利用率优化实现

验证

为了验证设置线程优先级对线程取得CPU时刻片的进步作用,咱们创立一组作业线程,并同时履行,每个线程会履行一个类似死循环的作业,这样每个线程都不会主动让出CPU,作业5秒后,核算当时线程得到CPU履行的时刻。 为了更好比照线程优先级对CPU时刻片分配的影响,咱们将这组线程一致绑定到一个中心上,这样能够更好的观测线程优先级对CPU时刻片分配的的影响。

Android平台下的cpu利用率优化实现

依据输出成果能够发现,优先级为-20的线程占用了cpu98%的履行时刻,其他线程简直没得到履行。

而假如将线程优先级修正为0,也就是默许的线程优先级,那么这4个线程将会得到简直相同的履行时刻。

Android平台下的cpu利用率优化实现

从这个成果看,线程优先级的作用仍是比较明显的。

不过在实践状况中,假如这些线程并没有特别指定在某个CPU履行,那么它们或许会在任何CPU上履行,体系会主动将线程调度到其他不繁忙的CPU上。

以下是指定了 task4的优先级,但并没有绑定CPU核的状况 输出的成果:

Android平台下的cpu利用率优化实现

这儿有2个信息

  • 一开始task 2、3被分配cpu上,task 1、4被分配在cpu5上,因为咱们的使命简直是一个空循环使命,对CPU的运用率较高,此刻每个使命都无法得到足够的CPU时刻片履行,而1、4中心或许又简直是闲暇的,因而体系主动将部分线程迁移到闲暇的CPU上履行
  • 因为线程被分配的不同的CPU上,因而这几个线程之间不存在优先级比较联系,因而每个使命都得到了充足的cpu时刻履行

从这儿咱们也能够看出,不合理的强绑定CPU中心, 有时候或许会起到反作用。

最终

本文只是共享了Android体系下自主操控cpu频率、线程指定中心和优先级的方法,不过这些才能需求详细落实到事务场景才干够取得实践的收益。

假如经过本文对你有所收成,能够来个点赞、保藏、重视三连,后续将共享更多功用监控与优化相关的文章。

本文相关测验完成代码已开源至: github.com/Knight-ZXW/…

APM功用监控与优化专栏

功用优化专栏历史文章:

文章 地址
抖音消息调度优化启动速度方案实践 /post/721766…
扒一扒抖音是怎么做线程优化的 /post/721244…
监控Android Looper Message调度的另一种姿势 /post/713974…
Android 高版别收集体系CPU运用率的方法 /post/713503…
Android 平台下的 Method Trace 完成及运用 /post/710713…
Android 怎么解决运用SharedPreferences 造成的卡顿、ANR问题 /post/705476…
根据JVMTI 完成功用监控 /post/694278…

参阅资料

  • www.iteye.com/blog/429564…
  • usermanual.wiki/Document/kb…
  • lwn.net/Articles/79…
  • dumps.tadiphone.dev/dumps/onepl…
  • deepinout.com/qcom-camx-d…
  • www.manongzj.com/blog/31-sgl…
  • powerhint.xml、powerhint.xml
  • en.wikipedia.org/wiki/Proces…