オフィスアワーがそろそろ始まるよ!()

インライン化

Zenにはループをコンパイル時に明示的にインライン展開する機能があります。ここではinline while / inline forについて説明します。

ループのインライン展開

ループのインライン展開 (ループ展開、もしくは、ループアンローリング) とは、プログラムサイズを犠牲に、プログラムの実行速度を向上する最適化手法です。各ループごとに発生するループのオーバーヘッド (ループ終了条件のテスト) を減少させることで、ループの実行速度を向上させます。

例えば次のwhileループでは、1ループごとに、ループ終了条件のテスト (i < 3) が行われます。

    var result: u32 = 0;
    var i: u32 = 0;
    while (i < 3) : (i += 1) {
        result += i;
    }

このwhileループをインライン展開すると次のコードになります。このように、ループ条件終了のテスト (i < 3) の実行をなくすことができます。

    var result: u32 = 0;
    var i: u32 = 0;
    // whileループ内の処理を展開
    result += i;
    i += 1;
    result += i;
    i += 1;
    result += i;
    i += 1;

通常のwhileforではこのようなループのインライン展開は行われません。

ノート: 実際には、Zenのコンパイラ (およびLLVM) のリリースモード最適化は極めて優秀なので、この程度のループであれば定数への畳込みが行われます。

inline while / inline for

inline while / inline forを使うことで、プログラマはコンパイラに対して明示的にループのインライン展開を指示できます。

通常のwhile / forと異なり、inline while / inline forループの制御がコンパイル時計算可能でなければなりません。

上述の例をinline whileを使ってインライン展開するコードは、以下の通りです。ループの制御変数icomptime varとし、コンパイル時計算可能な変数にしている部分が重要です。

examples/ch11-advanced/inlining/src/inlining.zen:4:11

test "inline while" {
    var result: u32 = 0;
    comptime var i: u32 = 0;
    inline while ( i < 3 ) : (i += 1) {
        result += i;
    }
    ok(result == 3);
}

上のコードは、下のようなコードに展開されます。

    var result: u32 = 0;
    // whileループ内の処理を展開
    result += 0;
    result += 1;
    result += 2;

もしアセンブリに馴染みがあるようでしたら、ぜひコンパイルして確認してみて下さい。ただし、リリースモードでビルドを行うと最適化により、定数に畳み込まれるため、デバッグモードでビルドして下さい。

次に、inline forを使用したコードの例を示します。ループ制御はコンパイル時計算可能であれば良いため、を使うことも可能です。

test "inline for" {
    var result: usize = 0;
    inline for ([_]type{ f16, f32, f64, f128 }) |T| {
        result += @sizeOf(T);
    }
    ok(result == 30);
}

このような利用方法は、例えば、ジェネリックなライブラリを作成した場合のテストケース作成に役立ちます。

組込み関数との組み合わせ

組込み関数と組み合わせてメタ情報を取得することで、次のような応用も可能です。

lscatgrepというコマンドがあり、文字列を入力として受け取り、列挙型として返したいとします。その場合は、次のように書くことができます。

const Commands = enum {
    ls,
    cat,
    grep,
};

fn stringMatch(cmd: []u8) !Commands {
    const info = @typeInfo(Commands);
    inline for (info.Enum.fields) |field| {
        if (std.mem.equal(u8, cmd, field.name)) {
            return @field(Commands, field.name);
        }
    }
    return error.NotFound;
}

test "string match" {
    const cmd = try stringMatch(&"cat");
    testing.equal(Commands.cat, cmd);
}

少し解説すると、@typeInfoCommand列挙型のメタ情報を得ます。このメタ情報には、フィールドの配列が含まれます(info.Enum.fields)。これをinline forで展開しています。

フィールド名から列挙型の値を取得するには、@fieldに列挙型とフィールド名を渡します。

☰ 人の生きた証は永遠に残るよう ☰
Copyright © 2018-2019 connectFree Corporation. All rights reserved.
Zen, the Zen three-circles logo and The Zen Programming Language are trademarks of connectFree corporation in Japan and other countries.