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;
}
代码说明
-
缓冲区准备:
- 使用
drm_buf_alloc
分配两个 DRM 缓冲区。 - 使用
importbuffer_fd
将缓冲区导入到 RGA。 - 使用
wrapbuffer_handle
创建 RGA 可用的缓冲区结构。
- 使用
-
性能测试:
- 对不同缓冲区复制 (
imcopy(rga_buffer1, rga_buffer2)
) 和相同缓冲区复制 (imcopy(rga_buffer1, rga_buffer1)
) 分别进行 100 次测试。 - 使用
std::chrono::high_resolution_clock
测量每次操作的执行时间。
- 对不同缓冲区复制 (
-
结果计算:
- 计算并输出两种复制操作的平均执行时间。
-
资源清理:
- 使用
releasebuffer_handle
和drm_buf_destroy
释放所有分配的资源。
- 使用
## 测试场景
我们比较了两种不同的 imcopy
操作:
- 不同缓冲区之间的复制:
imcopy(rga_buffer1, rga_buffer2)
- 相同缓冲区内的复制:
imcopy(rga_buffer1, rga_buffer1)
实验结果
经过 100 次重复测试,我们得到了以下平均结果:
- 不同缓冲区之间的复制:平均耗时 769.55 微秒
- 相同缓冲区内的复制:平均耗时 1309 微秒
结果分析
-
性能差异 :
相同缓冲区内的复制操作比不同缓冲区之间的复制慢了约 70%。这个结果初看可能令人惊讶,但有一个潜在的合理解释。
-
关键洞察 :
基于观察到的性能差异(大约两倍的时间差),我们提出了一个新的假说:RGA 在处理同一缓冲区的复制时可能采用了不同的策略。
-
可能的实现机制 :
当源和目标是同一缓冲区时(例如
imcopy(rga_buffer1, rga_buffer1)
),RGA 可能执行了以下步骤:a. 将数据从
buffer1
复制到一个临时缓冲区(例如temp
)b. 然后将数据从
temp
复制回buffer1
这个两步过程(
buffer1
=>temp
=>buffer1
)解释了为什么同缓冲区复制大约需要两倍的时间。 -
原因分析:
- 数据完整性:这种方法可以确保在复制过程中不会出现数据覆盖或丢失。
- 硬件限制:RGA 硬件可能不支持直接的原地复制操作。
- 通用性:这种实现方式可以处理源和目标区域重叠的情况。
-
其他可能因素 :
虽然上述假说很可能是主要原因,但我们之前提到的其他因素仍可能在一定程度上影响性能:
- 缓存影响
- 内存对齐
- 硬件特性
-
实际应用影响:
- 在需要频繁进行图像复制的应用中,使用不同的源和目标缓冲区可以带来显著的性能提升。
- 对于需要原地操作的场景,开发者应意识到存在的性能损失,并考虑使用显式的临时缓冲区来优化性能。
优化建议
- 避免同缓冲区复制:尽可能使用不同的缓冲区作为复制的源和目标。
- 显式使用临时缓冲区:如果必须在同一缓冲区内操作,考虑手动实现两步复制过程,这可能在某些情况下提供更多的控制和潜在的优化空间。
- 缓冲区管理:在内存受限的系统中,权衡性能和内存使用。考虑复用临时缓冲区以减少内存占用。
- 批处理:将多个小的复制操作合并为较大的操作,减少潜在的额外开销。
- 异步操作:利用 RGA 的异步接口,在复制过程中执行其他任务。
结论
这个实验不仅揭示了 RGA imcopy
函数在不同场景下的性能特征,还让我们深入了解了可能的底层实现细节。观察到的性能差异很可能是由于同缓冲区复制时采用了两步复制策略导致的。这一发现强调了理解底层实现对于优化性能的重要性,同时也说明了在嵌入式系统优化中进行实际测量和深入分析的必要性。
未来工作
- 优化实验代码,增加更多的测试场景,如不同的图像大小、格式,以及在不同的 Rockchip 平台上进行测试。
- 实现一个自定义的两步复制过程,与 RGA 的
imcopy
进行性能对比。
编译和运行说明
要编译和运行此代码,您需要以下条件:
- 安装了 RGA 和 DRM 库的 Rockchip 开发环境。
- 确保
im2d.h
、RgaUtils.h
和drm_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
请注意,您可能需要根据您的具体开发环境调整编译命令。