Halide学习笔记----Halide tutorial源码阅读5
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相关推荐
- python学习笔记之三——MakeHuman源码阅读
1.@装饰器的用法 简单的说,@装饰器就是用来提供调用的, def funA(arg):print 'A'a=arg()@funA def funB():print 'B' 此处的@相当于funA(f ...
- 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)) ...
- JUC.Condition学习笔记[附详细源码解析]
JUC.Condition学习笔记[附详细源码解析] 目录 Condition的概念 大体实现流程 I.初始化状态 II.await()操作 III.signal()操作 3个主要方法 Conditi ...
- K8s基础知识学习笔记及部分源码剖析
K8s基础知识学习笔记及部分源码剖析 在学习b站黑马k8s视频资料的基础上,查阅了配套基础知识笔记和源码剖析,仅作个人学习和回顾使用. 参考资料: 概念 | Kubernetes 四层.七层负载均衡的 ...
- LOAM笔记及A-LOAM源码阅读
转载出处:LOAM笔记及A-LOAM源码阅读 - WellP.C - 博客园 导读 下面是我对LOAM论文的理解以及对A-LOAM的源码阅读(中文注释版的A-LOAM已经push到github,见A- ...
- Vuex 4源码学习笔记 - 通过Vuex源码学习E2E测试(十一)
在上一篇笔记中:Vuex 4源码学习笔记 - 做好changelog更新日志很重要(十) 我们学到了通过conventional-changelog来生成项目的Changelog更新日志,通过更新日志 ...
- Netty学习笔记 - 1 (带源码分析部分)
2021年12月 北京 xxd 一.Netty是什么 Netty 是由 JBOSS 提供的一个 Java 开源框架,现为 Github 上的独立项目. Netty 是一个异步的.基于事件驱动的网络应用 ...
- The Things Network LoRaWAN Stack V3 学习笔记 1.2 源码编译
前言 源码编译是重头戏,这节笔记记录如何使用 make 命令编译相关部件.由于部分包在墙外,带来了一点麻烦,还分享一个 replace 方式来翻墙的办法. 小能手这段时间在学习 The Things ...
- 【从线性回归到 卷积神经网络CNN 循环神经网络RNN Pytorch 学习笔记 目录整合 源码解读 B站刘二大人 绪论(0/10)】
深度学习 Pytorch 学习笔记 目录整合 数学推导与源码详解 B站刘二大人 目录传送门: 线性模型 Linear-Model 数学原理分析以及源码详解 深度学习 Pytorch笔记 B站刘二大人( ...
- The Things Network LoRaWAN Stack V3 学习笔记 1.2 源码编译 - 190821
文章目录 前言 1 依赖包替换 2 编译准备 3 编译 3.1 cli 编译 3.2 stack 编译 3.3 前端编译 END 前言 源码编译是重头戏,这节笔记记录如何使用 make 命令编译相关部 ...
最新文章
- js最简单的几个特效_高阶函数不会用?教你JS中最实用的几个高阶函数用法
- SQL中IN与EXISTS的比较
- 高处看Surface,WIndow,View,SurfaceView
- android 轮播 getWith,NavigationTermSet.GetWithNewView 方法
- 算法设计与分析——回溯法——符号三角形问题
- 理解transformer
- 钉钉一个人怎么多部门 钉钉一个人加入多个部门的技巧
- DHCP通过NAP认证
- 产品运营周报报表分析案例
- IE浏览器“SEC7113: CSS 因 Mime 类型不匹配而被忽略”问题的解决方法
- bos物流项目面试问题汇总
- 微型计算机cpu组成部分组成部分的功能,微处理器的组成及其各部分的功能?
- AN APPROACH OF VECTOR FIELD TEXTURE VISUALIZATION BASED ON FIELD DRIVEN STRENGTH算法实现
- bzoj4094 luogu3097 最优挤奶
- C语言——将一个正整数分解质因数。例如:输入90,打印出90=2*3*3*5。
- PHP过滤昵称中emoji表情
- 无人驾驶真体验!老百姓都能打得到的“共享无人车”来了
- 码支付源码 无授权—个人免签约支付系统二维码收款即时到账源码
- 哈工大计算机854考研经验分享
- matplotlib basemap 绘制多边形区域曲线
热门文章
- 什么是状态机(Finite-state machine)?
- python dataframe是什么_什么是Pandas的DataFrame?
- 【附源码】计算机毕业设计JAVA学生公寓管理系统
- MS08-067远程代码执行漏洞(CVE-2008-4250) | Windows Server服务RPC请求缓冲区溢出漏洞复现
- Mysql 民族数据库
- 初次接触tridium niagara软件
- 常见职位英文缩写词解释
- 【转】电阻屏和电容屏之 二
- 编程n的阶乘使用while语句_谷歌工程师新作,东北话编程
- 【校招VIP】前端校招考点之页面转换算法