最近看 OpenCV 源码时注意到一个有意思的地方:

template<typename T, size_t BinsOnStack = 0u>
static double getThreshVal_Otsu( const Mat& _src, const Size& size)
{const int N = std::numeric_limits<T>::max() + 1;int i, j;#if CV_ENABLE_UNROLLEDAutoBuffer<int, 4 * BinsOnStack> hBuf(4 * N); // 分配了4倍 buffer#elseAutoBuffer<int, BinsOnStack> hBuf(N);#endifmemset(hBuf.data(), 0, hBuf.size() * sizeof(int));int* h = hBuf.data();#if CV_ENABLE_UNROLLEDint* h_unrolled[3] = {h + N, h + 2 * N, h + 3 * N };#endiffor( i = 0; i < size.height; i++ ){ // 统计灰度直方图const T* src = _src.ptr<T>(i, 0);j = 0;#if CV_ENABLE_UNROLLEDfor( ; j <= size.width - 4; j += 4 ){ // 行方向上的迭代次数减少为 1/4int v0 = src[j], v1 = src[j+1];h[v0]++; h_unrolled[0][v1]++;v0 = src[j+2]; v1 = src[j+3];h_unrolled[1][v0]++; h_unrolled[2][v1]++;}#endiffor( ; j < size.width; j++ )h[src[j]]++;}double mu = 0, scale = 1./(size.width*size.height);for( i = 0; i < N; i++ ){#if CV_ENABLE_UNROLLEDh[i] += h_unrolled[0][i] + h_unrolled[1][i] + h_unrolled[2][i];#endifmu += i*(double)h[i];}mu *= scale;double mu1 = 0, q1 = 0;double max_sigma = 0, max_val = 0;for(i = 0; i < N; i++ ){double p_i, q2, mu2, sigma;p_i = h[i]*scale;mu1 *= q1;q1 += p_i;q2 = 1. - q1;if( std::min(q1,q2) < FLT_EPSILON || std::max(q1,q2) > 1. - FLT_EPSILON )continue;mu1 = (mu1 + i*p_i)/q1;mu2 = (mu - q1*mu1)/q2;sigma = q1*q2*(mu1 - mu2)*(mu1 - mu2);if( sigma > max_sigma ){max_sigma = sigma;max_val = i;}}return max_val;
}

这是大津法求二值化阈值的过程,里面有个CV_ENABLE_UNROLLED宏(默认为 1,即开启)。经过测试,我发现开启该宏有比较明显的加速效果。当这个宏开启时,行方向上的迭代次数减少为 1/4,加上宏名中带有“UNROLLED”,起初我以为这里应该是利用 loop unrolling(循环展开)或者 loop tiling(循环分块)达到加速效果的吧,但分配 4 倍的 buffer 有必要吗?

于是我去掉了 4 倍 buffer,将循环部分改成:

for (; j <= image.cols - 4; j += 4) {h[src[j]]++;h[src[j + 1]]++;h[src[j + 2]]++;h[src[j + 3]]++;
}

神奇的是,加速效果完全消失

百思不得其解,网上也很难找到有用的资料。后来跟同事探讨的过程中,经提醒还有个 CPU 流水线的概念 (图片来自 Eigen CGLibs 2013. Giugno Pisa. P37)

CPU 流水线(Pipelining)大概的意思就是:CPU 使用不同的处理单元进行不同的数据操作(比如从内存取数据、数据相加、存数据到内存),对同一内存单元的不同操作只能串行执行,但对不同内存单元的不同操作可以自动并行!

回过头看 otsu 算法,由于图像中相邻像素很可能有相同的像素值,即 h[src[j + ?]] 很可能指向同一个内存单元,这种情况下循环中前后两条指令就无法利用流水线,只能串行执行。但如果使用 4 个 buffer,则可以确保循环中 4 条语句使用不同的内存单元,前一条语句进行加法操作时,后一条语句可以同时进行数据读取操作。

速度测试程序如下:

#include <chrono>
#include <opencv2/opencv.hpp>using namespace cv;
using namespace std;
using namespace chrono;template <bool ENABLE_UNROLL>
static double pixCount(const Mat& image)
{constexpr int N = 256;constexpr int buff_size = ENABLE_UNROLL ? N * 4 : N;int i, j;std::array<int, buff_size> hBuf;memset(hBuf.data(), 0, hBuf.size() * sizeof(int));int* h = hBuf.data();int* h_unrolled[3] = { h + N, h + 2 * N, h + 3 * N };for (i = 0; i < image.rows; i++) {const uchar* src = image.ptr<uchar>(i, 0);j = 0;for (; j <= image.cols - 4; j += 4) {h[src[j]]++;if constexpr (ENABLE_UNROLL) {h_unrolled[0][src[j + 1]]++;h_unrolled[1][src[j + 2]]++;h_unrolled[2][src[j + 3]]++;} else {h[src[j + 1]]++;h[src[j + 2]]++;h[src[j + 3]]++;}}for (; j < image.cols; j++)h[src[j]]++;}double mu = 0;for (i = 0; i < N; i++) {if constexpr (ENABLE_UNROLL) {h[i] += h_unrolled[0][i] + h_unrolled[1][i] + h_unrolled[2][i];}mu += i * (double)h[i];}return mu;
}int main(int argc, char** argv)
{Mat image = imread(argv[1], IMREAD_GRAYSCALE);cout << "image size: " << image.cols << "x" << image.rows << endl;auto time_start = system_clock::now();auto res = pixCount<false>(image);auto time_cost = duration_cast<nanoseconds>(system_clock::now() - time_start).count();cout << "no unrolling: " << time_cost * 1e-6 << " ms \tresult: " << res << endl;time_start = system_clock::now();res = pixCount<true>(image);time_cost = duration_cast<nanoseconds>(system_clock::now() - time_start).count();cout << "enable unrolling: " << time_cost * 1e-6 << " ms \tresult: " << res << endl;
}

执行结果:

image size: 3200x2400
no unrolling: 9.12246 ms        result: 1.16491e+08
enable unrolling: 3.35144 ms    result: 1.16491e+08

当图像中相邻像素值相同的概率很大时(比如存在纯色背景或者图像内容简单),利用 CPU 流水线的处理方式加速效果非常显著。

利用 CPU 流水线加快数据处理相关推荐

  1. 为什么CPU流水线会提高代码执行效率?

    关注.星标公众号,不错过精彩内容 素材来源:网络 编辑整理:strongerHuang 为什么有些CPU的主频更低,但运算效率却更高呢? 比如:51单片机30M主频,STM32单片机20M主频,执行相 ...

  2. 计算机组成原理 — CPU — 流水线与执行周期

    目录 文章目录 目录 CPU 流水线 时钟周期.机器周期.指令周期和总线周期 CPU 流水线 不同的 CPU 指令集架构在执行指令的过程会有所差别,以经典的 RISC(精简指令集架构)为例,存在以下步 ...

  3. CPU流水线技术演进

    一. 线性流水线 流水线技术是一种将每条指令分解为多步,并让各步操作重叠,从而实现几条指令并行处理的技术.程序中的指令仍是一条条顺序执行,但可以预先取若干条指令,并在当前指令尚未执行完时,提前启动后续 ...

  4. 跟涛哥一起学嵌入式 31:深入浅出CPU流水线工作原理

    现在的CPU处理器一般都是超流水线工作,动不动就是10级以上流水线,超高主频,这两者之间有什么关系呢?今天就跟大家科普下CPU流水线的工作原理,以及他们之间的关系. 说到流水线,很多人会想到富士康:说 ...

  5. #C++# #likely# #unlikely#减少CPU流水线分支预测错误带来的性能损失

    目录 流水线技术 分支预测 什么是likely和unlikely likely/unlikely的原理 likely/unlikely的适用条件 C++20中的likely/unlikely 流水线技 ...

  6. html优化网站的方法,利用HTML优化加快网页速度方法介绍

    减少web页面下载时间的关键就是设法减小文件大小.当多个页面共用一些成分内容时,就可以考虑将这些公用部分单独分离出来.比如:我们可以将多个HTML页面都用到的脚本程序编写成独立存在的.js文件,然后再 ...

  7. CPU流水线的探秘之旅

    英文原文:A Journey Through the CPU Pipeline 编译:@deuso_ICT 作为程序员,CPU 在我们的工作中扮演了核心角色,因此了解处理器内部的工作方式对程序员来说不 ...

  8. 利用国内镜像加快pip下载速度和成功率

    利用国内镜像加快pip下载速度和成功率 利用pip安装软件包时国外的源下载速度太慢,还经常不稳定,特别容易出现超时,下载失败,安装v出错等问题,将pip的安装源替换为国内镜像即可解决该问题 解决方法: ...

  9. Linux ubuntu18.04.4下如何利用CPU利用率画出50%直线、正弦曲线、心形曲线

    Linux ubuntu18.04.4下如何利用CPU利用率画出50%直线.正弦曲线.心形曲线 记录一下,经过了各种失败以后,本菜鸡在一步步的尝试中终于成功画出了曲线.整理一下,我经历的最主要问题如下 ...

最新文章

  1. 附录3:Pandas实例记录
  2. Android提醒微技巧,你真的了解Dialog、Toast和Snackbar吗?
  3. pgsql 查询每天的最后一条_Qamp;A | 如何允许他人查询表单数据?
  4. html php上传图片验证判断,HTML_PHP实例:上传多个图片并校验的代码,单张的图片上传是不复杂的, - phpStudy...
  5. mysql5.7.26修改账号密码_修改mysql5.7的用户密码
  6. [ 转载 ] Java基础10--关于Object类下所有方法的简单解析
  7. 【实践】腾讯PCG数据中台DEVOPS和AIOPS实践.pdf(附下载链接)
  8. 为什么可积不一定可导_为什么很多人开车时一定要听歌?老司机:不听歌,要车何用?...
  9. zz推荐的软件测试英文网站
  10. instanceof和typeof
  11. 62.Linux/Unix 系统编程手册(下) -- 终端
  12. UI设计中金刚区图标设计总结
  13. 小米开源文件管理器MiCodeFileExplorer-源码研究(9)-入口分析
  14. 2018年,Windows Phone 8.1还能做什么
  15. web端 小米商城网站总结
  16. 赞!WEB设计之路!网络视觉艺术发展史概览
  17. origin画图修改横坐标
  18. C语言 常量和宏定义
  19. android usb otg 查看,android USB OTG功能如何打开及实现
  20. 64位系统最大支持多少内存

热门文章

  1. js实现父页面的刷新
  2. 先建立一个Point(点)类,包含数据成员x,y(坐标点)。以它为基类,派生出一个Circle(圆)类,增加数据成员r(半径),再以Circle类为直接基类,派生出一个Cylinder(圆柱体)类,再
  3. 【网络信息安全】PKI 技术
  4. 参数辨识法来调节simulink中的PI参数
  5. 解决M1芯片版本安装Sketch问题 M1芯片安装那个Sketch版本?Sketch已完美支持M1芯片安装 支持big sur系统
  6. python画circos图_​用Python把图做的好看点:用Matplotlib画个Circos和弦图
  7. 万一开车撞人了,一位退休交警教给你的方法,一定要收存!
  8. linux下仿qq聊天源代码,Linux+glade(GTK+)+C语言+mysql的模仿QQ聊天工具(完善版)...
  9. 医院药品管理系统开源项目-04 【药品销售管理】
  10. 单片机节日彩灯实训报告_基于单片机的节日彩灯控制电路与程序设计报告与资料...