本篇是 Halide Wiki 上的一篇
Generator Enhancement 的翻譯
由於 Halide Tutorial 中使用了新的寫法, 因此需要了解其中的改進, 儘管 Tutorial 中已經提供基本的 Generator 使用方式, 然而許多進階與注意事項在此文件提到了更多
Generator 這些改進提供了更清晰與洗煉的表示方式, 新的參數讓編譯更有彈性, 以及更強大的混合使用, 新的設計更考量了 AutoScheduler 的搭配, 值得花時間學習
====
在 2016 年 10月下旬 (
https://github.com/halide/Halide/pull/1523), Halide Generators 已被增進與改善:
- 改善 Generator 的可讀性與彈性
- 提供機械產生的 Stubs 讓 Generator 能彼此間的使用更簡單
- 使得與 Autoscheduler 間的整合更容易與更可靠
請注意並沒有任何的改變會讓既有存在的 Generator 無法運作(所有現在既存的 Generator 應該如其地運作); 所有現存的 Generator 在可見的未來將持續如同以往地運作.
這份文件主要在於提供改變的本質與描述如何"升級" Generator 來使用新的增進功能.
使用 Input<X> 取代 Param<X> (以及用 Input<Func> 取代 ImageParam )
Param
<> 依然存在, 但 Generators 現在能夠使用一個新的類別,
Input
<>, 來取代. 對於純量型別, 這些實質上能夠被認為等同於
Param
<>, 但因為一些後續將介紹的程式碼簡明緣故而有著不同的名字.
同樣地,
ImageParam
依然存在, 但 Generators 現在能夠使用一個
Input
<Func> 來取代. 這(實質上)類似一個
ImageParam
, 而有著最主要的差異是背後可能不(或者可能)再有著實際的 buffer, 因此沒有事先定義的延伸範圍.
Input<Func> input{"input", Float(32), 2};
類似於下列方式, 這等義於一個背後有著能被一個的
Input<Buffer
<
T
>
> 所建構的
實體 buffer 的
ImageParam
:
ImageParam input{UInt(8), 2, "input"};
轉變為:
Input
<Buffer
<
uint8_t
>
>
input{"input", 2};
這允許程式寫作者 (相較於使用
Input<Func>
) 透過 input.dim(0).extend() 以及
input.dim(1).extend() 來
取得 buffer 的寬與高.
同時宣告
Input
<>
與
Param
<>
或
ImageParam
(i.e.: 如果使用
Input
<>
可能無法使用先前的語法) 對於 Generator 是一種錯誤. 請注意
Input
<> 只能搭配 Generator 使用, 並且無法使用在其他 Halide 程式碼中; 除了在 Generator 中之外, 它並非打算來取代
Param
<>
範例:
class SumColumns : Generator<SumColumns> {
ImageParam input{Float(32), 2, "input"};
Func build() {
RDom r(0, input.width());
Func f;
Var y;
f(y) = 0.f;
f(y) += input(r.x, y);
return f;
}
};
轉變為
class SumColumns : Generator
<
SumColumns> {
Input<Func
>
input{"input", Float(32), 2};
Input
<
int32_t
>
width{"width"};
Func build() {
RDom r(0, width);
Func f;
Var y;
f(y) = 0.f;
f(y) += input(r.x, y);
return f;
}
};
能夠選擇性地不指定 Input<Func> 的型別與/或維度, 在這時數值直接由 Func 傳入的數值推斷. 當然, 若指定了明確的型別與維度, 將要求傳入的 Func 符合, 否則會產生編譯錯誤.
Input<Func> input{ "input", 3 }; // require 3-dimensional Func,
// but leave Type unspecified
當一個使用了 Input<ltFunc> 的 Generator 被直接編譯時 (e.g. 使用 GenGen), 而型別與(或)維度在程式碼中並沒有被指明, 此時 Input<ltFunc> 必須明確地指明. 能夠透過 GeneratorParams 搭配Input 或 Output 衍生出的名字來指定. (在上述的例子中, input 有著名為 "input.type" 的 GeneratorParam 與名為 "input.dim" 的 GeneratorParam)
明確宣告輸出
所有 Generator 的輸入可以藉由其成員來反推, 但是關於輸出的資訊在先前僅能藉由呼叫 build() 並確認回傳值來確定(可能是個 Func 或是一個 Pipeline)
而這次的改變, Generator 相對地能明確地宣告其輸出為一個成員變數, 並且提供 generator() 介面函式取代 build() (等同於 generate 沒有回傳任何數值)
範例:
class SumColumns : Generator<SumColumns> {
Input<Func> input{"input", Float(32), 2};
Input<int32_t> width{"width"};
Func build() {
RDom r(0, width);
Func f;
Var y;
f(y) = 0.f;
f(y) += input(r.x, y);
return f;
}
};
轉變為:
class SumColumns : Generator<SumColumns> {
Input<Func> input{"input", Float(32), 2};
Input<int32_t> width{"width"};
Output<Func> sum_cols{"sum_cols", Float(32), 1};
void generate() {
RDom r(0, width);
Var y;
sum_cols(y) = 0.f;
sum_cols(y) += input(r, y);
}
};
當搭配
Output<Func>, 能夠選擇性地
不指定 Output<Func> 的型別與/或維度; 當任何未被指明的型別必須透過
GeneratorParam 指定才能夠使用高階編譯.
請注意
Output<> 僅能搭配 Generator 使用, 並不能在其他 Halide 程式使用.
Generator 的底層實作將會驗證(在呼叫 generate() 之後)所有定義的輸出, 與定義是否符合宣告.
開發者能夠透過 Type 清單來指明一個回傳為 Tuple 的輸出:
class Tupler : Generator<Tupler> {
Input<Func> input{"input", Int(32), 2};
Output<Func> output{"output", {Float(32), UInt(8)}, 2};
void generate() {
Var x, y;
output(x, y) = Tuple(cast(input(x, y)), cast<uint8_t>(input(x, y)));
}
};
一個 Generator 能夠定義多個 Output (相當於悄悄地實作為一個 Pipeline):
class SumRowsAndColumns : Generator<SumRowsAndColumns> {
Input<Func> input{"input", Float(32), 2};
Input
<
int32_t
>
width{"width"};
Input
<
int32_t
>
height{"height"};
Output
<
Func
>
sum_rows{"sum_rows", Float(32), 1};
Output
<
Func
>
sum_cols{"sum_cols", Float(32), 1};
void generate() {
RDom rc(0, height);
Var x;
sum_rows(x) = 0.f;
sum_rows(x) += input(x, rc);
RDom rr(0, width);
Var y;
sum_cols(y) = 0.f;
sum_cols(y) += input(rr, y);
}
};
並且允許指定輸出為任意的純量型別(除了 Handle 型別外); 這僅只是在零維度 Func 上的語法糖果, 但相當的方便, 特別是用在多個輸出上:
class Sum : Generator<Sum
>
{
Input
<
Func> input{"input", Float(32), 2};
Input
<
int32_t
>
width{"width"};
Input
<
int32_t
>
height{"height"};
Output
<
Func
>
sum_rows{"sum_rows", Float(32), 1};
Output
<
Func
>
sum_cols{"sum_cols", Float(32), 1};
Output
<
float
>
sum{"sum"};
void generate() {
RDom rc(0, height);
Var x;
sum_rows(x) = 0.f;
sum_rows(x) += input(x, rc);
RDom rr(0, width);
Var y;
sum_cols(y) = 0.f;
sum_cols(y) += input(rr, y);
RDom r(0, width, 0, height);
sum() = 0.f;
sum() += input(r.x, r.y);
}
};
請注意同時定義 build() 與 generate() 對 Halide 是一個錯誤.
輸入與輸出陣列
藉由使用一個型別陣列來作為型別參數, 亦能夠透過使用新的語法來宣告一個輸入或輸出的陣列:
// Takes exactly 3 images and outputs exactly 3 sums.
class SumRowsAndColumns : Generator<SumRowsAndColumns> {
Input
<
Func[3]
>
inputs{"inputs", Float(32), 2};
Input
<
int32_t[2]
>
extents{"extents"};
Output
<
Func[3]
>
sums{"sums", Float(32), 1};
void generate() {
assert(inputs.size() == sums.size());
// assume all inputs are same extent
Expr width = extent[0];
Expr height = extent[1];
for (size_t i = 0; i < inputs.size(); ++i) {
RDom r(0, width, 0, height);
sums[i]() = 0.f;
sums[i]() += inputs[i](r.x, r.y);
}
}
};
陣列大小能夠不指定, 但有著注意事項:
- 對於 ahead-of-time 編譯, 輸入必須有著明確的大小, 編譯時藉由一個 GeneratorParam 帶入 (e.g., pyramid.size=3)
- 對於透過一個 Stub 的 JIT 編譯, 輸入陣列大小將自傳入的向量推斷.
- 對於 ahead-of-time 編譯, 輸出可能在編譯時藉由一個 GeneratorParam 指定了具體的大小 (e.g., pyramid.size=3), 或是能夠透過 resize 的方式指定大小.
class Pyramid : public Generator<Pyramid> {
public:
GeneratorParam
<
int32_t
>
levels{"levels", 10};
Input
<
Func
>
input{ "input", Float(32), 2 };
Output
<
Func[]
>
pyramid{ "pyramid", Float(32), 2 };
void generate() {
pyramid.resize(levels);
pyramid[0](x, y) = input(x, y);
for (int i = 1; i < pyramid.size(); i++) {
pyramid[i](x, y) = (pyramid[i-1](2*x, 2*y) +
pyramid[i-1](2*x+1, 2*y) +
pyramid[i-1](2*x, 2*y+1) +
pyramid[i-1](2*x+1, 2*y+1))/4;
}
}
};
一個未指定大小的 Input/Output 陣列必須在高階編譯時有著明確的大小;
現在有著
基於命名的 GeneratorParam
用來設定此數值
, (在上面的範例為"pyramid.size").
請注意 Input 與 Output 陣列都支援來自
std::vector<>
有限的介面
:
operator[]
size()
begin()
end()
-
resize()
(Output only)
自 Build() 中分開排程
一個 Generator 現在能夠將既存的
build()
分為兩個介面函式:
void generate() { ... }
void schedule() { ... }
如此一個
Generator
必須將所有對於中介 Func 排程的程式碼移到
schedule()
中
. 請注意這表示可排程的
Func
,
Var
, 等等將需要宣告為 Generator 的成員變數. (因為
Output<>
需要宣告為成員變數, 這相當簡單, 但對於中介需要排程
Func
可能需要相當變動.)
範例:
class Example : Generator<Example> {
Output<Func
>
output{"output", Float(32), 2};
void generate() {
Var x, y;
Func intermediate;
intermediate(x, y) = SomeExpr(x, y);
output(x, y) = intermediate(x, y);
intermediate.compute_at(output, y);
}
};
轉變為:
class Example : Generator<Example> {
Output<Func> output{"output", Float(32), 2};
void generate() {
intermediate(x, y) = SomeExpr(x, y);
output(x, y) = intermediate(x, y);
}
void schedule() {
intermediate.compute_at(output, y);
}
Func intermediate;
Var x, y;
};
請注意輸出的
Func
並不需要一個對於
compute_at()
或
store_at()
的排程指令: 要不就是潛在地
compute_root()
(當直接編譯為濾波器), 或是透過呼叫者來排程 (如後面將看到的, 當被用作為子項目元件).
即便中介的 Halide 程式碼並沒有必要的排程 (e.g.
全為 inline), 依然應該提供一個空的
schedule()
來使其明確與簡潔.
範例:
class ExampleInline : Generator<ExampleInline> {
Output
<
Func
>
output{"output", Float(32), 2};
void generate() {
Var x, y;
output(x, y) = SomeExpr(x, y);
}
};
轉變為
class ExampleInline : Generator<ExampleInline> {
Output<Func> output{"output", Float(32), 2};
void generate() {
output(x, y) = SomeExpr(x, y);
}
void schedule() {
// empty
}
Var x, y;
};
當需要時將 GeneratorParam 轉為 ScheduleParam
GeneratorParam
現在透過新的
ScheduleParam
型別來改進. 所有被 schedule() 所使用的
generator 參數必須宣告為
ScheduleParam
而非
GeneratorParam
. 這有著兩個目的:
- 允許在任意的 Generator 之間陳列與傳遞排程資訊的方式 (之後將會看到).
- 使得哪些 GeneratorParams 是作為排程用途, 這有助於未來的 Autoscheduler 運作.
請注意有些一般性的
GeneratorParam
約定早已如同
ScheduleParam
動作(特別是
vectorize
與
parallelize
); 這僅只是型式化先前的慣例.
GeneratorParam
與
ScheduleParam
將共存在單一個
namespace (i.e., 將
GeneratorParam
與
ScheduleParam
以相同命名宣告是個錯誤).
當一個
GeneratorParam
能用在 Generator 中任何地方使用 (無論
generate()
或者
schedule()
), 一個
ScheduleParam
僅能夠限縮在
schedule()
中存取. (在未來, 這將可能會是編譯時期的限制)
請注意當
GeneratorParam
必須能夠對字串雙向序列化 (如同 GeneratorParams), 一些
ScheduleParam
數值並非可序列化, 因為可能參照了執行時期的 Halide 結構 (特別是,
LoopLevel
, 並不能在一般情況下可靠地以命名指定). 嘗試從 GenGen 設定如此的
ScheduleParam
將產生編譯時期錯誤.
範例:
class Example : Generator<Example> {
GeneratorParam
<
int32_t
>
iters{"iters", 10};
GeneratorParam
<
bool
>
vectorize{"vectorize", true};
Func generate() {
Var x, y;
vector
<
Func
>
intermediates;
for (int i = 0; i < iters; ++i) {
Func g;
g(x, y) = (i == 0) ? SomeExpr(x, y) : SomeExpr2(g(x, y));
intermediates.push_back(g);
}
Func f;
f(x, y) = intermediates.back()(x, y);
// Schedule
for (auto fi : intermediates) {
fi.compute_at(f, y);
if (vectorize) fi.vectorize(x, natural_vector_size());
}
return f;
}
};
轉變為:
class Example : Generator<Example> {
GeneratorParam
<
int32_t
>
iters{"iters", 10};
ScheduleParam
<
bool
>
vectorize{"vectorize", true};
Output
<
Func
>
output{"output", Float(32), 2};
void generate() {
for (int i = 0; i < iters; ++i) {
Func g;
g(x, y) = (i == 0) ? SomeExpr(x, y) : SomeExpr2(g(x, y));
intermediates.push_back(g);
}
output(x, y) = intermediates.back()(x, y);
}
void schedule() {
for (auto fi : intermediates) {
fi.compute_at(output, y);
if (vectorize) fi.vectorize(x, natural_vector_size());
}
}
Var x, y;
vector<Func> intermediates;
};
請注意
ScheduleParam
也能夠有著其他有趣的數值, 特別是
LoopLevel
:
class Example : Generator<Example> {
// Specify a LoopLevel at which we want intermediate Func(s)
// to be computed and/or stored.
ScheduleParam
<
LoopLevel
>
intermediate_compute_level{"level", "undefined"};
ScheduleParam
<
LoopLevel
>
intermediate_store_level{"level", "root"};
Output
<
Func> output{"output", Float(32), 2};
void generate() {
intermediate(x, y) = SomeExpr(x, y);
output(x, y) = intermediate(x, y);
}
void schedule() {
intermediate
// If intermediate_compute_level is undefined,
// default to computing at output's rows
.compute_at(intermediate_compute_level.defined() ?
intermediate_compute_level :
LoopLevel(output, y))
.store_at(intermediate_store_level);
}
Func intermediate;
Var x, y;
};
請注意
ScheduleParam<LoopLevel>
能夠預設為 "root", "inline", 或
"undefined"; 所有其他的數值 (e.g. Func-and-Var) 必須在實際程式碼中指定. (LoopLevel(Func, Var) 明確地無法藉由像是 "func.var" 的命名來指定; 雖然 Halide 內部使用這慣例, 但目前無法保證在任意集合的 Generator 使用唯一的 Func 命名)
請注意使用一個未定義的 LoopLevel 作為排程是一個錯誤
修訂的 RegisterGenerator 語法
在以往, 必須註冊藉由在廣域下明確的實體化一個 RegisterGenerator 來註冊 Generator:
Halide::RegisterGenerator<MyGen> register_my_gen{"my_gen"};
這依然有效, 但導入了更簡單的註冊巨集:
HALIDE_REGISTER_GENERATOR(MyGen, my_gen) // no semicolon at end
若想要針對特定 Generator 產生一個 Stub, 必須使用這新的註冊巨集, 並加入該資訊到宣告之中:
// We must forward-declare the name we want for the stub,
// inside the proper namespace(s). None of the namespace(s)
// may be anonymous (if they are, failures will occur at Halide
// compilation time).
namespace SomeNamespace { class MyGenStub; }
HALIDE_REGISTER_GENERATOR(MyGen, "my_gen", SomeNamespace::MyGenStub)
若對於第3個參數的 stub 合格命名沒有被適當地宣告, 將產生編譯錯誤. 完全合格的命名必須至少有著一個 namespace (也就是, 一個全域命名是不會被接受的).
Generator Stubs
先以使用範例做開始, 接著再回頭解釋過程. 若有著一個想重複使用的 RGB-to-YCbCr 元件:
class RgbToYCbCr : public Generator<RgbToYCbCr> {
Input<Func> input{"input", Float(32), 3};
Output
<
Func
>
output{"output", Float(32), 3};
void generate() { ... conversion code here ... }
void schedule() { ... scheduling code here ... }
};
RegisterGenerator register_me{"rgb_to_ycbcr"};
GenGen 現在能夠產生一個類Func 的包覆一個 generator 的 stub 類別, (通常) 以一個副檔名 為 ".stub.h" 檔案發出. 看起來像是:
/path/to/rgb_to_rcbcr.stub.h:
// MACHINE-GENERATED
class RgbToYCbCr : public GeneratorStub {
struct Inputs {
// All the Input<>s declared in the Generator are listed here,
// as either Func or Expr
Func input;
};
struct GeneratorParams { ... };
struct ScheduleParams { ... };
// ctor, with required inputs, and (optional) GeneratorParams.
RgbToYCbCr(GeneratorContext* context,
const Inputs& inputs,
const GeneratorParams& = {}) { ... }
// Output(s)
Func output;
// Overloads for first output
operator Func() const { return output; }
Expr operator()(Expr x, Expr y, Expr z) const { return output(x, y, z); }
Expr operator()(std::vector<Expr> args) const { return output(args); }
Expr operator()(std::vector
<
Var
>
args) const { return output(args); }
void schedule(const ScheduleParams ¶ms = {});
};
請注意這只是一個 "header-only" 類別; 所有的函式都是 inline 方式(或是
template-multilinked, etc) 所以並沒有對應要合入的 .cpp . 並且注意到有一個 "by-value", 基於內部處理的類別, 如同多數其他在 Halide 中的類別(e.g.
Func
,
Expr
, etc).
接著像是如此般地使用:
#include "/path/to/rgb_to_rcbcr.stub.h"
class AwesomeFilter : public Generator<AwesomeFilter> {
public:
Input
<
Func
>
input{"input", Float(32), 3};
Output
<
Func
>
output{"output", Float(32), 3};
void generate() {
// Snap image into buckets while still in RGB.
quantized(x, y, c) = Quantize(input(x, y, c));
// Convert to YCbCr.
rgb_to_ycbcr = RgbToYCbCr(this, {quantized});
// Do something awesome with it. Note that rgb_to_ycbcr autoconverts to a Func.
output(x, y, c) = SomethingAwesome(rgb_to_ycbcr(x, y, c));
}
void schedule() {
// explicitly schedule the intermediate Funcs we used
// (including any reusable Generators).
quantized.
.vectorize(x, natural_vector_size<float
>
())
.compute_at(rgb_to_ycbcr, y);
rgb_to_ycbcr
.vectorize(x, natural_vector_size
<
float
>
())
.compute_at(output, y);
// *Also* call the schedule method for all reusable Generators we used,
// so that they can schedule their own intermediate results as needed.
// (Note that we may have to pass them appropriate values for ScheduleParam,
// which vary from Generator to Generator; since RgbToYCbCr has none,
// we don't need to pass any.)
rgb_to_ycbcr.schedule();
}
private:
Var x, y, c;
Func quantized;
RgbToYCbCr rgb_to_ycbcr;
Expr Quantize(Expr e) { ... }
Expr SomethingAwesome(Expr e) { ... }
};
值得一提的是所有對子元件的輸入必須在子元件建構時明確地提供 (以其 ctor 參數的方式); 呼叫者必須負責提供這些資訊 (並沒有自呼叫者到子元件自動傳遞的概念)
若
RgbToYCbCr
有著輸入或輸出陣列會如何? 像是:
class RgbToYCbCrMulti : public Generator<RgbToYCbCrMulti> {
Input
<
Func[3]
>
inputs{"inputs", Float(32), 3};
Input
<
float
>
coefficients{"coefficients", 1.f};
Output
<
Func[3]
>
outputs{"outputs", Float(32), 3};
...
};
在這例子中, 所產生的
RgbToYCbCrMulti
類別對於輸入需要 vector-of-Func (或
vector-of-Expr) , 並且提供 vector-of-Func 作為輸出成員:
class RgbToYCbCrMulti : public GeneratorStub {
struct Inputs {
std::vector<Func> inputs;
std::vector
<
Expr
>
coefficients;
};
RgbToYCbCr(GeneratorContext* context,
const Inputs& inputs,
const GeneratorParams& = {}}) { ... }
...
std::vector<Func
>
outputs;
};
若
RgbToYCbCr
有著多個輸出會如何? 像是:
class RgbToYCbCrMulti : public Generator<RgbToYCbCrMulti> {
Input
<
Func> input{"input", Float(32), 3};
Output
<
Func
>
output{"output", Float(32), 3};
Output
<
Func
>
mask{"mask", UInt(8), 2};
Output
<
float
>
score{"score"};
...
};
在這例子中, 所產生的
RgbToYCbCrMulti
類別將所有的輸出搭配對應宣告在 Generator 中的命名作為結構成員:
struct RgbToYCbCrMulti {
...
Func output;
Func mask;
Func score;
};
請注意純量輸出為了一致性依然以(零維度)函式方式表現. (並且注意到 "output" 並不是一個特別的命名; 只是發生在 Generator 第一個輸出的命名上)
亦請注意第一個輸出總是以 "是" 與 "有著" 的關係表示: RgbToYCbCrMulti 複載了必要的運算元, 因此使用其 "output" 就如同使用以一個 Func, 在此例中:
struct RgbToYCbCrMulti {
...
Func output;
operator Func() const { return output; }
Expr operator()(Expr x, Expr y, Expr z) const { return output(x, y, z); }
Expr operator()(std::vector<Expr> args) const { return output(args); }
Expr operator()(std::vector
<
Var
>
args) const { return output(args); }
...
};
這(的確)是多餘的, 但這是考量過的: 這讓大部分(單一輸出)的情況更為便利, 然而對多輸出的情況沒有影響.
consumer 可能如此般地使用:
#include "/path/to/rgb_to_rcbcr_multi.stub.h"
class AwesomeFilter : public Generator<AwesomeFilter> {
...
void generate() {
rgb_to_ycbcr_multi = RgbToYCbCrMulti(this, {input});
output(x, y, c) = SomethingAwesome(rgb_to_ycbcr_multi.output(x, y, c),
rgb_to_ycbcr_multi.mask(x, y),
rgb_to_ycbcr_multi.score());
}
void schedule() {
rgb_to_ycbcr_multi.output
.vectorize(x, natural_vector_size
<
float>())
.compute_at(output, y);
rgb_to_ycbcr_multi.mask
.vectorize(x, natural_vector_size
<
float
>
())
.compute_at(output, y);
rgb_to_ycbcr_multi.score
.compute_root();
// Don't forget to call the schedule() function.
rgb_to_ycbcr_multi.schedule();
}
};
當
RgbToYCbCr 之中
有著想要設定來做 code generation 的 GeneratorParam 會如何? 在這情況, 當呼叫建構子時將傳入一個數值到
一個
generator_params
的選擇性欄位.
class RgbToYCbCr : public Generator<RgbToYCbCr> {
GeneratorParam
<
Type
>
input_type{"input_type", UInt(8)};
GeneratorParam
<
bool
>
fast_but_less_accurate{"fast_but_less_accurate", false};
...
};
這可能會產生一個
的
不一樣定義的
GeneratorParams, 其
有著每個
GeneratorParam 欄位並以適當預設值作初始
:
struct GeneratorParams {
Halide::Type input_type{UInt(8)};
bool fast_but_less_accurate{false};
};
接著可能手動地填入:
class AwesomeFilter : public Generator<AwesomeFilter> {
void generate() {
...
GeneratorParams generator_params;
generator_params.input_type = Float(32);
generator_params.fast_but_less_accurate = true;
rgb_to_ycbcr = RgbToYCbCr(this, input, generator_params);
...
}
}
或者, 在 C++ 編譯時期得知其型別時, 能更精練地使用一個範本化建構子(templated constructor):
class AwesomeFilter : public Generator<AwesomeFilter> {
void generate() {
...
rgb_to_ycbcr = RgbToYCbCr::make<float, true>(this, input);
...
}
}
當
RgbToYCbCr 中有著
ScheduleParam
會如何?
class RgbToYCbCr : public Generator<RgbToYCbCr> {
ScheduleParam
<
LoopLevel
>
level{"level"};
ScheduleParam
<
bool
>
vectorize{"vectorize"};
void generate() {
intermediate(x, y) = SomeExpr(x, y);
output(x, y) = intermediate(x, y);
}
void schedule() {
intermediate.compute_at(level);
if (vectorize) intermediate.vectorize(x, natural_vector_width<float
>
());
}
Var x, y;
Func intermediate;
};
在這情形中, 產生的 stub 程式碼對於
ScheduleParams
將有著一個不一樣的宣告
:
struct ScheduleParams {
LoopLevel level{"undefined"};
bool vectorize{false};
};
並且可能如此地呼叫:
class AwesomeFilter : public Generator<AwesomeFilter> {
...
void schedule() {
rgb_to_ycbcr
.vectorize(x, natural_vector_size<float>())
.compute_at(output, y);
rgb_to_ycbcr.schedule({
// We want any intermediate products also at compute_at(output, y)
LoopLevel(output, y),
// vectorization: yes please
true
});
}
...
}