OpenCV 4.13.0 HAL 与性能优化

OpenCV 4.13.0 HAL 与性能优化

本文面向需要解释、选择或扩展 OpenCV 加速路径的开发者。内容依据 OpenCV 4.13.0 源码树中的 HAL、CPU 分发、并行、OpenCL 和 DNN 实现核验。文中的路径均相对于 OpenCV 源码根目录。

1. 先建立正确的性能模型

一次公开 API 调用可能依次经过:

1
2
3
4
5
6
7
公开 API
-> 参数检查与输出分配
-> OpenCL / 专用后端尝试
-> HAL 替换原语
-> CPU dispatch 或通用实现
-> parallel_for_ 分块
-> SIMD、标量与尾部处理

这不是所有函数都严格遵循的固定层级。不同模块会按算法、数据类型、尺寸和构建能力选择其中一部分。因此,“启用了某个库”不等于“每次调用都进入该库”。性能判断必须同时核对构建结果、运行时选择和实际输入。
HAL、SIMD、线程和异构设备解决的问题不同:

  • HAL 替换一组约定好的底层原语;
  • CPU dispatch 在同一二进制中选择不同指令集版本;
  • 通用 intrinsics 帮助源码以统一方式表达 SIMD;
  • parallel_for_ 把独立区间分给多个 CPU 线程;
  • T-API 通过 UMat 尝试 OpenCL;
  • DNN 后端把网络或网络片段交给专用执行引擎。

2. HAL 的两层含义

OpenCV 源码中的 HAL 至少有两层含义,阅读时不能混为一谈。

2.1 模块内的硬件抽象接口

主要入口包括:

1
2
3
4
5
6
modules/core/include/opencv2/core/hal/interface.h
modules/core/include/opencv2/core/hal/hal.hpp
modules/core/include/opencv2/core/hal/intrin.hpp
modules/core/include/opencv2/core/hal/intrin_*.hpp
modules/imgproc/include/opencv2/imgproc/hal/hal.hpp
modules/features2d/include/opencv2/features2d/hal/

interface.h 定义低层 C 风格接口、类型和返回码。接口返回值的三个基本约定是:

  • CV_HAL_ERROR_OK:实现已完成操作;
  • CV_HAL_ERROR_NOT_IMPLEMENTED:该输入或操作不支持,应由上层回退;
  • CV_HAL_ERROR_UNKNOWN:实现发生无法恢复的错误。
    各模块的 src/hal_replacement.hpp 提供默认替换入口。自定义头通过宏重新绑定 cv_hal_* 名称。例如实现头可先 #undef cv_hal_xxx,再将它定义为供应商函数。这是一种编译期替换机制,不是运行时插件虚函数表。
    intrin.hpp 及架构专用头属于另一类抽象。它们把向量寄存器、加载、存储、算术和归约包装成统一 API。算法可由同一份模板生成 baseline 或多个 dispatch 版本。intrinsics 不是外置 HAL,但两者可以在同一调用链中同时存在。

2.2 源码根目录中的 HAL 子工程

源码根目录 hal/ 保存树内可选实现:

1
2
3
4
5
6
7
hal/carotene/
hal/fastcv/
hal/kleidicv/
hal/ndsrvp/
hal/riscv-rvv/
hal/ipp/
hal/openvx/

这些目录各自构建静态辅助库或接入外部项目。根 CMakeLists.txt 决定其顺序、头文件注入和链接库收集。目录存在并不表示当前平台一定构建或使用它。

3. HAL 注册链与优先级

3.1 CMake 阶段的完整链路

根构建脚本中的关键流程是:

  1. 平台与依赖探测生成 HAVE_*
  2. 用户选项和探测结果修改 OpenCV_HAL 列表;
  3. foreach(hal ${OpenCV_HAL}) 按列表顺序处理实现;
  4. 树内实现通过 add_subdirectory(hal/...) 构建;
  5. 未识别名称通过 find_package(<name> NO_MODULE QUIET) 查找;
  6. ocv_hal_register() 收集库、头和 include 目录;
  7. custom_hal.hpp.in 被配置成构建目录中的 custom_hal.hpp
  8. 收集到的库进入 OpenCV 模块链接关系。
    ocv_hal_register() 实际做三件事:
  • 将实现库追加到 OPENCV_HAL_LINKER_LIBS
  • 将实现头变成生成头中的 #include
  • 将实现 include 目录加入构建。
    根脚本默认把 OpenCV_HAL 设为字符串 OpenCV_HAL。这个名称会走 find_package(OpenCV_HAL NO_MODULE QUIET)。因此 OpenCV_HAL_DIR 可指向一个含 OpenCV_HALConfig.cmake 的外置实现构建目录。

3.2 顺序为什么重要

IPP、OpenVX、FastCV、KleidiCV、Carotene、NDSRVP 和 RVV 会按条件前插到列表。每个注册头都可能重定义同一个 cv_hal_* 宏。生成的 custom_hal.hpp 按注册顺序包含这些头。后包含且确实重定义某接口的头,可能覆盖先前绑定。没有覆盖的接口继续由前一实现或默认实现提供。
不要仅根据“某库排在列表中”推断最终处理者。应检查生成的 custom_hal.hpp、具体实现头和目标链接命令。

3.3 从公开 API 到回退

典型路径可以概括为:

1
2
3
4
5
cv::算法
-> cv_hal_xxx(...)
-> 返回 OK:结束
-> 返回 NOT_IMPLEMENTED:执行 OpenCV 内部实现
-> 返回 UNKNOWN:按调用点的错误策略处理

是否允许回退由具体调用点决定。实现必须遵守接口的步长、尺寸、原地操作、边界和数值约定。“不支持此组合”应返回 NOT_IMPLEMENTED,不能悄悄产生近似错误结果。

4. 编写和接入自定义 HAL

源码自带两个教学实现:

1
2
samples/hal/c_hal/
samples/hal/slow_hal/

c_hal 的函数返回错误,用于验证错误处理和回退。slow_hal 替换按位运算,用可观察的慢实现证明绑定已生效。两者都会生成 OpenCV_HALConfig.cmake

4.1 最小接入流程

先单独构建自定义 HAL:

1
2
3
4
cmake -S /path/to/opencv/samples/hal/slow_hal \
-B /path/to/build-hal \
-D CMAKE_BUILD_TYPE=Release
cmake --build /path/to/build-hal -j

再配置 OpenCV:

1
2
3
4
5
cmake -S /path/to/opencv \
-B /path/to/build-opencv \
-D CMAKE_BUILD_TYPE=Release \
-D OpenCV_HAL_DIR=/path/to/build-hal
cmake --build /path/to/build-opencv -j

外置包至少需要导出:

1
2
3
4
5
set(OpenCV_HAL_FOUND TRUE)
set(OpenCV_HAL_VERSION 0.1.0)
set(OpenCV_HAL_LIBRARIES /path/to/libcustom_hal.a)
set(OpenCV_HAL_HEADERS custom_hal_impl.hpp)
set(OpenCV_HAL_INCLUDE_DIRS /path/to/include)

生产实现应使用可重定位的导出目标或安装路径。上面的绝对占位仅说明变量语义。

4.2 实现检查清单

  • 函数签名必须与当前 4.13.0 接口完全一致;
  • 只重定义真正实现的接口;
  • 对不支持的深度、通道或边界模式返回 NOT_IMPLEMENTED
  • 正确处理非连续矩阵和字节步长;
  • 明确是否支持源、目的区域重叠;
  • 避免越过每行有效宽度;
  • 保证多线程并发调用安全;
  • 不把进程级可变状态放进无保护全局变量;
  • 静态库用于共享 OpenCV 时通常需要位置无关代码;
  • 用正确性测试覆盖空尺寸、奇数尺寸和尾部元素;
  • 用性能测试证明收益大于分派与转换成本;
  • 将支持矩阵、版本和供应商运行库要求写入发布说明。

4.3 如何确认自定义 HAL 生效

检查以下证据链:

  1. CMake 配置日志找到了自定义包;
  2. 生成的 custom_hal.hpp 包含自定义头;
  3. 目标链接命令含自定义 HAL 库;
  4. 针对被替换接口的测试通过;
  5. 通过日志、计数器或调试器确认函数被调用;
  6. 关闭自定义 HAL 后基准结果按预期变化。
    不要只靠符号出现在静态库中判断生效。链接器可能丢弃未引用对象,宏绑定也可能已被后续头覆盖。

5. 树内 HAL 实现

5.1 Carotene

选项是 WITH_CAROTENE。根 CMake 仅在 ARM/AArch64 的适用平台显示该选项。真正注册前还要求 CPU_BASELINE_FINAL 包含 NEON。接线头是 hal/carotene/hal/tegra_hal.hpp
Carotene 覆盖多种 core 与 imgproc 原语。其对象库会按 WITH_NEON 添加定义,并包含针对内联增长的编译参数。适合已有 NEON baseline 的 ARM 构建。它不是所有 ARM 算法的统一开关,也不等价于 CPU dispatch 中的 NEON。

5.2 Qualcomm FastCV

选项是 WITH_FASTCV,默认关闭。可见平台为 ARM/AArch64 上的 Android 或适用 Unix。依赖探测成功后形成 HAVE_FASTCV。实现位于 hal/fastcv/src/,注册头覆盖 core 和 imgproc 的部分接口。
hal/fastcv/CMakeLists.txt 将外部 FASTCV_LIBRARY 链接到 fastcv_hal。选项打开但库或头未找到时,HAL 不可用。发布时必须同时考虑 FastCV 二进制、许可、目标 ABI 和运行时装载路径。

5.3 Arm KleidiCV

选项是 WITH_KLEIDICV。源码将其限制到 AArch64 的 Android 或 Unix 环境。依赖可用时,OpenCV包含 KleidiCV 自带的 adapters/opencv/CMakeLists.txt。适配器负责生成 kleidicv_hal 和对应注册信息。
构建中还提供 KLEIDICV_ENABLE_SME2,默认关闭。源码注释指出它与部分 Android NDK Clang 版本不兼容。启用 SME2 前要同时验证编译器、运行设备和部署基线。

5.4 Andes NDSRVP

选项是 WITH_NDSRVP,仅在 RISC-V 平台可见。注册还要求 C 与 C++ flags 都包含 -mext-dsp。同时要求 baseline 不含 RVV。这体现了 NDSRVP 与 RVV HAL 的选择关系。
实现覆盖部分 core、imgproc 和 features2d 接口。构建生成 ndsrvp_hal 静态库。应使用 Andes 对应工具链验证编译选项,不能在普通 RISC-V 工具链上只强开选项。

5.5 RISC-V RVV HAL

选项是 WITH_HAL_RVV,仅在 RISC-V 平台可见。注册要求 CPU_BASELINE_FINALRVV。构建目标为 rvv_hal。入口头 rvv_hal.hpp 汇集 core、imgproc 和 features2d 替换。
源码覆盖矩阵分解、数学、颜色转换、滤波、几何变换、直方图和 FAST 等多类原语。具体接口仍可能因数据类型或参数而回退。RVV HAL 与通用 intrinsics 中的 RVV dispatch 是不同机制。

5.6 Intel IPP

选项是 WITH_IPP。根 CMake 在 x86/x86_64 且适用平台提供该选项,并以 HAVE_IPP 验证探测结果。注册目标 ipphal 覆盖 mean、min/max、norm、极坐标转换、变换、warp 和 sum 等接口。
OpenCV 其他位置仍保留 IPP 集成。hal/ipp/CMakeLists.txt 的注释明确指出 HAL 尚未成为唯一 IPP 来源。因此分析 IPP 命中时要同时看 HAL 和模块内部 IPP 路径。WITH_IPP_CALLS_ENFORCED 更适合验证和开发,不应在不了解回退影响时用于通用发布。

5.7 OpenVX

选项是 WITH_OPENVX,默认关闭。cmake/FindOpenVX.cmake 探测实现并形成 HAVE_OPENVX。只有探测成功才进入 hal/openvx/hal/。源码还包含 ivx.hpp 等 C++ 包装。
OpenVX HAL 只覆盖它实现的原语。性能受图构建、数据导入导出、供应商实现和目标设备影响。不能把 WITH_OPENVX=ON 理解为整个 OpenCV 流水线自动转换为 OpenVX 图。

5.8 选择摘要

平台或依赖 首先评估 关键验证
x86/x86_64 CPU dispatch、IPP HAVE_IPP、实际命中、线程数
通用 ARM/AArch64 NEON、Carotene、KleidiCV baseline、编译器、覆盖接口
Qualcomm ARM FastCV 外部库、许可、ABI、回退
Andes RISC-V DSP NDSRVP -mext-dsp 且 baseline 非 RVV
RISC-V Vector RVV HAL CPU_BASELINE_FINAL 含 RVV
OpenVX 设备 OpenVX HAL 实现版本、传输和图开销

6. CPU baseline 与 runtime dispatch

6.1 两类构建结果

CPU_BASELINE 指定库运行所需的最低优化能力。baseline 指令可以出现在通用代码路径中。把 AVX2 设为 baseline 意味着不支持 AVX2 的机器不应运行该二进制。
CPU_DISPATCH 指定额外编译的优化版本。运行时检测 CPU 后选择可用的最高合适版本。不满足这些额外能力的机器仍可走 baseline。
高级约束还有:

  • CPU_BASELINE_REQUIRE:必须成功启用的 baseline;
  • CPU_BASELINE_DISABLE:禁止的 baseline;
  • CPU_DISPATCH_REQUIRE:必须成功生成的 dispatch。
    最终结果记录在:
  • CPU_BASELINE_FINAL
  • CPU_DISPATCH_FINAL
  • CMake Summary 的 CPU/HW features 段。

6.2 源码组织

热点实现常使用:

1
2
3
name.dispatch.cpp
name.simd.hpp
name.avx2.cpp

CV_CPU_DISPATCH 宏按运行时能力调用命名空间中的版本。dispatch 链最终落到 BASELINE。不是每个算法、深度和尺寸都有每种指令集实现。

6.3 构建与运行时控制

构建时 CV_DISABLE_OPTIMIZATION=ON 会关闭多类优化,适合建立调试对照。运行时 cv::setUseOptimized(false) 关闭受该全局标志控制的优化分支。它必须在没有其他 OpenCV 调用并发执行的顶层安全位置调用。
环境变量 OPENCV_CPU_DISABLE 可禁用以逗号或分号分隔的 dispatch 特性。它适合定位某个指令集实现,不适合作为长期性能配置的替代品。

1
OPENCV_CPU_DISABLE=AVX2,AVX512-SKX /path/to/app

cv::getCPUFeaturesLine() 的标记含义为:

  • 无标记:baseline;
  • 前缀 *:dispatch 中编译的特性;
  • 后缀 ?:已编译但当前硬件不可用。

7. parallel_for_ 与线程后端

cv::parallel_for_ 接受一个 Range 和并行循环体。它把区间划分为多个 stripe,再交给并行后端。构建可能使用 TBB、OpenMP、平台后端、插件或内置实现。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
class Body final : public cv::ParallelLoopBody {
public:
Body(const cv::Mat& src, cv::Mat& dst) : src_(src), dst_(dst) {}

void operator()(const cv::Range& range) const override {
for (int y = range.start; y < range.end; ++y)
src_.row(y).copyTo(dst_.row(y));
}

private:
const cv::Mat& src_;
cv::Mat& dst_;
};

cv::parallel_for_(cv::Range(0, src.rows), Body(src, dst));

循环体应满足:

  • 每个 stripe 写入互不重叠的输出;
  • 只读共享输入;
  • 共享统计量使用归约或同步;
  • 不依赖 stripe 的执行顺序;
  • 粒度足够大,能覆盖调度开销;
  • 异常和对象生命周期符合调用线程语义。
    常用控制 API:
1
2
3
cv::setNumThreads(1);
int configured = cv::getNumThreads();
int cpus = cv::getNumberOfCPUs();

setNumThreads(1) 可用于串行对照。传负数恢复系统默认。该函数不是线程安全的,不能在并行区或并发调用中修改。getNumThreads() 的精确语义取决于 TBB、OpenMP 等后端。
上层线程池与 OpenCV 内部线程并行叠加会过度订阅。批量处理时常见策略是“外层并行、库内单线程”或反之。应分别测量,不能凭核心数直接决定。

8. OpenCL Transparent API

T-API 使用 cv::UMat 表达可由 OpenCL 管理的数据。支持 OpenCL 的函数会尝试设备实现,不支持时可走 CPU 路径。

1
2
3
4
5
cv::UMat src, gray, blurred;
cv::imread("input.jpg", cv::IMREAD_COLOR).copyTo(src);
cv::cvtColor(src, gray, cv::COLOR_BGR2GRAY);
cv::GaussianBlur(gray, blurred, cv::Size(5, 5), 1.2);
cv::ocl::finish();

诊断 API:

1
2
3
4
5
std::cout << cv::ocl::haveOpenCL() << '\n';
std::cout << cv::ocl::useOpenCL() << '\n';
cv::ocl::setUseOpenCL(true);
const cv::ocl::Device& dev = cv::ocl::Device::getDefault();
std::cout << dev.name() << '\n';

环境变量 OPENCV_OPENCL_DEVICE 可选择设备或设为 disabled。设备选择应在 OpenCL 上下文首次初始化前完成。
性能陷阱包括:

  • 小算子启动成本高于计算收益;
  • MatUMat 频繁互转造成上传和下载;
  • getMat() 可能触发同步;
  • 只计异步提交而没有 cv::ocl::finish() 会低估时间;
  • 某一步回退到 CPU 可能打断整条设备流水线;
  • 首次编译 kernel 与缓存建立不代表稳定态。
    最佳实践是让尽可能长的支持链保持 UMat。端到端基准必须包含必要的输入传输、最终同步和输出取回。

9. DNN 后端与目标

OpenCV 4.13.0 的公开枚举包含:

  • 后端:Default、Halide、OpenVINO、OpenCV、VkCom、CUDA、WebNN、TIM-VX、CANN;
  • 目标:CPU、OpenCL、OpenCL FP16、Myriad、Vulkan、FPGA、CUDA、CUDA FP16、HDDL、NPU、ARM CPU FP16。
    枚举存在不代表当前构建可用。应查询实际组合:
1
2
3
4
for (const auto& item : cv::dnn::getAvailableBackends())
std::cout << item.first << " -> " << item.second << '\n';

auto targets = cv::dnn::getAvailableTargets(cv::dnn::DNN_BACKEND_OPENCV);

显式选择示例:

1
2
3
cv::dnn::Net net = cv::dnn::readNet("/path/to/model.onnx");
net.setPreferableBackend(cv::dnn::DNN_BACKEND_OPENCV);
net.setPreferableTarget(cv::dnn::DNN_TARGET_CPU);

CUDA 后端通常要求构建探测到 CUDA,并按能力使用 cuDNN、cuBLAS 等。OpenVINO、TIM-VX、CANN 等也各有构建和运行时依赖。不支持的层可能回退、重新分区或直接报错,取决于后端。
DNN 性能测量应分离:

  • 模型读取与解析;
  • 首次网络初始化;
  • 首次推理和 kernel 编译;
  • 稳定态推理;
  • blob 预处理;
  • 输出复制与后处理。
    Net::getPerfProfile() 可返回层级时间,但支持范围与后端有关。最终仍要以应用端到端延迟和吞吐为准。

10. 内存、数据布局与缓存

许多“计算优化”最终受内存带宽限制。优先检查:

  • 是否在循环中反复创建同尺寸输出;
  • 是否存在无意的 clone()copyTo() 或类型转换;
  • ROI 是否导致非连续步长;
  • 通道转换能否前移、合并或取消;
  • 数据类型是否超出精度需求;
  • 大图处理是否有更好的 tile;
  • 多线程是否争用同一缓存行;
  • NUMA 机器上的分配与执行是否跨节点。
    cv::Mat 是引用计数的浅拷贝头。赋值通常不复制像素,clone() 才执行深拷贝。ROI 可能不连续,底层代码必须尊重 step。可用 isContinuous() 判断能否把多行合并为一个线性区间。
    OpenCV 提供 fastMalloc()fastFree()。需要特殊分配策略时可研究 MatAllocator,但这是高级扩展点。自定义 allocator 必须处理生命周期、对齐、map/unmap 和异常路径。一般应用先复用 Mat::create() 管理的缓冲区即可。
    减少峰值内存的常用方法:
  • 原地操作仅在 API 明确支持时使用;
  • 将中间缓冲区放到循环外复用;
  • 流式读取视频,不缓存全部帧;
  • 控制 DNN batch 与输入分辨率;
  • 对超大图采用分块并处理 halo;
  • 避免同时保留 MatUMat 和设备副本。

11. 可复现 benchmark 方法

11.1 基准骨架

1
2
3
4
5
6
7
8
9
10
11
12
13
14
cv::TickMeter tm;
cv::Mat dst;

for (int i = 0; i < 10; ++i)
cv::GaussianBlur(src, dst, cv::Size(5, 5), 1.2);

std::vector<double> samples;
for (int i = 0; i < 100; ++i) {
tm.reset();
tm.start();
cv::GaussianBlur(src, dst, cv::Size(5, 5), 1.2);
tm.stop();
samples.push_back(tm.getTimeMilli());
}

GPU 或 OpenCL 路径要在计时区间末尾同步。异步设备后端也要使用其对应同步机制。不要使用 getCPUTickCount() 直接换算耗时;源码文档建议一般计时使用 getTickCount()

11.2 必须固定和记录

  • OpenCV 完整版本与源码修订;
  • Release、Debug 或 RelWithDebInfo;
  • 编译器、标准库和关键编译参数;
  • baseline、dispatch 与 HAL 列表;
  • 第三方加速库和驱动版本;
  • CPU 型号、频率策略、NUMA 与亲和性;
  • GPU/NPU 型号、功耗模式与温度;
  • 输入尺寸、类型、通道、步长和内容分布;
  • 线程数、并行后端和上层并发;
  • 预热次数、样本数和同步位置;
  • 中位数、分位数与离群值规则;
  • 优化前后的输出误差阈值;
  • 是否包含 I/O、传输、预处理和后处理。

11.3 OpenCV 自带 perf

启用 BUILD_PERF_TESTS=ON 后可构建 opencv_perf_<module>。使用 GoogleTest 风格过滤器缩小测试范围:

1
2
/path/to/build/bin/opencv_perf_core \
--gtest_filter='*bitwise_and*'

先运行 --help 查看当前二进制支持的 perf 参数。不同构建和版本的参数集合可能变化。不要把单个微基准直接外推为完整业务收益。

12. 诊断 API 与环境变量

最小诊断程序:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
#include <opencv2/core.hpp>
#include <opencv2/core/ocl.hpp>
#include <opencv2/core/utils/logger.hpp>
#include <iostream>

int main() {
cv::utils::logging::setLogLevel(
cv::utils::logging::LOG_LEVEL_INFO);
std::cout << cv::getVersionString() << '\n';
std::cout << cv::getBuildInformation() << '\n';
std::cout << cv::getCPUFeaturesLine() << '\n';
std::cout << "optimized=" << cv::useOptimized() << '\n';
std::cout << "threads=" << cv::getNumThreads() << '\n';
std::cout << "cpus=" << cv::getNumberOfCPUs() << '\n';
std::cout << "opencl=" << cv::ocl::haveOpenCL() << '\n';
}

常用环境变量:

1
2
3
4
OPENCV_LOG_LEVEL=DEBUG /path/to/app
OPENCV_CPU_DISABLE=AVX2 /path/to/app
OPENCV_FOR_THREADS_NUM=1 /path/to/app
OPENCV_OPENCL_DEVICE=disabled /path/to/app

诊断顺序建议:

  1. 打印 getBuildInformation()
  2. 核对目标模块和第三方依赖确实为 YES;
  3. 打印 CPU features 和线程数;
  4. 开启日志观察后端选择;
  5. 逐个关闭线程、dispatch、OpenCL 或专用后端;
  6. 保持相同输入比较结果和时间;
  7. 用调试器或 profiler 获取最终证据。

13. 平台决策树

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
先确认热点是否在 OpenCV 内
|
+-- 否 -> 优化 I/O、业务逻辑或数据搬运
|
+-- 是
|
+-- x86/x86_64
| -> Release + 合理 baseline/dispatch
| -> 检查 IPP、线程和内存带宽
|
+-- ARM/AArch64
| -> 检查 NEON baseline/dispatch
| -> 评估 Carotene / KleidiCV
| -> Qualcomm 设备再评估 FastCV
|
+-- RISC-V
| -> RVV baseline 可用:评估 RVV HAL
| -> Andes DSP 工具链:评估 NDSRVP
|
+-- 有 OpenCL
| -> 流水线可长期保留 UMat:测 T-API
| -> 频繁传输或小算子:优先 CPU
|
+-- DNN
-> 查询可用 backend/target
-> 按模型覆盖率、精度、延迟选择

14. 优化实施顺序

  1. 建立正确性基线和误差标准;
  2. 用 profiler 找到真实热点;
  3. 先减少输入量、重复计算和数据搬运;
  4. 确保使用 Release 构建;
  5. 核验已有 SIMD、HAL、IPP 和线程路径;
  6. 调整线程层级和任务粒度;
  7. 评估保持设备驻留的 OpenCL 或 DNN 后端;
  8. 仅对稳定且高占比的底层原语开发自定义 HAL;
  9. 用同一套正确性与性能测试验收;
  10. 在目标设备和热稳态条件下重新测量。
    最终选择应以目标平台的可重复端到端数据为依据。任何加速路径都必须保留可诊断的回退方案,并验证回退结果正确。

文章互动

阅读 --

留言

0 条留言

正在加载留言…