【Rockchip系列】RGA imcopy 性能分析:不同缓冲区拷贝的对比(含实验代码)

RGA imcopy 性能分析:不同缓冲区拷贝的对比

引言

在嵌入式系统和移动设备中,图形处理的性能至关重要。Rockchip 图形加速器(RGA)提供了高效的 2D 图形操作,其中 imcopy 函数是一个常用的图像复制工具。本文将深入分析 imcopy 函数在不同场景下的性能表现。

实验设置

我们使用以下配置进行测试:

  • 图像分辨率:1280x720
  • 图像格式:RK_FORMAT_RGBA_8888
  • 测试平台:Firefly 开发板(具体型号RK3588S)
  • RGA API 版本:1.10.0_[8]
  • 测试次数:每种情况 100 次

实验代码

以下是我们用于进行性能测试的 C++ 代码。这段代码使用 RGA 库来执行图像复制操作,并测量不同场景下的执行时间。

cpp 复制代码
#include <iostream>
#include <chrono>
#include <vector>
#include <numeric>
#include "im2d.h"
#include "RgaUtils.h"
#include "drm_alloc.h"

int main() {
    int width = 1280;
    int height = 720;
    int format = RK_FORMAT_RGBA_8888;
    int fd1, fd2, handle1, handle2;
    size_t size1, size2;
    int flags = 0;

    // 分配第一个缓冲区
    void* buf1 = drm_buf_alloc(width, height, get_bpp_from_format(format) * 8, &fd1, &handle1, &size1, flags);
    if (buf1 == nullptr) {
        std::cerr << "Failed to allocate first DRM buffer!" << std::endl;
        return -1;
    }

    // 分配第二个缓冲区
    void* buf2 = drm_buf_alloc(width, height, get_bpp_from_format(format) * 8, &fd2, &handle2, &size2, flags);
    if (buf2 == nullptr) {
        std::cerr << "Failed to allocate second DRM buffer!" << std::endl;
        drm_buf_destroy(fd1, handle1, buf1, size1);
        return -1;
    }

    // 导入第一个缓冲区
    rga_buffer_handle_t rga_handle1 = importbuffer_fd(fd1, width, height, format);
    if (rga_handle1 == 0) {
        std::cerr << "Failed to import first buffer" << std::endl;
        drm_buf_destroy(fd1, handle1, buf1, size1);
        drm_buf_destroy(fd2, handle2, buf2, size2);
        return -1;
    }

    // 导入第二个缓冲区
    rga_buffer_handle_t rga_handle2 = importbuffer_fd(fd2, width, height, format);
    if (rga_handle2 == 0) {
        std::cerr << "Failed to import second buffer" << std::endl;
        releasebuffer_handle(rga_handle1);
        drm_buf_destroy(fd1, handle1, buf1, size1);
        drm_buf_destroy(fd2, handle2, buf2, size2);
        return -1;
    }

    // 创建第一个 rga_buffer_t 结构
    rga_buffer_t rga_buffer1 = wrapbuffer_handle(rga_handle1, width, height, format);
    if (rga_buffer1.handle == 0) {
        std::cerr << "Failed to wrap first buffer" << std::endl;
        releasebuffer_handle(rga_handle1);
        releasebuffer_handle(rga_handle2);
        drm_buf_destroy(fd1, handle1, buf1, size1);
        drm_buf_destroy(fd2, handle2, buf2, size2);
        return -1;
    }

    // 创建第二个 rga_buffer_t 结构
    rga_buffer_t rga_buffer2 = wrapbuffer_handle(rga_handle2, width, height, format);
    if (rga_buffer2.handle == 0) {
        std::cerr << "Failed to wrap second buffer" << std::endl;
        releasebuffer_handle(rga_handle1);
        releasebuffer_handle(rga_handle2);
        drm_buf_destroy(fd1, handle1, buf1, size1);
        drm_buf_destroy(fd2, handle2, buf2, size2);
        return -1;
    }

    std::cout << "Buffers prepared successfully" << std::endl;

    // Vector to store durations for different buffers
    std::vector<long long> durations_diff_buffers;
    durations_diff_buffers.reserve(100);

    // Vector to store durations for same buffer
    std::vector<long long> durations_same_buffer;
    durations_same_buffer.reserve(100);

    // Loop 100 times for different buffers
    for (int i = 0; i < 100; ++i) {
        auto start = std::chrono::high_resolution_clock::now();

        IM_STATUS status = imcopy(rga_buffer1, rga_buffer2);

        auto end = std::chrono::high_resolution_clock::now();
        auto duration = std::chrono::duration_cast<std::chrono::microseconds>(end - start);
        durations_diff_buffers.push_back(duration.count());

        if (status != IM_STATUS_SUCCESS) {
            std::cerr << "Buffer copy (different buffers) failed on iteration " << i + 1 << std::endl;
            break;
        }
    }

    // Loop 100 times for same buffer
    for (int i = 0; i < 100; ++i) {
        auto start = std::chrono::high_resolution_clock::now();

        IM_STATUS status = imcopy(rga_buffer1, rga_buffer1);

        auto end = std::chrono::high_resolution_clock::now();
        auto duration = std::chrono::duration_cast<std::chrono::microseconds>(end - start);
        durations_same_buffer.push_back(duration.count());

        if (status != IM_STATUS_SUCCESS) {
            std::cerr << "Buffer copy (same buffer) failed on iteration " << i + 1 << std::endl;
            break;
        }
    }

    // Calculate and print average duration for different buffers
    if (!durations_diff_buffers.empty()) {
        long long total_duration = std::accumulate(durations_diff_buffers.begin(), durations_diff_buffers.end(), 0LL);
        double average_duration = static_cast<double>(total_duration) / durations_diff_buffers.size();
        std::cout << "Average time for imcopy(rga_buffer1, rga_buffer2): " << average_duration << " microseconds" << std::endl;
    }

    // Calculate and print average duration for same buffer
    if (!durations_same_buffer.empty()) {
        long long total_duration = std::accumulate(durations_same_buffer.begin(), durations_same_buffer.end(), 0LL);
        double average_duration = static_cast<double>(total_duration) / durations_same_buffer.size();
        std::cout << "Average time for imcopy(rga_buffer1, rga_buffer1): " << average_duration << " microseconds" << std::endl;
    }

    // 清理资源
    releasebuffer_handle(rga_handle1);
    releasebuffer_handle(rga_handle2);
    drm_buf_destroy(fd1, handle1, buf1, size1);
    drm_buf_destroy(fd2, handle2, buf2, size2);

    return 0;
}

代码说明

  1. 缓冲区准备

    • 使用 drm_buf_alloc 分配两个 DRM 缓冲区。
    • 使用 importbuffer_fd 将缓冲区导入到 RGA。
    • 使用 wrapbuffer_handle 创建 RGA 可用的缓冲区结构。
  2. 性能测试

    • 对不同缓冲区复制 (imcopy(rga_buffer1, rga_buffer2)) 和相同缓冲区复制 (imcopy(rga_buffer1, rga_buffer1)) 分别进行 100 次测试。
    • 使用 std::chrono::high_resolution_clock 测量每次操作的执行时间。
  3. 结果计算

    • 计算并输出两种复制操作的平均执行时间。
  4. 资源清理

    • 使用 releasebuffer_handledrm_buf_destroy 释放所有分配的资源。

## 测试场景

我们比较了两种不同的 imcopy 操作:

  1. 不同缓冲区之间的复制:imcopy(rga_buffer1, rga_buffer2)
  2. 相同缓冲区内的复制:imcopy(rga_buffer1, rga_buffer1)

实验结果

经过 100 次重复测试,我们得到了以下平均结果:

  1. 不同缓冲区之间的复制:平均耗时 769.55 微秒
  2. 相同缓冲区内的复制:平均耗时 1309 微秒

结果分析

  1. 性能差异

    相同缓冲区内的复制操作比不同缓冲区之间的复制慢了约 70%。这个结果初看可能令人惊讶,但有一个潜在的合理解释。

  2. 关键洞察

    基于观察到的性能差异(大约两倍的时间差),我们提出了一个新的假说:RGA 在处理同一缓冲区的复制时可能采用了不同的策略。

  3. 可能的实现机制

    当源和目标是同一缓冲区时(例如 imcopy(rga_buffer1, rga_buffer1)),RGA 可能执行了以下步骤:

    a. 将数据从 buffer1 复制到一个临时缓冲区(例如 temp

    b. 然后将数据从 temp 复制回 buffer1

    这个两步过程(buffer1 => temp => buffer1)解释了为什么同缓冲区复制大约需要两倍的时间。

  4. 原因分析

    • 数据完整性:这种方法可以确保在复制过程中不会出现数据覆盖或丢失。
    • 硬件限制:RGA 硬件可能不支持直接的原地复制操作。
    • 通用性:这种实现方式可以处理源和目标区域重叠的情况。
  5. 其他可能因素

    虽然上述假说很可能是主要原因,但我们之前提到的其他因素仍可能在一定程度上影响性能:

    • 缓存影响
    • 内存对齐
    • 硬件特性
  6. 实际应用影响

    • 在需要频繁进行图像复制的应用中,使用不同的源和目标缓冲区可以带来显著的性能提升。
    • 对于需要原地操作的场景,开发者应意识到存在的性能损失,并考虑使用显式的临时缓冲区来优化性能。

优化建议

  1. 避免同缓冲区复制:尽可能使用不同的缓冲区作为复制的源和目标。
  2. 显式使用临时缓冲区:如果必须在同一缓冲区内操作,考虑手动实现两步复制过程,这可能在某些情况下提供更多的控制和潜在的优化空间。
  3. 缓冲区管理:在内存受限的系统中,权衡性能和内存使用。考虑复用临时缓冲区以减少内存占用。
  4. 批处理:将多个小的复制操作合并为较大的操作,减少潜在的额外开销。
  5. 异步操作:利用 RGA 的异步接口,在复制过程中执行其他任务。

结论

这个实验不仅揭示了 RGA imcopy 函数在不同场景下的性能特征,还让我们深入了解了可能的底层实现细节。观察到的性能差异很可能是由于同缓冲区复制时采用了两步复制策略导致的。这一发现强调了理解底层实现对于优化性能的重要性,同时也说明了在嵌入式系统优化中进行实际测量和深入分析的必要性。

未来工作

  1. 优化实验代码,增加更多的测试场景,如不同的图像大小、格式,以及在不同的 Rockchip 平台上进行测试。
  2. 实现一个自定义的两步复制过程,与 RGA 的 imcopy 进行性能对比。

编译和运行说明

要编译和运行此代码,您需要以下条件:

  1. 安装了 RGA 和 DRM 库的 Rockchip 开发环境。
  2. 确保 im2d.hRgaUtils.hdrm_alloc.h 头文件在您的包含路径中。

编译命令示例:

bash 复制代码
g++ -o rga_imcopy_test rga_imcopy_test.cpp -lrga -ldrm -I/path/to/rga/include -I/path/to/drm/include

运行命令:

bash 复制代码
./rga_imcopy_test

请注意,您可能需要根据您的具体开发环境调整编译命令。

相关推荐
转世成为计算机大神8 分钟前
易考八股文之Java中的设计模式?
java·开发语言·设计模式
机器视觉知识推荐、就业指导17 分钟前
C++设计模式:建造者模式(Builder) 房屋建造案例
c++
宅小海27 分钟前
scala String
大数据·开发语言·scala
qq_3273427330 分钟前
Java实现离线身份证号码OCR识别
java·开发语言
锅包肉的九珍31 分钟前
Scala的Array数组
开发语言·后端·scala
心仪悦悦34 分钟前
Scala的Array(2)
开发语言·后端·scala
yqcoder1 小时前
reactflow 中 useNodesState 模块作用
开发语言·前端·javascript
baivfhpwxf20231 小时前
C# 5000 转16进制 字节(激光器串口通讯生成指定格式命令)
开发语言·c#
许嵩661 小时前
IC脚本之perl
开发语言·perl
长亭外的少年1 小时前
Kotlin 编译失败问题及解决方案:从守护进程到 Gradle 配置
android·开发语言·kotlin