halide编程技术指南(连载二)

本文是halide编程指南的连载,已同步至公众号

目录

第二章 图像处理

第三章 检查生成的代码

第四章 使用tracing, print, 和print_when进行调试

第五章 Vectorize, parallelize, unroll and tile 你的代码


第二章 图像处理

本章演示如何传递输入图像并进行操作。

// halide提供了对应方法用于导入png图像
#include "halide_image_io.h"

using namespace Halide::Tools;

int main(int argc, char **argv) {
    // 下面这个程序是对图像做亮度增强

    // 首先导入图像
    Halide::Buffer<uint8_t> input = load_image("images/rgb.png");

    // 就是下面这个图
    参考下面的图21

    // 下面定义一个代表这个图像处理管道的
    Halide::Func brighter;

    // ”Func” 有三个参数,代表了图像的位置和颜色通道,halide把颜色通道作为图像的额外维度。
    Halide::Var x, y, c;

    // 通常,我们可能会将整个函数定义写在一行上。 在这里,我们将其分解,以便我们可以解释我们在每个步骤中所做的事情。

    // 对于输入图像的每个像素
    Halide::Expr value = input(x, y, c);

    // 将其强制转换为浮点值
    value = Halide::cast<float>(value);

    // 乘以1.5使其变亮。halide将实数表示为浮点数,而不是双精度数,因此我们在常数的末尾加上“ f”。
    value = value * 1.5f;

    // 将其限制为小于255,因此将其强制转换回8位unsigned int时不会溢出。
    value = Halide::min(value, 255.0f);

    // 将其强制转换为8位无符号整数。
    value = Halide::cast<uint8_t>(value);

    // 定义函数
    brighter(x, y, c) = value;

    // 将上面所有的等效成一行
    // brighter(x, y, c) = Halide::cast<uint8_t>(min(input(x, y, c) * 1.5f, 255));
    // 在这个版本中
    // - 跳过了强制转换为浮点数,因为乘以1.5f会自动执行。
    // - 还使用了整数常量作为对min的调用中的第二个参数,因为它被强制转换为float以便与第一个参数兼容。
    // - 在调用min函数时,省略了halide::,因为使用了koenig lookup。(也叫做argument dependent lookup(ADL)。

    // 请记住,到目前为止,我们所做的只是在内存中构建Halide程序的表示形式。 我们实际上尚未处理任何像素。我们甚至还没有编译该Halide程序。

    // 因此,现在我们将实现Func。输出图像的尺寸应与输入图像的尺寸匹配。如果我们只是想加亮输入图像的一部分,我们可以要求使用较小的尺寸。如果我们请求更大的尺寸,那么Halide将在运行时抛出错误,告诉我们正试图读取输入图像上的边界。
    Halide::Buffer<uint8_t> output =
        brighter.realize(input.width(), input.height(), input.channels());

    // 保存输出以进行检查。它看起来像只鹦鹉。
    save_image(output, "brighter.png");

    // 有关输出的小版本,请参见下文。
    参考下面的图22
    printf("Success!\n");
    return 0;
}

图21

图22

第三章 检查生成的代码

演示了如何检查Halide编译器正在生成的内容。

#include "Halide.h"
#include <stdio.h>

// 这次我们将导入整个 Halide 命名空间
using namespace Halide;
int main(int argc, char **argv) {
    // 我们将从第1课定义的简单的单阶段成像管道开始。
    // 本课将涉及调试,但是不幸的是,在C ++中,对象不知道自己的名称,这使我们很难理解生成的代码。要解决此问题,您可以将字符串传递给Func和Var构造函数以为其命名,从而进行调试。也就是说,给对象一个名字,在调试的时候,只需要看这个名字,就知道这个变量是,值是多少。
    Func gradient("gradient");
    Var x("x"), y("y");
    gradient(x, y) = x + y;

    // 实现该功能以产生输出图像。在本课程中,我们将使其保持很小的尺寸。
    Buffer<int> output = gradient.realize(8, 8);

    // 那一行编译并运行了管道。 尝试在环境变量HL_DEBUG_CODEGEN设置为1的情况下运行本课程。它将打印出编译的各个阶段以及最终管道的伪代码表示。
    // 如果将HL_DEBUG_CODEGEN设置为更高的数字,则可以看到有关Halide如何编译管道的越来越多的详细信息。设置 HL_DEBUG_CODEGEN = 2将显示编译的每个阶段的Halide代码,以及最后生成的llvm位代码。
    // Halide还将输出此输出的HTML版本,该版本支持语法突出显示和代码折叠,因此对于大型管道而言,阅读起来会更好。运行本教程后,用浏览器打开gradient.html。
    gradient.compile_to_lowered_stmt("gradient.html", {}, HTML);

    // 通常,您可以找出Halide使用此伪代码生成的代码。 在下一课中,我们将看到如何在运行时监听Halide。
    printf("Success!\n");
    return 0;
}

第四章 使用tracing, print, 和print_when进行调试

本课演示如何在运行时遵循Halide的操作。

#include "Halide.h"
#include <stdio.h>
using namespace Halide;

int main(int argc, char **argv) {
    Var x("x"), y("y");
    // 在计算Funcs的值时将其打印出来。
    {
        // 我们将像以前一样定义渐变函数。
        Func gradient("gradient");
        gradient(x, y) = x + y;

        // 并告诉Halide我们希望收到所有评估的通知。
        gradient.trace_stores();

        // 在8x8区域上实现该功能。
        printf("Evaluating gradient\n");
        Buffer<int> output = gradient.realize(8, 8);
---------------------------------------
 > Begin pipeline gradient.0()
 > Store gradient.0(0, 0) = 0
 > Store gradient.0(1, 0) = 1
 > Store gradient.0(2, 0) = 2
 > Store gradient.0(3, 0) = 3
 > Store gradient.0(4, 0) = 4
 > Store gradient.0(5, 0) = 5
 > Store gradient.0(6, 0) = 6
 > Store gradient.0(7, 0) = 7
 > Store gradient.0(0, 1) = 1
 > Store gradient.0(1, 1) = 2
 > Store gradient.0(2, 1) = 3
 > Store gradient.0(3, 1) = 4
 > Store gradient.0(4, 1) = 5
 > Store gradient.0(5, 1) = 6
 > Store gradient.0(6, 1) = 7
 > Store gradient.0(7, 1) = 8
 > Store gradient.0(0, 2) = 2
 > Store gradient.0(1, 2) = 3
 > Store gradient.0(2, 2) = 4
 > Store gradient.0(3, 2) = 5
 > Store gradient.0(4, 2) = 6
 > Store gradient.0(5, 2) = 7
 > Store gradient.0(6, 2) = 8
 > Store gradient.0(7, 2) = 9
 > Store gradient.0(0, 3) = 3
 > Store gradient.0(1, 3) = 4
 > Store gradient.0(2, 3) = 5
 > Store gradient.0(3, 3) = 6
 > Store gradient.0(4, 3) = 7
 > Store gradient.0(5, 3) = 8
 > Store gradient.0(6, 3) = 9
 > Store gradient.0(7, 3) = 10
 > Store gradient.0(0, 4) = 4
 > Store gradient.0(1, 4) = 5
 > Store gradient.0(2, 4) = 6
 > Store gradient.0(3, 4) = 7
 > Store gradient.0(4, 4) = 8
 > Store gradient.0(5, 4) = 9
 > Store gradient.0(6, 4) = 10
 > Store gradient.0(7, 4) = 11
 > Store gradient.0(0, 5) = 5
 > Store gradient.0(1, 5) = 6
 > Store gradient.0(2, 5) = 7
 > Store gradient.0(3, 5) = 8
 > Store gradient.0(4, 5) = 9
 > Store gradient.0(5, 5) = 10
 > Store gradient.0(6, 5) = 11
 > Store gradient.0(7, 5) = 12
 > Store gradient.0(0, 6) = 6
 > Store gradient.0(1, 6) = 7
 > Store gradient.0(2, 6) = 8
 > Store gradient.0(3, 6) = 9
 > Store gradient.0(4, 6) = 10
 > Store gradient.0(5, 6) = 11
 > Store gradient.0(6, 6) = 12
 > Store gradient.0(7, 6) = 13
 > Store gradient.0(0, 7) = 7
 > Store gradient.0(1, 7) = 8
 > Store gradient.0(2, 7) = 9
 > Store gradient.0(3, 7) = 10
 > Store gradient.0(4, 7) = 11
 > Store gradient.0(5, 7) = 12
 > Store gradient.0(6, 7) = 13
 > Store gradient.0(7, 7) = 14
 > End pipeline gradient.0()
---------------------------------------
        // 这将打印出所有评估gradient(x,y)的时间。

        // 现在我们可以窥探Halide在做什么,让我们尝试第一个调度原语。我们将制作一个新版本的渐变,它可以并行处理每个扫描线。
        Func parallel_gradient("parallel_gradient");
        parallel_gradient(x, y) = x + y;

        // 我们还将跟踪此功能。
        parallel_gradient.trace_stores();

        // 到目前为止,情况是一样的。 我们已经定义了算法,但是还没有说明如何安排它。 通常,探索不同的调度决策不会改变描述算法的代码。

        // 现在,我们告诉Halide在y坐标上使用并行for循环。 在Linux上,我们使用线程池和任务队列来运行它。 在OS X上,我们调用大型中央调度,这对我们执行相同的操作。
        parallel_gradient.parallel(y);

        // 这次,printfs应该出现故障,因为每条扫描线都可能在不同的线程中处理。 线程数应适合您的系统,但是在  Linux上,您可以使用环境变量HL_NUM_THREADS手动控制它。
        printf("\nEvaluating parallel_gradient\n");
        parallel_gradient.realize(8, 8);
--------------------------------------------
 > Begin pipeline parallel_gradient.0()
 > Store parallel_gradient.0(0, 0) = 0
 > Store parallel_gradient.0(1, 0) = 1
 > Store parallel_gradient.0(2, 0) = 2
 > Store parallel_gradient.0(3, 0) = 3
 > Store parallel_gradient.0(4, 0) = 4
 > Store parallel_gradient.0(5, 0) = 5
 > Store parallel_gradient.0(6, 0) = 6
 > Store parallel_gradient.0(7, 0) = 7
 > Store parallel_gradient.0(0, 1) = 1
 > Store parallel_gradient.0(0, 3) = 3
 > Store parallel_gradient.0(0, 2) = 2
 > Store parallel_gradient.0(1, 3) = 4
 > Store parallel_gradient.0(1, 1) = 2
 > Store parallel_gradient.0(1, 2) = 3
 > Store parallel_gradient.0(2, 3) = 5
 > Store parallel_gradient.0(0, 4) = 4
 > Store parallel_gradient.0(2, 1) = 3
 > Store parallel_gradient.0(3, 3) = 6
 > Store parallel_gradient.0(2, 2) = 4
 > Store parallel_gradient.0(0, 5) = 5
 > Store parallel_gradient.0(0, 7) = 7
 > Store parallel_gradient.0(0, 6) = 6
 > Store parallel_gradient.0(1, 6) = 7
 > Store parallel_gradient.0(1, 7) = 8
 > Store parallel_gradient.0(3, 1) = 4
 > Store parallel_gradient.0(2, 6) = 8
 > Store parallel_gradient.0(2, 7) = 9
 > Store parallel_gradient.0(1, 4) = 5
 > Store parallel_gradient.0(3, 7) = 10
 > Store parallel_gradient.0(3, 6) = 9
 > Store parallel_gradient.0(4, 3) = 7
 > Store parallel_gradient.0(4, 6) = 10
 > Store parallel_gradient.0(4, 7) = 11
 > Store parallel_gradient.0(3, 2) = 5
 > Store parallel_gradient.0(1, 5) = 6
 > Store parallel_gradient.0(5, 7) = 12
 > Store parallel_gradient.0(5, 6) = 11
 > Store parallel_gradient.0(2, 4) = 6
 > Store parallel_gradient.0(6, 6) = 12
 > Store parallel_gradient.0(6, 7) = 13
 > Store parallel_gradient.0(7, 6) = 13
 > Store parallel_gradient.0(7, 7) = 14
 > Store parallel_gradient.0(4, 1) = 5
 > Store parallel_gradient.0(4, 2) = 6
 > Store parallel_gradient.0(2, 5) = 7
 > Store parallel_gradient.0(5, 1) = 6
 > Store parallel_gradient.0(3, 4) = 7
 > Store parallel_gradient.0(5, 3) = 8
 > Store parallel_gradient.0(5, 2) = 7
 > Store parallel_gradient.0(3, 5) = 8
 > Store parallel_gradient.0(6, 2) = 8
 > Store parallel_gradient.0(4, 5) = 9
 > Store parallel_gradient.0(7, 2) = 9
 > Store parallel_gradient.0(5, 5) = 10
 > Store parallel_gradient.0(6, 1) = 7
 > Store parallel_gradient.0(6, 3) = 9
 > Store parallel_gradient.0(6, 5) = 11
 > Store parallel_gradient.0(7, 3) = 10
 > Store parallel_gradient.0(7, 1) = 8
 > Store parallel_gradient.0(4, 4) = 8
 > Store parallel_gradient.0(7, 5) = 12
 > Store parallel_gradient.0(5, 4) = 9
 > Store parallel_gradient.0(6, 4) = 10
 > Store parallel_gradient.0(7, 4) = 11
 > End pipeline parallel_gradient.0()
----------------------------------------------
    }

    // 打印单个Exprs。
    {
        // trace_stores()只能打印Func的值。有时您想检查子表达式的值,而不是整个Func。内置函数'print'可以包装在任何Expr周围,以在每次评估时打印该Expr的值。

        // 例如,假设我们有一些Func,它是两个项的和:
        Func f;
        f(x, y) = sin(x) + cos(y);

        // 如果我们只想检查其中一个术语,则可以像这样包装“print”:
        Func g;
        g(x, y) = sin(x) + print(cos(y));

        printf("\nEvaluating sin(x) + cos(y), and just printing cos(y)\n");
        g.realize(4, 4);
--------------------------------------
 > 1.000000
 > 1.000000
 > 1.000000
 > 1.000000
 > 0.540302
 > 0.540302
 > 0.540302
 > 0.540302
 > -0.416147
 > -0.416147
 > -0.416147
 > -0.416147
 > -0.989992
 > -0.989992
 > -0.989992
 > -0.989992
-----------------------------------------
    }

    // 打印其他上下文。
    {
        // print可以使用多个参数。它会打印所有内容并评估为第一个。参数可以是Exprs或常量字符串。这可以用于在值旁边打印其他上下文:
        Func f;
        f(x, y) = sin(x) + print(cos(y), "<- this is cos(", y, ") when x =", x);

        printf("\nEvaluating sin(x) + cos(y), and printing cos(y) with more context\n");
        f.realize(4, 4);
------------------------------------------------
 > 1.000000 <- this is cos( 0 ) when x = 0
 > 1.000000 <- this is cos( 0 ) when x = 1
 > 1.000000 <- this is cos( 0 ) when x = 2
 > 1.000000 <- this is cos( 0 ) when x = 3
 > 0.540302 <- this is cos( 1 ) when x = 0
 > 0.540302 <- this is cos( 1 ) when x = 1
 > 0.540302 <- this is cos( 1 ) when x = 2
 > 0.540302 <- this is cos( 1 ) when x = 3
 > -0.416147 <- this is cos( 2 ) when x = 0
 > -0.416147 <- this is cos( 2 ) when x = 1
 > -0.416147 <- this is cos( 2 ) when x = 2
 > -0.416147 <- this is cos( 2 ) when x = 3
 > -0.989992 <- this is cos( 3 ) when x = 0
 > -0.989992 <- this is cos( 3 ) when x = 1
 > -0.989992 <- this is cos( 3 ) when x = 2
 > -0.989992 <- this is cos( 3 ) when x = 3
-------------------------------------------------
        // 将上面的表达式分成多行可能会很有用,以使调试时更容易打开和关闭打印某些值。
        Expr e = cos(y);
        // 取消注释以下行以打印cos(y)的值
        // e = print(e, "<- this is cos(", y, ") when x =", x);
        Func g;
        g(x, y) = sin(x) + e;
        g.realize(4, 4);
    }

    // 条件打印
    {
        // print和trace_stores均可产生大量输出。如果您正在寻找一个罕见的事件,或者只是想看看单个像素发生了什么,那么很难挖掘出如此大量的输出。可以使用函数print_when有条件地打印Expr。print_when的第一个参数是布尔Expr。 如果Expr的计算结果为true,则返回第二个参数并打印所有参数。如果Expr的计算结果为false,则仅返回第二个参数,并且不打印。

        Func f;
        Expr e = cos(y);
        e = print_when(x == 37 && y == 42, e, "<- this is cos(y) at x, y == (37, 42)");
        f(x, y) = sin(x) + e;
        printf("\nEvaluating sin(x) + cos(y), and printing cos(y) at a single pixel\n");
        f.realize(640, 480);
---------------------------------------------------------------
> -0.399985 <- this is cos(y) at x, y == (37, 42)
----------------------------------------------------------------
        // print_when也可以用来检查您不期望的值:
        Func g;
        e = cos(y);
        e = print_when(e < 0, e, "cos(y) < 0 at y ==", y);
        g(x, y) = sin(x) + e;
        printf("\nEvaluating sin(x) + cos(y), and printing whenever cos(y) < 0\n");
        g.realize(4, 4);
------------------------------------------
 > -0.416147 cos(y) < 0 at y == 2
 > -0.416147 cos(y) < 0 at y == 2
 > -0.416147 cos(y) < 0 at y == 2
 > -0.416147 cos(y) < 0 at y == 2
 > -0.989992 cos(y) < 0 at y == 3
 > -0.989992 cos(y) < 0 at y == 3
 > -0.989992 cos(y) < 0 at y == 3
 > -0.989992 cos(y) < 0 at y == 3
-------------------------------------------
    }

    // 在编译时打印表达式。
    {
        // 上面的代码跨几行代码构建了Halide Expr。如果您正在以编程方式构造一个复杂的表达式,并且想要检查所创建的Expr是否符合您的想法,则还可以使用C++流打印出表达式本身:
        Var fizz("fizz"), buzz("buzz");
        Expr e = 1;
        for (int i = 2; i < 100; i++) {
            if (i % 3 == 0 && i % 5 == 0) e += fizz*buzz;
            else if (i % 3 == 0) e += fizz;
            else if (i % 5 == 0) e += buzz;
            else e += i;
        }
        std::cout << "Printing a complex Expr: " << e << "\n";
-------------------------------------------------------------------------------
> Printing a complex Expr: ((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((1 + 2) + fizz) + 4) + buzz) + fizz) + 7) + 8) + fizz) + buzz) + 11) + fizz) + 13) + 14) + (fizz*buzz)) + 16) + 17) + fizz) + 19) + buzz) + fizz) + 22) + 23) + fizz) + buzz) + 26) + fizz) + 28) + 29) + (fizz*buzz)) + 31) + 32) + fizz) + 34) + buzz) + fizz) + 37) + 38) + fizz) + buzz) + 41) + fizz) + 43) + 44) + (fizz*buzz)) + 46) + 47) + fizz) + 49) + buzz) + fizz) + 52) + 53) + fizz) + buzz) + 56) + fizz) + 58) + 59) + (fizz*buzz)) + 61) + 62) + fizz) + 64) + buzz) + fizz) + 67) + 68) + fizz) + buzz) + 71) + fizz) + 73) + 74) + (fizz*buzz)) + 76) + 77) + fizz) + 79) + buzz) + fizz) + 82) + 83) + fizz) + buzz) + 86) + fizz) + 88) + 89) + (fizz*buzz)) + 91) + 92) + fizz) + 94) + buzz) + fizz) + 97) + 98) + fizz)    }
-----------------------------------------------------------------------------------
    printf("Success!\n");
    return 0;
}

第五章 Vectorize, parallelize, unroll and tile 你的代码

#include "Halide.h"
#include <stdio.h>
#include <algorithm>
using namespace Halide;
int main(int argc, char **argv) {
    // 我们将以几种不同的方式定义和安排渐变函数,并查看像素的计算顺序。
    Var x("x"), y("y");
    // 首先,我们观察默认顺序。
    {
        Func gradient("gradient");
        gradient(x, y) = x + y;
        gradient.trace_stores();

        // 默认情况下,我们沿着行走,然后沿着列走。这意味着x变化很快,而y变化缓慢。x是列,y是行,因此这是行主要遍历。
        printf("Evaluating gradient row-major\n");
        Buffer<int> output = gradient.realize(4, 4);
----------------------------------------------------------------
 > Begin pipeline gradient.0()
 > Store gradient.0(0, 0) = 0
 > Store gradient.0(1, 0) = 1
 > Store gradient.0(2, 0) = 2
 > Store gradient.0(3, 0) = 3
 > Store gradient.0(0, 1) = 1
 > Store gradient.0(1, 1) = 2
 > Store gradient.0(2, 1) = 3
 > Store gradient.0(3, 1) = 4
 > Store gradient.0(0, 2) = 2
 > Store gradient.0(1, 2) = 3
 > Store gradient.0(2, 2) = 4
 > Store gradient.0(3, 2) = 5
 > Store gradient.0(0, 3) = 3
 > Store gradient.0(1, 3) = 4
 > Store gradient.0(2, 3) = 5
 > Store gradient.0(3, 3) = 6
 > End pipeline gradient.0()
----------------------------------------------------------------
        // 有关此操作的可视化信息,请参见下图。
        参考下面图51

        // 等价的C代码为:
        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");

        // Tracing 是了解执行步骤的一种有用方法。您还可以要求Halide打印出伪代码,以显示Halide正在生成哪些循环:
        printf("Pseudo-code for the schedule:\n");
        gradient.print_loop_nest();
        printf("\n");
----------------------------------------------------------
 > produce gradient:
 >   for y:
 >     for x:
 >       gradient(...) = ...
----------------------------------------------------------
        // 因为我们使用的是默认顺序,所以它应该打印:
        // compute gradient:
        //   for y:
        //     for x:
        //       gradient(...) = ...
    }

    // 重新排列变量.
    {
        Func gradient("gradient_col_major");
        gradient(x, y) = x + y;
        gradient.trace_stores();

        // 如果我们对x和y重新排序,我们可以改为沿列走。重新排序的调用采用Func的参数,并为生成的for循环设置新的嵌套顺序。参数是从最内层循环中指定的,因此以下调用将y放入内层循环:
        gradient.reorder(y, x);

        // 这意味着y(行)将快速变化,而x(列)将缓慢变化,因此这是列的主要遍历。

        printf("Evaluating gradient column-major\n");
        Buffer<int> output = gradient.realize(4, 4);
--------------------------------------------------------
 > Begin pipeline gradient_col_major.0()
 > Store gradient_col_major.0(0, 0) = 0
 > Store gradient_col_major.0(0, 1) = 1
 > Store gradient_col_major.0(0, 2) = 2
 > Store gradient_col_major.0(0, 3) = 3
 > Store gradient_col_major.0(1, 0) = 1
 > Store gradient_col_major.0(1, 1) = 2
 > Store gradient_col_major.0(1, 2) = 3
 > Store gradient_col_major.0(1, 3) = 4
 > Store gradient_col_major.0(2, 0) = 2
 > Store gradient_col_major.0(2, 1) = 3
 > Store gradient_col_major.0(2, 2) = 4
 > Store gradient_col_major.0(2, 3) = 5
 > Store gradient_col_major.0(3, 0) = 3
 > Store gradient_col_major.0(3, 1) = 4
 > Store gradient_col_major.0(3, 2) = 5
 > Store gradient_col_major.0(3, 3) = 6
 > End pipeline gradient_col_major.0()
-------------------------------------------------------
        // 有关此操作的可视化信息,请参见下图。
        参考下面图52

        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");

        // 如果我们为此时间表打印伪代码,我们将看到y循环现在位于x循环内。
        printf("Pseudo-code for the schedule:\n");
        gradient.print_loop_nest();
        printf("\n");
----------------------------------------------------------------
 > produce gradient_col_major:
 >   for x:
 >     for y:
 >       gradient_col_major(...) = ...
----------------------------------------------------------------
    }

    // 将变量分成两个.
    {
        Func gradient("gradient_split");
        gradient(x, y) = x + y;
        gradient.trace_stores();

        // 对var执行的最强大的原始调度操作是将其拆分为内部和外部子变量:
        Var x_outer, x_inner;
        gradient.split(x, x_outer, x_inner, 2);

        // 这将x上的循环分为两个嵌套循环:x_outer上的外部循环和x_inner上的内部循环。 拆分的最后一个参数是“split factor(分离因子)”。 内部循环从零到分离因子。 外循环从零开始到所需的范围x(在这种情况下为4)除以分离因子。 在循环中,旧变量定义为外部*因子+内部。 如果旧循环以非零值开始,则该值也将添加到循环中。

        printf("Evaluating gradient with x split into x_outer and x_inner \n");
        Buffer<int> output = gradient.realize(4, 4);
------------------------------------------------------
 > Begin pipeline gradient_split.0()
 > Store gradient_split.0(0, 0) = 0
 > Store gradient_split.0(1, 0) = 1
 > Store gradient_split.0(2, 0) = 2
 > Store gradient_split.0(3, 0) = 3
 > Store gradient_split.0(0, 1) = 1
 > Store gradient_split.0(1, 1) = 2
 > Store gradient_split.0(2, 1) = 3
 > Store gradient_split.0(3, 1) = 4
 > Store gradient_split.0(0, 2) = 2
 > Store gradient_split.0(1, 2) = 3
 > Store gradient_split.0(2, 2) = 4
 > Store gradient_split.0(3, 2) = 5
 > Store gradient_split.0(0, 3) = 3
 > Store gradient_split.0(1, 3) = 4
 > Store gradient_split.0(2, 3) = 5
 > Store gradient_split.0(3, 3) = 6
 > End pipeline gradient_split.0()
--------------------------------------------------------
        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");
------------------------------------------------------
 > produce gradient_split:
 >   for y:
 >     for x.x_outer:
 >       for x.x_inner in [0, 1]:
 >         gradient_split(...) = ...
------------------------------------------------------
        // 请注意,像素的评估顺序实际上并没有改变! 拆分本身没有任何作用,但是它确实提供了我们将在下面探讨的所有调度可能性。
    }

    // 将两个变量融合为一个.
    {
        Func gradient("gradient_fused");
        gradient(x, y) = x + y;

        // 分离的相反是“融合”。 融合两个变量会将两个循环合并到扩展范围乘积上的单个for循环中。融合的重要性不如拆分,但它也很有用(我们将在本课程的后面看到)。像拆分一样,融合本身不会改变评估的顺序。
        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");
-------------------------------------------
 > produce gradient_fused:
 >   for x.fused:
 >     gradient_fused(...) = ...
-------------------------------------------
    }

    // 在图块中评估。
    {
        Func gradient("gradient_tiled");
        gradient(x, y) = x + y;
        gradient.trace_stores();

        // 现在我们既可以拆分又可以重新排序,我们可以进行切片评估。让我们将x和y分别除以4,然后对var重新排序以表示块遍历。块遍历将域划分为小的矩形块,并且最外层迭代在块上,并且在其中进行遍历每个块内的点。 如果相邻像素使用重叠的输入数据(例如模糊),则可能对性能有好处。 我们可以这样表示块遍历:
        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);

        // 这种模式很常见,因此有一个简写:
        // 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);
-------------------------------------------------------------
 > Begin pipeline gradient_tiled.0()
 > Store gradient_tiled.0(0, 0) = 0
 > Store gradient_tiled.0(1, 0) = 1
 > Store gradient_tiled.0(2, 0) = 2
 > Store gradient_tiled.0(3, 0) = 3
 > Store gradient_tiled.0(0, 1) = 1
 > Store gradient_tiled.0(1, 1) = 2
 > Store gradient_tiled.0(2, 1) = 3
 > Store gradient_tiled.0(3, 1) = 4
 > Store gradient_tiled.0(0, 2) = 2
 > Store gradient_tiled.0(1, 2) = 3
 > Store gradient_tiled.0(2, 2) = 4
 > Store gradient_tiled.0(3, 2) = 5
 > Store gradient_tiled.0(0, 3) = 3
 > Store gradient_tiled.0(1, 3) = 4
 > Store gradient_tiled.0(2, 3) = 5
 > Store gradient_tiled.0(3, 3) = 6
 > Store gradient_tiled.0(4, 0) = 4
 > Store gradient_tiled.0(5, 0) = 5
 > Store gradient_tiled.0(6, 0) = 6
 > Store gradient_tiled.0(7, 0) = 7
 > Store gradient_tiled.0(4, 1) = 5
 > Store gradient_tiled.0(5, 1) = 6
 > Store gradient_tiled.0(6, 1) = 7
 > Store gradient_tiled.0(7, 1) = 8
 > Store gradient_tiled.0(4, 2) = 6
 > Store gradient_tiled.0(5, 2) = 7
 > Store gradient_tiled.0(6, 2) = 8
 > Store gradient_tiled.0(7, 2) = 9
 > Store gradient_tiled.0(4, 3) = 7
 > Store gradient_tiled.0(5, 3) = 8
 > Store gradient_tiled.0(6, 3) = 9
 > Store gradient_tiled.0(7, 3) = 10
 > Store gradient_tiled.0(0, 4) = 4
 > Store gradient_tiled.0(1, 4) = 5
 > Store gradient_tiled.0(2, 4) = 6
 > Store gradient_tiled.0(3, 4) = 7
 > Store gradient_tiled.0(0, 5) = 5
 > Store gradient_tiled.0(1, 5) = 6
 > Store gradient_tiled.0(2, 5) = 7
 > Store gradient_tiled.0(3, 5) = 8
 > Store gradient_tiled.0(0, 6) = 6
 > Store gradient_tiled.0(1, 6) = 7
 > Store gradient_tiled.0(2, 6) = 8
 > Store gradient_tiled.0(3, 6) = 9
 > Store gradient_tiled.0(0, 7) = 7
 > Store gradient_tiled.0(1, 7) = 8
 > Store gradient_tiled.0(2, 7) = 9
 > Store gradient_tiled.0(3, 7) = 10
 > Store gradient_tiled.0(4, 4) = 8
 > Store gradient_tiled.0(5, 4) = 9
 > Store gradient_tiled.0(6, 4) = 10
 > Store gradient_tiled.0(7, 4) = 11
 > Store gradient_tiled.0(4, 5) = 9
 > Store gradient_tiled.0(5, 5) = 10
 > Store gradient_tiled.0(6, 5) = 11
 > Store gradient_tiled.0(7, 5) = 12
 > Store gradient_tiled.0(4, 6) = 10
 > Store gradient_tiled.0(5, 6) = 11
 > Store gradient_tiled.0(6, 6) = 12
 > Store gradient_tiled.0(7, 6) = 13
 > Store gradient_tiled.0(4, 7) = 11
 > Store gradient_tiled.0(5, 7) = 12
 > Store gradient_tiled.0(6, 7) = 13
 > Store gradient_tiled.0(7, 7) = 14
 > End pipeline gradient_tiled.0()
-----------------------------------------------------------------------
        // 可视化图如下
        参考下面图53

        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");
-------------------------------------------------------------
 > produce gradient_tiled:
 >   for y.y_outer:
 >     for x.x_outer:
 >       for y.y_inner in [0, 3]:
 >         for x.x_inner in [0, 3]:
 >           gradient_tiled(...) = ...
-------------------------------------------------------------
    }

    // 用向量评估.
    {
        Func gradient("gradient_in_vectors");
        gradient(x, y) = x + y;
        gradient.trace_stores();

        // 拆分的好处在于,它确保内部变量从零到分割因子。在大多数情况下,分割因子将是编译时常量,因此我们可以使用单个向量来替换内部变量上的循环。这次我们将除以四,因为在X86上我们可以使用SSE来计算4宽向量。
        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的新外部变量。

        // 这次,我们将对一个8x4的框进行评估,以便每条扫描线有多个工作向量.
        printf("Evaluating gradient with x_inner vectorized \n");
        Buffer<int> output = gradient.realize(8, 4);
---------------------------------------------------------------------------------
 > Begin pipeline gradient_in_vectors.0()
 > Store gradient_in_vectors.0(<0, 1, 2, 3>, <0, 0, 0, 0>) = <0, 1, 2, 3>
 > Store gradient_in_vectors.0(<4, 5, 6, 7>, <0, 0, 0, 0>) = <4, 5, 6, 7>
 > Store gradient_in_vectors.0(<0, 1, 2, 3>, <1, 1, 1, 1>) = <1, 2, 3, 4>
 > Store gradient_in_vectors.0(<4, 5, 6, 7>, <1, 1, 1, 1>) = <5, 6, 7, 8>
 > Store gradient_in_vectors.0(<0, 1, 2, 3>, <2, 2, 2, 2>) = <2, 3, 4, 5>
 > Store gradient_in_vectors.0(<4, 5, 6, 7>, <2, 2, 2, 2>) = <6, 7, 8, 9>
 > Store gradient_in_vectors.0(<0, 1, 2, 3>, <3, 3, 3, 3>) = <3, 4, 5, 6>
 > Store gradient_in_vectors.0(<4, 5, 6, 7>, <3, 3, 3, 3>) = <7, 8, 9, 10>
 > End pipeline gradient_in_vectors.0()
----------------------------------------------------------------------------------
        // 可视化如下
        参考下面图54

        printf("Equivalent C:\n");
        for (int y = 0; y < 4; y++) {
            for (int x_outer = 0; x_outer < 2; x_outer++) {
                // x_inner上的循环已消失,并由表达式的矢量化版本取代。在x86处理器上,Halide为此生成所有SSE.
                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");
--------------------------------------------------------------------
 > produce gradient_in_vectors:
 >   for y:
 >     for x.x_outer:
 >       vectorized x.x_inner in [0, 3]:
 >         gradient_in_vectors(...) = ...
----------------------------------------------------------------------
    }

    // 展开循环.
    {
        Func gradient("gradient_unroll");
        gradient(x, y) = x + y;
        gradient.trace_stores();

        // 如果多个像素共享重叠的数据,则展开计算是有意义的,以便共享值仅计算或加载一次。我们这样做与表示向量化的方式类似。我们拆分一个维度,然后完全展开内部变量的循环。展开不会改变评估的顺序.
        Var x_outer, x_inner;
        gradient.split(x, x_outer, x_inner, 2);
        gradient.unroll(x_inner);

        // 简写为:
        // gradient.unroll(x, 2);

        printf("Evaluating gradient unrolled by a factor of two\n");
        Buffer<int> result = gradient.realize(4, 4);
-----------------------------------------------------------------------------
 > Begin pipeline gradient_unroll.0()
 > Store gradient_unroll.0(0, 0) = 0
 > Store gradient_unroll.0(1, 0) = 1
 > Store gradient_unroll.0(2, 0) = 2
 > Store gradient_unroll.0(3, 0) = 3
 > Store gradient_unroll.0(0, 1) = 1
 > Store gradient_unroll.0(1, 1) = 2
 > Store gradient_unroll.0(2, 1) = 3
 > Store gradient_unroll.0(3, 1) = 4
 > Store gradient_unroll.0(0, 2) = 2
 > Store gradient_unroll.0(1, 2) = 3
 > Store gradient_unroll.0(2, 2) = 4
 > Store gradient_unroll.0(3, 2) = 5
 > Store gradient_unroll.0(0, 3) = 3
 > Store gradient_unroll.0(1, 3) = 4
 > Store gradient_unroll.0(2, 3) = 5
 > Store gradient_unroll.0(3, 3) = 6
 > End pipeline gradient_unroll.0()
-----------------------------------------------------------------------------
        printf("Equivalent C:\n");
        for (int y = 0; y < 4; y++) {
            for (int x_outer = 0; x_outer < 2; x_outer++) {
                // 我们得到了最里面的语句的两个副本,而不是x_inner上的for循环。
                {
                    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");
----------------------------------------------------------------
 > produce gradient_unroll:
 >   for y:
 >     for x.x_outer:
 >       unrolled x.x_inner in [0, 1]:
 >         gradient_unroll(...) = ...
------------------------------------------------------------------
    }

    // 在不分割内容的条件下,利用因子进行分割
    {
        Func gradient("gradient_split_7x2");
        gradient(x, y) = x + y;
        gradient.trace_stores();

        // 拆分可确保内部循环从零到拆分因子,这对于我们上面看到的用法很重要。那么,当我们希望评估x 的总范围不是分割因子的倍数时,会发生什么? 我们将系数除以三,然后评估7x2框而不是我们一直使用的4x4框的渐变。
        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);
-----------------------------------------------------------------------
 > Begin pipeline gradient_split_7x2.0()
 > Store gradient_split_7x2.0(0, 0) = 0
 > Store gradient_split_7x2.0(1, 0) = 1
 > Store gradient_split_7x2.0(2, 0) = 2
 > Store gradient_split_7x2.0(3, 0) = 3
 > Store gradient_split_7x2.0(4, 0) = 4
 > Store gradient_split_7x2.0(5, 0) = 5
 > Store gradient_split_7x2.0(4, 0) = 4
 > Store gradient_split_7x2.0(5, 0) = 5
 > Store gradient_split_7x2.0(6, 0) = 6
 > Store gradient_split_7x2.0(0, 1) = 1
 > Store gradient_split_7x2.0(1, 1) = 2
 > Store gradient_split_7x2.0(2, 1) = 3
 > Store gradient_split_7x2.0(3, 1) = 4
 > Store gradient_split_7x2.0(4, 1) = 5
 > Store gradient_split_7x2.0(5, 1) = 6
 > Store gradient_split_7x2.0(4, 1) = 5
 > Store gradient_split_7x2.0(5, 1) = 6
 > Store gradient_split_7x2.0(6, 1) = 7
 > End pipeline gradient_split_7x2.0()
--------------------------------------------------------------------------
        // 请参阅以下内容以了解发生的情况。 请注意,有些点得到的评价不止一次!
        参考下面图55

        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 2
                for (int x_inner = 0; x_inner < 3; x_inner++) {
                    int x = x_outer * 3;
                    // 在添加x_inner之前,请确保不要评估7x2框之外的点。 我们将x限制为最大4(7减去分割因子)。
                    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");
--------------------------------------------------------------------
 > produce gradient_split_7x2:
 >   for y:
 >     for x.x_outer:
 >       for x.x_inner in [0, 2]:
 >         gradient_split_7x2(...) = ...
-------------------------------------------------------------------

        // 如果看输出,您将看到对某些坐标进行了多次评估。 通常没关系,因为纯Halide函数没有副作用,因此可以安全地多次评估同一点。 如果您像我们一样使用C函数,则有责任确保可以处理多次被评估的同一点。

        // 规则是: 如果我们要求x从x_min到x_min + x_extent,并除以因子“ factor”,则:
        // x_outer从0 to (x_extent + factor - 1)/factor
        // x_inner 从 0 to factor
        // x = min(x_outer * factor, x_extent - factor) + x_inner + x_min
        // 在本例中, x_min 是0, x_extent 是7, factor 是3.

        // 但是,如果您编写带有更新定义的Halide函数(请参阅第9节),那么多次评估同一点是不安全的,因此我们不会应用此技巧。 取而代之的是,计算的值范围将四舍五入到拆分因子的下一个倍数。
    }

    // 融合,平铺和并行化.
    {
        // 在上一课中,我们看到了可以对变量进行并行化的过程。 在这里,我们将其与融合和平铺结合起来以表达有用的模式-并行处理图块。

        // 这是融合的光芒。 当您想跨多个维度并行化而不引入嵌套并行性时,融合会有所帮助。嵌套并行性(并行for循环中的parallel for循环)受Halide支持,但与将并行变量融合到单个并行for循环中相比,性能通常较差。

        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);

        // 调度调用全部返回对Func的引用,因此您也可以将它们链接到一个语句中,以使事情更加清晰:
        // 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);
---------------------------------------------------------------
 > Begin pipeline gradient_fused_tiles.0()
 > Store gradient_fused_tiles.0(0, 0) = 0
 > Store gradient_fused_tiles.0(1, 0) = 1
 > Store gradient_fused_tiles.0(2, 0) = 2
 > Store gradient_fused_tiles.0(3, 0) = 3
 > Store gradient_fused_tiles.0(0, 1) = 1
 > Store gradient_fused_tiles.0(1, 1) = 2
 > Store gradient_fused_tiles.0(2, 1) = 3
 > Store gradient_fused_tiles.0(3, 1) = 4
 > Store gradient_fused_tiles.0(0, 2) = 2
 > Store gradient_fused_tiles.0(1, 2) = 3
 > Store gradient_fused_tiles.0(4, 0) = 4
 > Store gradient_fused_tiles.0(2, 2) = 4
 > Store gradient_fused_tiles.0(5, 0) = 5
 > Store gradient_fused_tiles.0(0, 4) = 4
 > Store gradient_fused_tiles.0(6, 0) = 6
 > Store gradient_fused_tiles.0(1, 4) = 5
 > Store gradient_fused_tiles.0(7, 0) = 7
 > Store gradient_fused_tiles.0(3, 2) = 5
 > Store gradient_fused_tiles.0(4, 1) = 5
 > Store gradient_fused_tiles.0(2, 4) = 6
 > Store gradient_fused_tiles.0(0, 3) = 3
 > Store gradient_fused_tiles.0(5, 1) = 6
 > Store gradient_fused_tiles.0(3, 4) = 7
 > Store gradient_fused_tiles.0(6, 1) = 7
 > Store gradient_fused_tiles.0(1, 3) = 4
 > Store gradient_fused_tiles.0(4, 4) = 8
 > Store gradient_fused_tiles.0(7, 1) = 8
 > Store gradient_fused_tiles.0(0, 5) = 5
 > Store gradient_fused_tiles.0(5, 4) = 9
 > Store gradient_fused_tiles.0(2, 3) = 5
 > Store gradient_fused_tiles.0(4, 2) = 6
 > Store gradient_fused_tiles.0(3, 3) = 6
 > Store gradient_fused_tiles.0(1, 5) = 6
 > Store gradient_fused_tiles.0(5, 2) = 7
 > Store gradient_fused_tiles.0(2, 5) = 7
 > Store gradient_fused_tiles.0(6, 4) = 10
 > Store gradient_fused_tiles.0(6, 2) = 8
 > Store gradient_fused_tiles.0(3, 5) = 8
 > Store gradient_fused_tiles.0(7, 4) = 11
 > Store gradient_fused_tiles.0(7, 2) = 9
 > Store gradient_fused_tiles.0(0, 6) = 6
 > Store gradient_fused_tiles.0(4, 3) = 7
 > Store gradient_fused_tiles.0(4, 5) = 9
 > Store gradient_fused_tiles.0(1, 6) = 7
 > Store gradient_fused_tiles.0(5, 3) = 8
 > Store gradient_fused_tiles.0(5, 5) = 10
 > Store gradient_fused_tiles.0(6, 3) = 9
 > Store gradient_fused_tiles.0(2, 6) = 8
 > Store gradient_fused_tiles.0(6, 5) = 11
 > Store gradient_fused_tiles.0(3, 6) = 9
 > Store gradient_fused_tiles.0(7, 3) = 10
 > Store gradient_fused_tiles.0(7, 5) = 12
 > Store gradient_fused_tiles.0(0, 7) = 7
 > Store gradient_fused_tiles.0(4, 6) = 10
 > Store gradient_fused_tiles.0(1, 7) = 8
 > Store gradient_fused_tiles.0(5, 6) = 11
 > Store gradient_fused_tiles.0(2, 7) = 9
 > Store gradient_fused_tiles.0(6, 6) = 12
 > Store gradient_fused_tiles.0(3, 7) = 10
 > Store gradient_fused_tiles.0(7, 6) = 13
 > Store gradient_fused_tiles.0(4, 7) = 11
 > Store gradient_fused_tiles.0(5, 7) = 12
 > Store gradient_fused_tiles.0(6, 7) = 13
 > Store gradient_fused_tiles.0(7, 7) = 14
 > End pipeline gradient_fused_tiles.0()
-------------------------------------------------------------
        // 切片应以任意顺序出现,但在每个切片中,像素将以行优先顺序遍历。 请参阅下面的可视化内容。
        参考下面图56

        printf("Equivalent (serial) C:\n");
        // 这个最外面的循环应该是并行的for循环,但是在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");
------------------------------------------------------------------------
 > produce gradient_fused_tiles:
 >   parallel x.x_outer.tile_index:
 >     for y.y_inner in [0, 3]:
 >       for x.x_inner in [0, 3]:
 >         gradient_fused_tiles(...) = ...
--------------------------------------------------------------------------
    }

    // 融合到一起.
    {
        // 你准备好了吗? 现在,我们将使用上面的所有功能。
        Func gradient_fast("gradient_fast");
        gradient_fast(x, y) = x + y;

        // 我们将并行处理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);

        // 当我们跨过每个图块时,我们将一次计算两条扫描线。 我们还将在x中向量化。 最简单的表达方式是在每个图块中再次递归地将其划分为4x2子图块,然后将x上的图块矢量化并在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);

        // 请注意,我们没有进行任何显式的拆分或重新排序。 这些是最重要的原始操作,但是大多数情况下,它们埋藏在平铺,矢量化或展开调用之下。
        // 现在让我们在不是图块大小倍数的范围内进行评估。

        // 如果愿意,您可以打开跟踪,但是它将产生很多printfs。 相反,我们将同时使用C语言和Halide计算答案,并查看答案是否匹配。
        Buffer<int> result = gradient_fast.realize(350, 250);

        // 看一下可视化的图
        参考下面图57
         

        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++) {
                    // 我们在x上向量化
                    int x = std::min(x_outer * 64, 350-64) + x_inner_outer*4;
                    int x_vec[4] = {x + 0,
                                    x + 1,
                                    x + 2,
                                    x + 3};

                    // 我们在y上展开
                    int y_base = std::min(y_outer * 64, 250-64) + y_inner_outer*2;
                    {
                        // y_pairs = 0
                        int 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]};

                        // 检查结果.
                        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 = 1
                        int 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]};

                        // 检查结果
                        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");
------------------------------------------------------------------------------
 > produce gradient_fast:
 >   parallel x.x_outer.tile_index:
 >     for y.y_inner.y_inner_outer:
 >       for x.x_inner.x_inner_outer:
 >         unrolled y.y_inner.y_pairs in [0, 1]:
 >           vectorized x.x_inner.x_vectors in [0, 3]:
 >             gradient_fast(...) = ...
-------------------------------------------------------------------------------
        // 请注意,在Halide版本中,算法与优化分开在算法顶部指定一次,并且总共没有多少行代码。将此与C版本进行比较。还有更多的代码(甚至没有并行化或向量化)。更令人讨厌的是,算法的语句(结果是x加y)被埋在混乱中的多个地方。 此C代码难以编写,难以读取,难以调试以及难以进一步优化。 这就是halide存在的原因。
    }
    printf("Success!\n");
    return 0;
}

图51

图52

图53

图54

图55

图56

图57

 

 

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
Halide 是一个开源的图像处理和计算机视觉 DSL(领域特定语言),其目的是让程序员更加轻松地编写高性能的图像处理代码。Halide 的特点是具有易于使用的语法、高性能的代码生成以及可移植性。 本系列文章将介绍 Halide 的基本语法和使用方法,并通过一些实例来演示如何使用 Halide 进行图像处理和计算机视觉任务。 第一篇文章将介绍 Halide 的基本概念和安装方法。 ## 什么是 Halide? Halide 是由丹尼尔·瑞德福(Daniel R. Johnson)和 Jonathan Ragan-Kelley 在 MIT 开发的一个开源项目。它是一个用于编写高性能图像处理和计算机视觉代码的 DSL。 Halide 的主要目标是使程序员能够使用一种简单易懂的语法编写高性能的代码,而无需了解 CPU 或 GPU 的细节。Halide 支持多种平台,包括 x86、ARM、MIPS 和 PowerPC 等 CPU,以及 NVIDIA、AMD 和 ARM 等 GPU。 Halide 的核心概念是“函数”。函数可以看作是一组描述了如何对输入数据进行处理的指令集合。这些指令可以被 Halide 编译成高效的 CPU 或 GPU 代码,并在运行时执行。 ## Halide 的安装方法 Halide 可以在 Linux、macOS 和 Windows 等操作系统上运行,并且支持多种编译器,包括 GCC、Clang 和 MSVC 等。下面是在 Ubuntu 20.04 上安装 Halide 的步骤: 1. 添加 Halide 的 PPA: ``` sudo add-apt-repository ppa:halide/ppa ``` 2. 更新软件包列表: ``` sudo apt-get update ``` 3. 安装 Halide: ``` sudo apt-get install libhalide-dev ``` 安装完成后,可以使用以下命令检查 Halide 是否已经安装成功: ``` pkg-config --cflags --libs Halide ``` 如果输出了一些 Halide 相关的信息,则表示 Halide 安装成功。 ## 总结 本篇文章介绍了 Halide 的基本概念和安装方法。下一篇文章将介绍 Halide 的基本语法和使用方法。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值