我开始使用Halide,虽然我已经掌握了其设计的基本原则,但我仍然在努力处理(读取:魔术)所需的细节有效地安排计算。
我已经发布了一个使用Halide将阵列从一个位置复制到另一个位置的MWE。我原以为这会编译成只有少数几个指令,运行时间不到一微秒。相反,它产生4000行装配,运行时间为40ms!因此,显然,我的理解存在重大漏洞。
Halide::Image
?copy
以有效执行复制?最小工作示例
#include <Halide.h>
using namespace Halide;
void _copy(uint8_t* in_ptr, uint8_t* out_ptr, const int M, const int N) {
Image<uint8_t> in(Buffer(UInt(8), N, M, 0, 0, in_ptr));
Image<uint8_t> out(Buffer(UInt(8), N, M, 0, 0, out_ptr));
Var x,y;
Func copy;
copy(x,y) = in(x,y);
copy.realize(out);
}
int main(void) {
uint8_t in[10000], out[10000];
_copy(in, out, 100, 100);
}
编译标志
clang++ -O3 -march=native -std=c++11 -Iinclude -Lbin -lHalide copy.cpp
答案 0 :(得分:2)
让我从第二个问题开始:_copy
需要很长时间,因为它需要将Halide代码编译为x86机器代码。 IIRC,Func
缓存机器代码,但由于copy
是_copy
本地的,因此缓存无法重用。无论如何,调度copy
非常简单,因为它是一个逐点操作:首先,对它进行矢量化可能是有意义的。其次,并行化它可能是有意义的(取决于有多少数据)。例如:
copy.vectorize(x,32).parallel(y);
将沿着x
向量化,向量大小为32,并沿y
并行化。 (我是从记忆中做出来的,可能会对正确的名称产生一些混淆。)当然,做这一切也可能会增加编译时间......
没有良好调度的方法。我通过查看compile_to_lowered_stmt
的输出并分析代码来实现。我还使用Halide::Generator
提供的AOT编译,这确保我只测量代码的运行时而不是编译时。
您的另一个问题是,如何在Halide::Image
中包装现有数组。我不这样做,主要是因为我使用AOT编译。但是,内部Halide使用名为buffer_t
的类型来处理与图像相关的所有内容。还有一个名为Halide::Buffer
的C ++包装器使buffer_t
更容易使用,我认为它也可以在Func::realize
而不是Halide::Image
中使用。重点是:如果你理解buffer_t
,你几乎可以将所有东西都包裹在Halide可以消化的东西中。
答案 1 :(得分:1)
要强调弗洛里安提到的第一件事,我认为这是误解的关键点:你似乎正在计算copy
操作的编制时间(“管道”,常见的Halide术语),而不仅仅是它的执行。你的代码大小估计大概也是由copy.cpp
产生的整个二进制文件,而不仅仅是Halide生成的copy
函数中的代码(实际上它甚至不会出现在你正在编译的二进制文件中) clang,因为它只是在运行时通过JITing构建的。)
您可以在copy.compile_jit()
之前调用realize
来观察您的管道的实际费用(realize
在第一次运行时隐式调用compile_jit
,所以它不是必要的,但将运行时与编译开销分开是很有价值的。然后,您可以将计时器专门置于realize
。
如果你真的想预先编译这个(或任何其他)管道以便静态链接到你的终极程序,这可能是你期望的,你真正想要做的就是使用Func::compile_to_file
一个编译和发出代码的程序(如copy.h
和copy.o
),然后在另一个程序中链接和调用它们。查看教程第10课以更详细地了解这一点:
https://github.com/halide/Halide/blob/master/tutorial/lesson_10_aot_compilation_generate.cpp https://github.com/halide/Halide/blob/master/tutorial/lesson_10_aot_compilation_run.cpp