RenderThread优化中的多RenderThread问题

975 阅读2分钟

RenderThread优化

简单来说开启硬件加速以后,会有一个RenderThread协助我们的主线程去绘制ui。 大厂中的常规优化手段有一种是调高RenderThread的线程优先级(调整到-19) 以及 将RenderThread的执行 绑定到cpu的大核上

这里面涉及到一个问题是如何找到RenderThread,要获取到RenderThread的tid

如何获取RenderThread的tid

还是依靠的Linux下的一切皆文件的思想

读取这个目录下的文件

"/proc/${Process.myPid()}/task/"

其实就是看这个进程下有多少个线程

然后遍历这些文件下的stat 文件内容 就可以得知这些线程的信息

我们根据这些线程信息就能知道 这些线程的名字和id 从而 达到我们获取RenderThread信息的目的

代码比较简单我就不写了,直接命令行展示一下 stat文件读出来是啥样子

image.png

有名字也有id,满足我们的诉求,但是你会发现 在很多app 包括我自己的app里 RenderThread会有多个

或者严格的说法是 name为RenderThread的线程有多个,这就有点意思了

如果是多个RenderThread 是不是每个RenderThread都要绑定大核 设置优先级?

多出来的RenderThread是哪来的?

jadx

首先怀疑是不是我们自己有代码设置线程名字的时候 设置了RenderThread,这里最简单的方法 debug包直接让jadx 反编译以后 搜对应的关键字,搜了一下没有

RenderThread 或许不是单例?

排查过程中怀疑过这个方向,毕竟网上复制粘贴错漏百出的文章也不是一个两个了

6.0的写法 ,明显看出来是个单例 image.png

7.0开始 换了一种写法 但本质上依然是个单例

这里要看仔细了, 我一开始没注意到 是static ,还以为这里会因为同步问题有多个。。。 image.png

显然这个方向也不对

或许是aosp代码中设置了名字?

RenderThread有没有可能是系统某个地方申请了一个thread也叫这个名字呢? 去搜了下 ,没发现有对应的代码

命令行分析

上面的路都不通,换一种思路

先看看我们自己的app 进程id 是多少

image.png

然后看下这个进程下的全部线程信息

adb shell ps -T | grep 26134

果不其然 这里3个RenderThread的状态是不一样的

image.png

image.png

注意看第一个的状态是Sys_epoll_wait 这个状态是对的,因为RenderThread 底层 就是一个loop循环 在不断等待新的任务到达

而多出来的这2个RenderThread 状态是futex,这不就是个普通thread的状态吗?这个状态其实显然就是李鬼了,只是名字恰巧一样而已

到这里我们百分之100确定这个多出来的2个RenderThread 只是名字叫这个,作用压根不是Render

李鬼哪来的

看下这里的代码

image.png

只是创建一个线程而已,而且也没有起名字为RenderThread

这里不熟悉的人很容易就被跳过去了, 这里虽然是创建了一个thread,但是她是在native下直接创建的,不是java虚拟机替我们创建的

native直接创建线程

做个实验,我们用clion连接我们的linux docker,这里主要是因为mac下么有pthread,所以需要docker帮个忙 然后创建一个c++工程,toolchains 链接到我们的docker上即可

稍微配置下cmake文件

cmake_minimum_required(VERSION 2.8.12.2)
project(untitled1)

set(CMAKE_CXX_STANDARD 11)
SET(CMAKE_CXX_FLAGS "-std=c++11 -O3")
# 寻找并链接线程库
find_package(Threads REQUIRED )
add_executable(untitled1 main.cpp)

# 链接线程库
if(THREADS_HAVE_PTHREAD_ARG)
    target_compile_options(PUBLIC untitled1 "SHELL:-pthread")
endif()

# 注意:如果 find_package(Threads) 寻找到了 pthread 库,它将定义 THREADS_PREFER_PTHREAD_FLAG 变量
# 并且设置 THREADS_HAVE_PTHREAD_ARG 以指示我们应该使用 -pthread 编译器和链接器标志。
if(CMAKE_THREAD_LIBS_INIT)
    target_link_libraries(untitled1 "${CMAKE_THREAD_LIBS_INIT}")
endif()

写一段测试代码

#include <pthread.h>
#include <thread>
#include <iostream>

void threadFunction2(){
    std::cout << "Thread started2." << std::endl;
    pthread_t threadId = pthread_self();

    constexpr size_t maxThreadNameLength = 16;
    char threadName[maxThreadNameLength];

    if(pthread_getname_np(threadId, threadName, maxThreadNameLength) == 0) {
        std::cout << "Thread started2. Thread name: " << threadName << std::endl;
    } else {
        std::cerr << "Failed to get the thread name." << std::endl;
    }
}
// 线程函数,打印线程启动信息
void threadFunction(){
    std::cout << "Thread started." << std::endl;
    std::thread t(threadFunction2);
    t.join();
}



int main() {
    // 创建线程
    std::thread t(threadFunction);
    // 获取线程原生句柄
    pthread_t nativeHandle = t.native_handle();
    pthread_setname_np(nativeHandle, "MyThread-1");
    t.join();

    return 0;
}

看下结果

image.png

这里你会发现,thread2这个线程 我们没有指定她的名字,但是因为她是在 thread1中创建的,所以他自动继承了thread1的名字

这和我们平时写java是完全不一样的

java创建匿名线程

Thread t1 = new Thread("t1"){
    @Override
    public void run() {
        Log.v("xxx","t1 name: "+Thread.currentThread().getName());
        Thread t2 = new Thread(){
            @Override
            public void run() {
                Log.v("xxx","t2 name: "+Thread.currentThread().getName());
            }
        };
        t2.start();
    }
};
t1.start();

对java来说,匿名创建的线程名字 可不是当前线程的名字,而是 Thread-数字 开头,这是和native创建线程最大的不同了

如何分辨到底哪个是真的RenderThread?

前面分析了RenderThread 真真假假的问题,最后回到主题,如何才知道哪个是真的RenderThread? 毕竟 ps - T 这个命令 我们没办法在代码里实现, stat文件信息 是拿不到 thread状态的

这里其实有个取巧的做法,取tid 最低的那个, linux中线程id 是逐步增长的, 既然我们的renderthread是个单例了,那一定是先被创建的,所以tid最低的那个 就是李逵,其他都是李鬼了