Halide代码流程:
- 声明函数,变量,表达式
- 用变量、表达式等实现算法
- 使用调度策略对算法进行调度调优
- 调用函数的realize成员函数,对函数进行实现
- 写出数据
example 1:
Halide::Func gradient;
// Func对象表示了一个pipeline阶段。它是一个纯函数,定义了每个像素点对应的值。
Halide::Var x, y;
// Var对象是Func的定义域,或者说是Func的参数。它们本身没有任何意义。Var用来索引
Halide::Expr e = x + y;
// 义了一个 x + y的表达式。变量重载了算数运算符,因此变量进行运算的结果是一个Expr对象
gradient(x, y) = e;
// 在像素点x,y处,图像的像素值为表达式e的结果 gradient(x, y) = x + y;
// 告诉Halide在在指定的区域进行计算。这里的domain可以理解为像素点的范围。
Halide::Buffer<int32_t> output = gradient.realize(800, 600);
output(i, j) != i + j
example 2:
Halide::Buffer<uint8_t> input = load_image("s.png");
//定义Func对象,Func对象表示我们将要进行的图像亮度提升pipeline
Halide::Func brighter;
//定义操作图像像素的索引,即Var(变量)x(column),y(row),c(channel)
// x,y为坐标索引,c为颜色通道索引。
Halide::Var x, y, c;
//value表达式表示c通道(x,y)坐标处的像素值
Halide::Expr value = input(x, y, c);
// 为了进行浮点计算,先将数据类型转换成单精度浮点类型
value = Halide::cast<float>(value);
// 将c通道(x,y)坐标处的像素值放大1.5倍
value = value * 1.5f;
// 为了防止数据溢出,将放大后的像素值clip到[0,255]区间,并转换成8位无符号整型
value = Halide::min(value, 255.0f);
value = Halide::cast<uint8_t>(value);
//定义函数,将亮度提升后的像素值,赋值给函数对象的(x,y,c)点
brighter(x, y, c) = value;
// brighter(x, y, c) = Halide::cast<uint8_t>(min(input(x, y, c) * 1.5f, 255));
// 上述所有操作知识在内存中建立Halide程序,告诉Halide怎么去进行算法操作,即对算法进行了定义。
// 实际上还没有开始进行任何像素的处理。甚至Halide程序还没有进行编译
// 现在将要实现函数。输出图像的尺寸必须和输入图像的尺寸相匹配。
//如果我们只想提高输入图像部分区域像素点的亮度,可以指定一个小一点的尺寸。
//如果需要一个更大尺寸的输出,Halide在运行时会抛出一个错误告诉我们,边界超出输入图像。
Halide::Buffer<uint8_t> output =
brighter.realize(input.width(), input.height(), input.channels());
// 写下被处理过的图像
save_image(output, "brighter.png");
调试
- 设置环境变量HL_DEBUG_CODEGEN=1,此时运行程序会打印出编译的不同阶段和最终pipeline的伪代码
- 设置HL_DEBUG_CODEGEN=2,此时会输出Halide编译的各个不同阶段,而且会输出llvm最终生成的字节码
- 也提供HTML形式的伪代码输出,支持语法高亮,代码折叠,翻遍大规模复杂pipeline的阅读
Func gradient("gradient");
Var x("x"), y("y");
gradient(x, y) = x + y;
// 给Func和Var的构造函数传入一个string类型的名字
Buffer<int> output = gradient.realize(8, 8);
gradient.compile_to_lowered_stmt("gradient.html", {}, HTML);
- 用tracing,print,print_when调试