Halide入门教程05


// Halide教程第五课:向量化,并行化,平铺,数据分块
// 本课展示了如何才操作函数像素索引的计算顺序,包括向量化/并行化/平铺/分块等技术// 在linux系统中,采用如下指令编译并执行
// g++ lesson_05*.cpp -g -I ../include -L ../bin -lHalide -lpthread -ldl -o lesson_05 -std=c++11
// LD_LIBRARY_PATH=../bin ./lesson_05#include "Halide.h"
#include <stdio.h>
#include <algorithm>
using namespace Halide;int main(int argc, char **argv) {Var x("x"), y("y");// First we observe the default ordering.{Func gradient("gradient");gradient(x, y) = x + y;gradient.trace_stores();//默认遍历像素的顺序是行优先,即内层循环沿着行方向,外层循环沿着列方向printf("Evaluating gradient row-major\n");Buffer<int> output = gradient.realize(4, 4);// The equivalent C is:printf("Equivalent C:\n");for (int y = 0; y < 4; y++) {for (int x = 0; x < 4; x++) {printf("Evaluating at x = %d, y = %d: %d\n", x, y, x + y);}}printf("\n\n");// 跟踪系统调度可以很容易理解调度系统如何工作。可以通过Halide提供的函数来打印出实际工作// 是执行的哪种循环调度。printf("Pseudo-code for the schedule:\n");gradient.print_loop_nest();printf("\n");// Because we're using the default ordering, it should print:// compute gradient://   for y://     for x://       gradient(...) = ...}// Reorder variables.{Func gradient("gradient_col_major");gradient(x, y) = x + y;gradient.trace_stores();// 可以通过reorder函数来改变函数遍历的顺序,下面的语句将行方向(y)置于内层循环,而将原本的内层// 循环调整到了外循环。也就是说y遍历比x遍历更快。是一种列优先的遍历方法gradient.reorder(y, x);printf("Evaluating gradient column-major\n");Buffer<int> output = gradient.realize(4, 4);printf("Equivalent C:\n");for (int x = 0; x < 4; x++) {for (int y = 0; y < 4; y++) {printf("Evaluating at x = %d, y = %d: %d\n", x, y, x + y);}}printf("\n");// printf("Pseudo-code for the schedule:\n");gradient.print_loop_nest();printf("\n");}// Split a variable into two.{Func gradient("gradient_split");gradient(x, y) = x + y;gradient.trace_stores();// 原始调度中,最有效的就是split调度了,它将一个大循环,拆解成一个外部循环和一个内部循环;// 即,将x方向的循环,拆成一个外部循环x_outer和一个内部循环x_inner// 下面的split将x拆成x_outer,x_inner, 内循环的长度为2Var x_outer, x_inner;gradient.split(x, x_outer, x_inner, 2);printf("Evaluating gradient with x split into x_outer and x_inner \n");Buffer<int> output = gradient.realize(4, 4);printf("Equivalent C:\n");for (int y = 0; y < 4; y++) {for (int x_outer = 0; x_outer < 2; x_outer++) {for (int x_inner = 0; x_inner < 2; x_inner++) {int x = x_outer * 2 + x_inner;printf("Evaluating at x = %d, y = %d: %d\n", x, y, x + y);}}}printf("\n");printf("Pseudo-code for the schedule:\n");gradient.print_loop_nest();printf("\n");}// Fuse two variables into one.{Func gradient("gradient_fused");gradient(x, y) = x + y;// 和split相反的是fuse,它将两个变量融合成一个变量,fuse的重要性并没有split高。Var fused;gradient.fuse(x, y, fused);printf("Evaluating gradient with x and y fused\n");Buffer<int> output = gradient.realize(4, 4);printf("Equivalent C:\n");for (int fused = 0; fused < 4*4; fused++) {int y = fused / 4;int x = fused % 4;printf("Evaluating at x = %d, y = %d: %d\n", x, y, x + y);}printf("\n");printf("Pseudo-code for the schedule:\n");gradient.print_loop_nest();printf("\n");}// Evaluating in tiles.// tile的中文意思是瓦片,在这里是指将图像数据拆分成和瓦片一项的小图像块{Func gradient("gradient_tiled");gradient(x, y) = x + y;gradient.trace_stores();// 既然我们可以拆分和调整顺序,我们可以按照划分数据块的方式来进行计算。将x和y方向拆分,然后// 调制x和y的顺序,按照小的数据块的方式来进行遍历。// 一个小的数据块将整个图像划分成小的矩形,外层循环在tile击毙恩进行循环,遍历所有的tile。Var x_outer, x_inner, y_outer, y_inner;gradient.split(x, x_outer, x_inner, 4);gradient.split(y, y_outer, y_inner, 4);gradient.reorder(x_inner, y_inner, x_outer, y_outer);// This pattern is common enough that there's a shorthand for it:// gradient.tile(x, y, x_outer, y_outer, x_inner, y_inner, 4, 4);printf("Evaluating gradient in 4x4 tiles\n");Buffer<int> output = gradient.realize(8, 8);printf("Equivalent C:\n");for (int y_outer = 0; y_outer < 2; y_outer++) {for (int x_outer = 0; x_outer < 2; x_outer++) {for (int y_inner = 0; y_inner < 4; y_inner++) {for (int x_inner = 0; x_inner < 4; x_inner++) {int x = x_outer * 4 + x_inner;int y = y_outer * 4 + y_inner;printf("Evaluating at x = %d, y = %d: %d\n", x, y, x + y);}}}}printf("\n");printf("Pseudo-code for the schedule:\n");gradient.print_loop_nest();printf("\n");}// Evaluating in vectors.{Func gradient("gradient_in_vectors");gradient(x, y) = x + y;gradient.trace_stores();// split能够让内层循环变量在一个划分银子内变化。这个划分因子通常是指定的,因此在编译时是一个常数// 因此我们可以调用向量化指令来执行内部循环。在这里我们指定这个因子为4,这样就可以调用x86机器上的SSE//指令来计算4倍宽的向量,这里充分利用了cpu的SIMD指令来加快计算Var x_outer, x_inner;gradient.split(x, x_outer, x_inner, 4);gradient.vectorize(x_inner);// 上述过程有更简单的形式// gradient.vectorize(x, 4);// 等价于// gradient.split(x, x, x_inner, 4);// gradient.vectorize(x_inner);// 这里我们重用了x,将它当作外循环变量,稍后的调度将x当作外循环(x_outer)来进行调度// 这次在一个8x4的矩形上执行gradient算法printf("Evaluating gradient with x_inner vectorized \n");Buffer<int> output = gradient.realize(8, 4);printf("Equivalent C:\n");for (int y = 0; y < 4; y++) {for (int x_outer = 0; x_outer < 2; x_outer++) {// The loop over x_inner has gone away, and has been// replaced by a vectorized version of the// expression. On x86 processors, Halide generates SSE// for all of this.int x_vec[] = {x_outer * 4 + 0,x_outer * 4 + 1,x_outer * 4 + 2,x_outer * 4 + 3};int val[] = {x_vec[0] + y,x_vec[1] + y,x_vec[2] + y,x_vec[3] + y};printf("Evaluating at <%d, %d, %d, %d>, <%d, %d, %d, %d>:"" <%d, %d, %d, %d>\n",x_vec[0], x_vec[1], x_vec[2], x_vec[3],y, y, y, y,val[0], val[1], val[2], val[3]);}}printf("\n");printf("Pseudo-code for the schedule:\n");gradient.print_loop_nest();printf("\n");}// Unrolling a loop.{Func gradient("gradient_unroll");gradient(x, y) = x + y;gradient.trace_stores();// 如果多个像素共享一些重复的(overlapping)数据,可以将循环铺平,从而共享的数据只需要载入或者// 计算一次。它和向量化的表达方式类似。先将数据进行划分,然后将内层循环铺平。Var x_outer, x_inner;gradient.split(x, x_outer, x_inner, 2);gradient.unroll(x_inner);// The shorthand for this is:// gradient.unroll(x, 2);printf("Evaluating gradient unrolled by a factor of two\n");Buffer<int> result = gradient.realize(4, 4);printf("Equivalent C:\n");for (int y = 0; y < 4; y++) {for (int x_outer = 0; x_outer < 2; x_outer++) {// Instead of a for loop over x_inner, we get two// copies of the innermost statement.{int x_inner = 0;int x = x_outer * 2 + x_inner;printf("Evaluating at x = %d, y = %d: %d\n", x, y, x + y);}{int x_inner = 1;int x = x_outer * 2 + x_inner;printf("Evaluating at x = %d, y = %d: %d\n", x, y, x + y);}}}printf("\n");printf("Pseudo-code for the schedule:\n");gradient.print_loop_nest();printf("\n");}// Splitting by factors that don't divide the extent.{Func gradient("gradient_split_7x2");gradient(x, y) = x + y;gradient.trace_stores();// 当原来图像尺寸不能整除划分的小矩形尺寸时,最后的一行或者一列的tile在边界处会出现重复计算的现象Var x_outer, x_inner;gradient.split(x, x_outer, x_inner, 3);printf("Evaluating gradient over a 7x2 box with x split by three \n");Buffer<int> output = gradient.realize(7, 2);printf("Equivalent C:\n");for (int y = 0; y < 2; y++) {for (int x_outer = 0; x_outer < 3; x_outer++) { // Now runs from 0 to 2for (int x_inner = 0; x_inner < 3; x_inner++) {int x = x_outer * 3;// Before we add x_inner, make sure we don't// evaluate points outside of the 7x2 box. We'll// clamp x to be at most 4 (7 minus the split// factor).if (x > 4) x = 4;x += x_inner;printf("Evaluating at x = %d, y = %d: %d\n", x, y, x + y);}}}printf("\n");printf("Pseudo-code for the schedule:\n");gradient.print_loop_nest();printf("\n");// 如果仔细查看程序的输出,你会发现有些像素点进行了不止一次计算。这是因为尺寸不能整除所致// 由于Halide函数没有边缘效应,因此计算多次并不会产生副作用。}// Fusing, tiling, and parallelizing.{// 这里才是fuse真正发挥威力的地方。如果想要在多个维度进行并行计算,// 可以将多个循环网fuse起来,然后在fuse后的维度下进行并行计算Func gradient("gradient_fused_tiles");gradient(x, y) = x + y;gradient.trace_stores();Var x_outer, y_outer, x_inner, y_inner, tile_index;gradient.tile(x, y, x_outer, y_outer, x_inner, y_inner, 4, 4);gradient.fuse(x_outer, y_outer, tile_index);gradient.parallel(tile_index);// 每个调度函数返回的是引用类型,因此可以按如下方式用点号连接起多次调用// gradient//     .tile(x, y, x_outer, y_outer, x_inner, y_inner, 2, 2)//     .fuse(x_outer, y_outer, tile_index)//     .parallel(tile_index);printf("Evaluating gradient tiles in parallel\n");Buffer<int> output = gradient.realize(8, 8);// tile层面的调度是乱序的,但是在每一个tile内部,是行优先的计算顺序printf("Equivalent (serial) C:\n");// This outermost loop should be a parallel for loop, but that's hard in C.for (int tile_index = 0; tile_index < 4; tile_index++) {int y_outer = tile_index / 2;int x_outer = tile_index % 2;for (int y_inner = 0; y_inner < 4; y_inner++) {for (int x_inner = 0; x_inner < 4; x_inner++) {int y = y_outer * 4 + y_inner;int x = x_outer * 4 + x_inner;printf("Evaluating at x = %d, y = %d: %d\n", x, y, x + y);}}}printf("\n");printf("Pseudo-code for the schedule:\n");gradient.print_loop_nest();printf("\n");}// 将前面演示的调度综合在一起{Func gradient_fast("gradient_fast");gradient_fast(x, y) = x + y;// tile尺寸为64x64,采用并行计算Var x_outer, y_outer, x_inner, y_inner, tile_index;gradient_fast.tile(x, y, x_outer, y_outer, x_inner, y_inner, 64, 64).fuse(x_outer, y_outer, tile_index).parallel(tile_index);// 将内部的64x64tile继续拆分成更小的tile。在x方向上采用向量化的计算(调用SIMD指令),// 在y方向进行平铺Var x_inner_outer, y_inner_outer, x_vectors, y_pairs;gradient_fast.tile(x_inner, y_inner, x_inner_outer, y_inner_outer, x_vectors, y_pairs, 4, 2).vectorize(x_vectors).unroll(y_pairs);Buffer<int> result = gradient_fast.realize(350, 250);printf("Checking Halide result against equivalent C...\n");for (int tile_index = 0; tile_index < 6 * 4; tile_index++) {int y_outer = tile_index / 4;int x_outer = tile_index % 4;for (int y_inner_outer = 0; y_inner_outer < 64/2; y_inner_outer++) {for (int x_inner_outer = 0; x_inner_outer < 64/4; x_inner_outer++) {// We're vectorized across xint x = std::min(x_outer * 64, 350-64) + x_inner_outer*4;int x_vec[4] = {x + 0,x + 1,x + 2,x + 3};// And we unrolled across yint y_base = std::min(y_outer * 64, 250-64) + y_inner_outer*2;{// y_pairs = 0int y = y_base + 0;int y_vec[4] = {y, y, y, y};int val[4] = {x_vec[0] + y_vec[0],x_vec[1] + y_vec[1],x_vec[2] + y_vec[2],x_vec[3] + y_vec[3]};// Check the result.for (int i = 0; i < 4; i++) {if (result(x_vec[i], y_vec[i]) != val[i]) {printf("There was an error at %d %d!\n",x_vec[i], y_vec[i]);return -1;}}}{// y_pairs = 1int y = y_base + 1;int y_vec[4] = {y, y, y, y};int val[4] = {x_vec[0] + y_vec[0],x_vec[1] + y_vec[1],x_vec[2] + y_vec[2],x_vec[3] + y_vec[3]};// Check the result.for (int i = 0; i < 4; i++) {if (result(x_vec[i], y_vec[i]) != val[i]) {printf("There was an error at %d %d!\n",x_vec[i], y_vec[i]);return -1;}}}}}}printf("\n");printf("Pseudo-code for the schedule:\n");gradient_fast.print_loop_nest();printf("\n");// Note that in the Halide version, the algorithm is specified// once at the top, separately from the optimizations, and there// aren't that many lines of code total. Compare this to the C// version. There's more code (and it isn't even parallelized or// vectorized properly). More annoyingly, the statement of the// algorithm (the result is x plus y) is buried in multiple places// within the mess. This C code is hard to write, hard to read,// hard to debug, and hard to optimize further. This is why Halide// exists.// 从前面给出的几个调度的例子可以看出,Halide对应版本的代码相对于C的代码,算法和调度分别进行分离// 调度方便,发ingbianjinxing游动优化,而对应的C语言代码,冗长杂乱,很难写/难读/调试困难,// 不方便进一步优化}printf("Success!\n");return 0;
}

编译执行:

$ g++ lesson_05*.cpp -g -I ../include -L ../bin -lHalide -lpthread -ldl -o lesson_05 -std=c++11
$ ./lesson_05

执行结果:
由于篇幅太长,略

代码详解与效果图示:
1 算法描述,对于所有版本都是一样,和算法调度/优化高度分离,无依赖性

gradient(x, y) = x + y;

2 reorder函数,默认是x为内循环,y为外循环

gradient.reorder(y, x); // 交换x和y循环的顺序

x为内循环

y为内循环

3 split函数,将一个大的变量拆分成两个小的变量进行循环

for x in(0, 20)://...Func.split(x, x_outer, x_inner, 5);// 等价于
for x_outer in(0,4):for x_inner in(0, 5):// ...

4 fuse函数,将多个循环合并成一个大循环,可以方便进行多个维度的并行调度

for x in(1,4):for y in(1,4)://...funcs.fuse(x, y, xy):
//等价于
for xy in (1,16):x = xy / 4;y = xy % 4;// ...

5 tile将整个图像划分成多个小矩形,分别在小矩形上进行处理,每个tile内部仍然是按照行优先的调度进行计算

6 vertorize向量化计算,调用x86的SSE指令,通常是单指令多数据指令(SIMD),加快计算

7 unroll,将循环铺平,铺平并不会影响计算顺序,当循环内部有数据共享时,铺平可以将重复计算的数据只需读一次和计算一次,减少重复计算

8 原图像尺寸不能整数split后tile小矩形尺寸情形

9 tile的外层循环融合成一个整体循环,在此基础上调用多核并行

    Var x_outer, y_outer, x_inner, y_inner, tile_index;gradient.tile(x, y, x_outer, y_outer, x_inner, y_inner, 4, 4);gradient.fuse(x_outer, y_outer, tile_index);gradient.parallel(tile_index);

10 综合调度

        // tile尺寸为64x64,采用并行计算Var x_outer, y_outer, x_inner, y_inner, tile_index;gradient_fast.tile(x, y, x_outer, y_outer, x_inner, y_inner, 64, 64).fuse(x_outer, y_outer, tile_index).parallel(tile_index);// 将内部的64x64tile继续拆分成更小的tile。在x方向上采用向量化的计算(调用SIMD指令),// 在y方向进行平铺Var x_inner_outer, y_inner_outer, x_vectors, y_pairs;gradient_fast.tile(x_inner, y_inner, x_inner_outer, y_inner_outer, x_vectors, y_pairs, 4, 2).vectorize(x_vectors).unroll(y_pairs);

Halide学习笔记----Halide tutorial源码阅读5相关推荐

  1. python学习笔记之三——MakeHuman源码阅读

    1.@装饰器的用法 简单的说,@装饰器就是用来提供调用的, def funA(arg):print 'A'a=arg()@funA def funB():print 'B' 此处的@相当于funA(f ...

  2. C-libev学习笔记-事件库源码阅读6-API-ev_default_loop(),ev_init()

    ev_default_loop() 声明: EV_API_DECL struct ev_loop *ev_default_loop (unsigned int flags EV_CPP (= 0)) ...

  3. JUC.Condition学习笔记[附详细源码解析]

    JUC.Condition学习笔记[附详细源码解析] 目录 Condition的概念 大体实现流程 I.初始化状态 II.await()操作 III.signal()操作 3个主要方法 Conditi ...

  4. K8s基础知识学习笔记及部分源码剖析

    K8s基础知识学习笔记及部分源码剖析 在学习b站黑马k8s视频资料的基础上,查阅了配套基础知识笔记和源码剖析,仅作个人学习和回顾使用. 参考资料: 概念 | Kubernetes 四层.七层负载均衡的 ...

  5. LOAM笔记及A-LOAM源码阅读

    转载出处:LOAM笔记及A-LOAM源码阅读 - WellP.C - 博客园 导读 下面是我对LOAM论文的理解以及对A-LOAM的源码阅读(中文注释版的A-LOAM已经push到github,见A- ...

  6. Vuex 4源码学习笔记 - 通过Vuex源码学习E2E测试(十一)

    在上一篇笔记中:Vuex 4源码学习笔记 - 做好changelog更新日志很重要(十) 我们学到了通过conventional-changelog来生成项目的Changelog更新日志,通过更新日志 ...

  7. Netty学习笔记 - 1 (带源码分析部分)

    2021年12月 北京 xxd 一.Netty是什么 Netty 是由 JBOSS 提供的一个 Java 开源框架,现为 Github 上的独立项目. Netty 是一个异步的.基于事件驱动的网络应用 ...

  8. The Things Network LoRaWAN Stack V3 学习笔记 1.2 源码编译

    前言 源码编译是重头戏,这节笔记记录如何使用 make 命令编译相关部件.由于部分包在墙外,带来了一点麻烦,还分享一个 replace 方式来翻墙的办法. 小能手这段时间在学习 The Things ...

  9. 【从线性回归到 卷积神经网络CNN 循环神经网络RNN Pytorch 学习笔记 目录整合 源码解读 B站刘二大人 绪论(0/10)】

    深度学习 Pytorch 学习笔记 目录整合 数学推导与源码详解 B站刘二大人 目录传送门: 线性模型 Linear-Model 数学原理分析以及源码详解 深度学习 Pytorch笔记 B站刘二大人( ...

  10. The Things Network LoRaWAN Stack V3 学习笔记 1.2 源码编译 - 190821

    文章目录 前言 1 依赖包替换 2 编译准备 3 编译 3.1 cli 编译 3.2 stack 编译 3.3 前端编译 END 前言 源码编译是重头戏,这节笔记记录如何使用 make 命令编译相关部 ...

最新文章

  1. js最简单的几个特效_高阶函数不会用?教你JS中最实用的几个高阶函数用法
  2. SQL中IN与EXISTS的比较
  3. 高处看Surface,WIndow,View,SurfaceView
  4. android 轮播 getWith,NavigationTermSet.GetWithNewView 方法
  5. 算法设计与分析——回溯法——符号三角形问题
  6. 理解transformer
  7. 钉钉一个人怎么多部门 钉钉一个人加入多个部门的技巧
  8. DHCP通过NAP认证
  9. 产品运营周报报表分析案例
  10. IE浏览器“SEC7113: CSS 因 Mime 类型不匹配而被忽略”问题的解决方法
  11. bos物流项目面试问题汇总
  12. 微型计算机cpu组成部分组成部分的功能,微处理器的组成及其各部分的功能?
  13. AN APPROACH OF VECTOR FIELD TEXTURE VISUALIZATION BASED ON FIELD DRIVEN STRENGTH算法实现
  14. bzoj4094 luogu3097 最优挤奶
  15. C语言——将一个正整数分解质因数。例如:输入90,打印出90=2*3*3*5。
  16. PHP过滤昵称中emoji表情
  17. 无人驾驶真体验!老百姓都能打得到的“共享无人车”来了
  18. 码支付源码 无授权—个人免签约支付系统二维码收款即时到账源码
  19. 哈工大计算机854考研经验分享
  20. matplotlib basemap 绘制多边形区域曲线

热门文章

  1. 什么是状态机(Finite-state machine)?
  2. python dataframe是什么_什么是Pandas的DataFrame?
  3. 【附源码】计算机毕业设计JAVA学生公寓管理系统
  4. MS08-067远程代码执行漏洞(CVE-2008-4250) | Windows Server服务RPC请求缓冲区溢出漏洞复现
  5. Mysql 民族数据库
  6. 初次接触tridium niagara软件
  7. 常见职位英文缩写词解释
  8. 【转】电阻屏和电容屏之 二
  9. 编程n的阶乘使用while语句_谷歌工程师新作,东北话编程
  10. 【校招VIP】前端校招考点之页面转换算法