PyTorch与OpenCV的uint8转float32性能差异及相关技术疑问
测试场景与代码
在处理1200×1920×3的大张量时,对比PyTorch C++、OpenCV以及PyTorch Python的uint8转float32转换速度,测试代码与结果如下:
C++测试代码
#include <chrono> #include <opencv2/opencv.hpp> #include <torch/torch.h> #include <iostream> int main() { cv::Mat frame = cv::Mat::zeros(1200, 1920, CV_8UC3); //OpenCV conversion auto start = std::chrono::high_resolution_clock::now(); cv::Mat float_frame = cv::Mat::zeros(1200, 1920, CV_32FC3); for (int i = 0; i < 1000; i++) { frame.convertTo(float_frame, CV_32F); } auto end = std::chrono::high_resolution_clock::now(); std::cout << "Conversion to float 32 with opencv: " << std::chrono::duration_cast<std::chrono::milliseconds>(end - start).count()/1000.0 << "ms" << std::endl; //Pytorch conversion torch::Tensor tensor = torch::from_blob(frame.data, {frame.rows, frame.cols, 3}, torch::kUInt8); start = std::chrono::high_resolution_clock::now(); torch::Tensor float_tensor = torch::empty({frame.rows, frame.cols, 3}, torch::kFloat32); for (int i = 0; i < 1000; i++) { float_tensor = tensor.to(torch::kFloat32); } end = std::chrono::high_resolution_clock::now(); std::cout << "Conversion to float 32 with pytorch: " << std::chrono::duration_cast<std::chrono::milliseconds>(end - start).count()/1000.0 << "ms" << std::endl; //Pythorch conversion with CUDA torch::Tensor tensor_cuda = tensor.to(torch::TensorOptions().device(torch::kCUDA).dtype(torch::kUInt8)); start = std::chrono::high_resolution_clock::now(); torch::Tensor float_tensor_cuda = torch::empty({frame.rows, frame.cols, 3}, torch::TensorOptions().device(torch::kCUDA).dtype(torch::kFloat32)); for (int i = 0; i < 1000; i++) { float_tensor_cuda = tensor_cuda.to(torch::kFloat32); } end = std::chrono::high_resolution_clock::now(); std::cout << "Conversion to float 32 with pytorch and CUDA: " << std::chrono::duration_cast<std::chrono::milliseconds>(end - start).count()/1000.0 << "ms" << std::endl; }
C++测试结果
Conversion to float 32 with opencv: 2.402 ms Conversion to float 32 with pytorch: 3.034 ms Conversion to float 32 with pytorch and CUDA: 0.01 ms
Python测试代码
import torch import time tensor = torch.randint(0, 256, (1200, 1920), dtype=torch.uint8) start = time.time() for _ in range(1000): img = tensor.to(torch.float) end = time.time() print("Time to convert to float: ", (end - start), "ms")
Python测试结果
Time to convert to float: 1.2969393730163574 ms
技术疑问解答
1. 为何C++ CPU环境下PyTorch与OpenCV转换性能有25%差异?
OpenCV是专为图像处理打造的库,convertTo对uint8转float32这类操作做了极致针对性优化——直接利用SIMD指令(如SSE/AVX)批量处理像素,且执行路径极简,没有额外抽象层开销。而PyTorch的to()是通用张量操作,需要兼容多种设备、数据类型、自动微分等深度学习场景,执行前会做大量兼容性检查、张量元数据处理,这些抽象层带来了额外开销。此外,PyTorch的CPU后端优化优先级更偏向深度学习算子,对这类纯图像处理操作的优化力度不如OpenCV。
2. PyTorch每次调用to()都会分配新张量吗?内存分配是否影响耗时?
是的,你当前的C++代码中,每次循环float_tensor = tensor.to(torch::kFloat32)都会创建新的float32张量——因为uint8和float32类型不同,无法原地转换,而to()默认返回新张量。反观OpenCV的convertTo是写入预先分配好的float_frame,全程复用内存,没有重复分配的开销。
内存分配的累积开销确实不可忽视:PyTorch的张量分配不仅要申请内存,还要维护内部内存池、梯度信息、设备标识等元数据,比OpenCV的Mat分配更重。如果修改C++代码,提前分配好目标张量,并用tensor.to(torch::kFloat32, /* out= */ float_tensor)的方式复用内存,能大幅缩小和OpenCV的性能差距。
3. 为何Python中的转换速度比C++快一倍?
核心原因是数据量差异:你的Python测试用的是单通道张量(1200×1920),而C测试用的是三通道张量(1200×1920×3),Python处理的数据量只有C的1/3,自然总耗时更短。此外,Python的PyTorch在CPU后端的优化逻辑和C++一致,但小数据量下,抽象层的开销占比更低,也会让速度表现更优。
4. uint8转float32这类简单操作,3-4毫秒的耗时是否合理?
合理。计算数据量:1200×1920×3 = 6912000个元素,从uint8转float32需要读取6.9MB数据、写入27.6MB数据,总内存访问量超过34MB。受限于CPU内存带宽(DDR4单通道带宽约20GB/s),理论上的内存操作时间就接近1.7微秒,再加上类型转换的指令执行、缓存命中开销,单次转换3毫秒左右完全在合理范围内。
内容的提问来源于stack exchange,提问作者leevii

