首页 > 其他分享 >Systrace看GPU渲染花费时间之Fence

Systrace看GPU渲染花费时间之Fence

时间:2023-08-18 22:45:20浏览次数:45  
标签:Fence fence 作用域 ATRACE Systrace FenceMonitor GPU message

一、前言

如上图所示的 Systrace 中,VSYNC-app 基本上没有什么变化,但是 VSYNC-sf 却一直在更新有可能是什么原因?

VSYNC-app 的作用通知 app 去开始进行绘制渲染更新 UI 了,DispSync 按照屏幕的刷新率的速率去通知 app,因此 app 会以跟屏幕刷新率匹配的速率去绘制渲染更新 UI。而在手游情况就有不同了,目前绝大部分手游都是使用游戏引擎,例如 Unity 或者 Unreal,而在这些游戏引擎中,引擎自己会去控制渲染的帧率(一般有多个帧率档位可以选择,例如和平精英有 20-90 的帧率可供选择),因此,手游就可以不去听 VSYNC-app 的速率,从而就有了上面 Systrace 中的现象了。

这期我们来一起学习一下如何在 Systrace 中查看 GPU 渲染花费的时间。建议大家在看这篇文章的时候可以配合着这篇 《fps 的计算原理》,缕清楚各个 fence 的作用和意义,有助于你理解这篇文章。


二、queueBuffer

我们应该经常能够看到一个 BufferQueue 中 Buffer 的流转图:

我们知道,app,也就是图中的 PRODUCER,它通过 dequeueBuffer() 从 BufferQueue 中获取到一个 Buffer,然后决定了要绘制的内容以后,会交给 GPU 去做最后的渲染工作,同时会通过 queueBuffer() 将 Buffer 还给 BufferQueue。所以,在 PRODUCER 执行 queueBuffer() 的时候,渲染工作是没有完成的。
那么我们能否从 Systrace 中了解到 GPU 何时渲染完成呢?如果 GPU 渲染的时间长,那么其实就可以初步判断出性能瓶颈出自于 GPU 侧了。


三、FenceMonitor

答案是可以的。在 Android Q 中,libgui 引入了一个新的内部类 —— FenceMonitor,作用是通过跟踪 Fence 的生命周期,在 Systrace 中展示一个 Fence 从产生到 signal 需要的时间。在 Surface.cpp 的 Surface::dequeueBuffer() 和 Surface::queueBuffer() 分别有两个 FenceMonitor 的静态变量,分别跟踪了 release fence 和 acquire fence 的生命周期:

int Surface::dequeueBuffer(android_native_buffer_t** buffer, int* fenceFd) {
    ......
        static FenceMonitor hwcReleaseThread("HWC release"); //传的是名字"HWC release"
        hwcReleaseThread.queueFence(fence);
        
int Surface::queueBuffer(android_native_buffer_t* buffer, int fenceFd) {
    ......
        static FenceMonitor hwcReleaseThread("HWC release");
        hwcReleaseThread.queueFence(fence);

让我们来看一下它是如何实现这个效果的:


三、实现原理

class FenceMonitor {
public:
    explicit FenceMonitor(const char* name) : mName(name), mFencesQueued(0), mFencesSignaled(0) {
        std::thread thread(&FenceMonitor::loop, this);
        pthread_setname_np(thread.native_handle(), mName);
        thread.detach();
    }

    void queueFence(const sp<Fence>& fence) {
        char message[64];

        std::lock_guard<std::mutex> lock(mMutex);
        if (fence->getSignalTime() != Fence::SIGNAL_TIME_PENDING) {
            snprintf(message, sizeof(message), "%s fence %u has signaled", mName, mFencesQueued);
            ATRACE_NAME(message);
            // Need an increment on both to make the trace number correct.
            mFencesQueued++;
            mFencesSignaled++;
            return;
        }
        snprintf(message, sizeof(message), "Trace %s fence %u", mName, mFencesQueued);
        ATRACE_NAME(message);

        // 将需要跟踪声明周期的 fence 入队并且唤醒 threadLoop() 中阻塞的循环
        mQueue.push_back(fence);
        mCondition.notify_one();
        mFencesQueued++;
        ATRACE_INT(mName, int32_t(mQueue.size()));
    }

private:
    void loop() {
        while (true) {
            threadLoop();
        }
    }

    void threadLoop() {
        sp<Fence> fence;
        uint32_t fenceNum;
        {
            std::unique_lock<std::mutex> lock(mMutex);
            // 在没有执行 queueFence() 之前,mQueue 为空,会被阻塞在这里
            while (mQueue.empty()) {
                mCondition.wait(lock);
            }
            // 获取队头的 fence 开始进行声明周期的跟踪
            fence = mQueue[0];
            fenceNum = mFencesSignaled;
        }
        // 注意这里使用了一个块作用域的小技巧,下面会详细说明
        {
            char message[64];
            //这里的mName就是 New FenceMonitor 对象时指定的名字
            snprintf(message, sizeof(message), "waiting for %s %u", mName, fenceNum);
            ATRACE_NAME(message);

            status_t result = fence->waitForever(message); //也有trace打印
            if (result != OK) {
                ALOGE("Error waiting for fence: %d", result);
            }
        }
        {
            std::lock_guard<std::mutex> lock(mMutex);
            mQueue.pop_front();
            mFencesSignaled++;
            ATRACE_INT(mName, int32_t(mQueue.size()));
        }
    }

    const char* mName;
    uint32_t mFencesQueued;
    uint32_t mFencesSignaled;
    std::deque<sp<Fence>> mQueue;
    std::condition_variable mCondition;
    std::mutex mMutex;
};

构造函数会去创建一个线程并且执行 loop() 这个函数,而 loop() 就是一个死循环,会一直去运行 threadLoop()。在 threadLoop() 中,分为三个块作用域(作用域中定义的对象退出作用域后析构),首先 threadLoop() 会阻塞在第一个块作用域,直到执行 queueFence() 为止。

而当执行 queueFence() 以后,传入的 fence 会入队,并且唤醒 threadLoop() 中循环,然后获取到队头的 fence。

进入第二个块作用域,开始进行生命周期的监听。Fence 生命周期的监听使用了 C++ 块作用域和 ATRACE_NAME() 析构函数的小技巧。ATRACE_CALL() 实际上是 ATRACE_NAME() 的一个特例(传入的字符串参数为当前调用的函数名),而 ATRACE_NAME() 本身定义了一个类型为 ScopedTrace 的变量,其构造函数会去调用 atrace_begin(),析构函数会去调用 atrace_end()。因此,在第二个块作用域中,通过 ATRACE_NAME() 调用 ScopedTrace 的构造函数间接调用 atrace_begin(),然后调用 waitForever() 等待 fence 被 signal,接着第二个块作用域结束,调用 ScopedTrace 的析构函数间接调用 atrace_end(),从而达到了跟踪 fence 生命周期的效果。

我们就可以在 Systrace 中看到如下的内容:

每个 waiting for GPU completion xxx 的长度,就是 GPU 渲染所需要的时间(即 acquire fence 释放的总时间),通过这个时间,我们就可以来判断是否有 GPU bound 的现象。

相应的,waiting for HWC release XXX 的长度就是 release fence 的释放总时间,在 release fence signal 之前,GPU 是无法对 dequeueBuffer() 拿到的 Buffer 去进行读写的(因为此时的 Buffer 所有者还处于 HWC),因此可以通过这一点来判断 Display 是否有问题。

 

参考:
https://juejin.cn/post/6879740386198323207

标签:Fence,fence,作用域,ATRACE,Systrace,FenceMonitor,GPU,message
From: https://www.cnblogs.com/hellokitty2/p/17641761.html

相关文章

  • 区分GPU和CPU
    做项目,一直不清楚GPU和CPU的概念。 超算:一群计算机连接一起,获得更强大的计算能力,使用GPU技术。以前是串行计算,现在是并行提交任务计算。CPU由于物理限制,工艺壁垒,主频无法突破,GPU在高速增长。GPU是专门为处理图形任务而产生的芯片对于GPU来说,它的任务是在屏幕上合成显示数......
  • LAXCUS和GPU软硬件结合,构建强大算力生态​
    随着科技的不断进步,计算机技术已经进入到我们生活的方方面面。其中,GPU(图形处理器)作为一种强大的计算设备,已经成为了人工智能、大数据、云计算等领域的核心硬件之一。然而,传统操作系统都是单机系统,只能在一台计算机上运行,对GPU的利用能力有限,无法充分发挥GPU强大计算性能,限制了其......
  • Electric Fence
    描述Inthisproblem,"latticepoints"intheplanearepointswithintegercoordinates.Inordertocontainhiscows,FarmerJohnconstructsatriangularelectricfencebystringinga"hot"wirefromtheorigin(0,0)toalatticepoint......
  • 在消费级GPU调试LLM的三种方法:梯度检查点,LoRA和量化
    前言 LLM的问题就是权重参数太大,无法在我们本地消费级GPU上进行调试,所以我们将介绍3种在训练过程中减少内存消耗,节省大量时间的方法:梯度检查点,LoRA和量化。本文转载自DeepHubIMBA仅用于学术分享,若侵权请联系删除欢迎关注公众号CV技术指南,专注于计算机视觉的技术总结、最新技......
  • pacemaker使用fence_sbd
    AdministrativeProceduresforRHELHighAvailabilityclusters-EnablingsbdfencinginRHEL7and8-RedHatCustomerPortal启动watchdog每个server执行modprobesoftdogmodprobesoftdogcat/etc/sysconfig/modules/softdog.modulesmodprobesoftdogchmod7......
  • llama2模型部署方案的简单调研-GPU显存占用(2023年7月25日版)
    https://blog.csdn.net/Fatfish7/article/details/131925595先说结论全精度llama27B最低显存要求:28GB全精度llama213B最低显存要求:52GB全精度llama270B最低显存要求:280GB16精度llama27B预测最低显存要求:14GB16精度llama213B预测最低显存要求:26GB16精度llama270B预测最低显......
  • WebGPU All In One
    WebGPUAllInOnechrome://flags/#enable-webgpu-developer-featuresWebGPUistheworkingnameforapotentialwebstandardandJavaScriptAPIforacceleratedgraphicsandcompute,aimingtoprovide"modern3Dgraphicsandcomputationcapabilities&......
  • 山东布谷科技直播系统源码热点分析:不同芯片实现高质量编码与渲染视频的GPU加速功能
    在现代科技的迅猛发展下,直播系统源码平台被开发搭建出来,为人们的生活方式带来了很大的改变,直播系统源码平台的好友、短视频、直播、社区等功能让很多人越来越热衷于去在平台上刷视频、看直播、分享生活。用户的喜爱也督促了直播系统源码平台要往更高质量上发展,图像质量与系统性能......
  • ubuntu系统升级软件sudo apt upgrade后GPU崩溃报错,显示驱动版本不匹配——ubuntu系统
     ubuntu系统升级软件(sudoaptupgrade)后,GPU崩溃报错,查看系统日志: Aug206:25:02lcwtrsyslogd:[originsoftware="rsyslogd"swVersion="8.32.0"x-pid="2059"x-info="http://www.rsyslog.com"]rsyslogdwasHUPedAug207:17:01lcwtCRON......
  • Painting the Fence 题解
    题目传送门一道枚举题。我们可以直接枚举那\(2\)个去掉的粉刷匠。先统计一下每个栅栏会被多少个粉刷匠刷到,然后枚举第一个被去掉的粉刷匠,然后计算剩下的粉刷匠会将每个栅栏刷到多少次,我们只需要看只能被刷\(1\)次的栅栏就行了。接着处理一个前缀和数组,记录前\(i\)个栅栏......