【Unity】【シェーダ】シェーダで扱う関数のインライン化とそのメリット・デメリット

シェーダで扱う関数のインライン化とそのメリット・デメリットについてまとめました。

はじめに

他の人が書いたシェーダを見ていると関数にinlineが付いているのをよく見かけます。

inline int example(int x)
{
    return x + x;
}

これをインライン関数と呼びますが、これはどういう意味で何のために行うのだろうという疑問が生まれたので調べました。

インライン化のメリット

さてまず以下のようなソースコードを考えます。

int func2(int x)
{
    return x + x;
}

int func()
{
    int example = 2;
    example = func2(example);
    return example;
}

これをコンパイルすると、「funcの途中でfunc2の処理に飛ぶ」という処理が記述された機械語になります。
要するにソースコードに記述したそのままに処理が実行されます。

そして次にこれをインライン関数にしたソースコードについて考えます。

// インライン関数
inline int func2(int x)
{
    return x + x;
}

int func()
{
    int example = 2;
    example = func2(example);
    return example;
}

関数func2にinlineを付けただけです。
これをコンパイルすると、以下の処理をコンパイルしたときと同様の結果が得られます。
(厳密にまったく同じなのかは自信ありません。イメージです。)

int func()
{
    int example = 2;
    example = example + example;
    return example;
}

つまり、関数2の中身を関数1の中にベタ書きしたものと同様の結果が得られます。
これにより関数呼び出しのオーバーヘッドがなくなり、処理が効率化されます。

関数呼び出しのオーバーヘッド?

では関数呼び出しのオーバーヘッドとは具体的にどういうことでしょうか。
下記の記事の「スタック」の節が参考になりそうです。

qiita.com

ここに次のような記述があります。

関数が呼ばれるたびにスタックに引数や、戻り先アドレスなどがどんどんとpushされていきます。 そして関数のリターン時に、スタックに積まれた情報を元に処理する位置を移動する、という処理を行います。

関数化すると、引数や戻り先をスタックに積む処理を行う分だけ処理が増えるようです。
(他にもあるのかもしれませんが・・)

インライン化するとこれらの処理をやらなくてよくなる分、処理効率が向上します。

インライン化のデメリット

インライン化はうまく使えば上記のような恩恵が得られますが、デメリットもあります。

例えば、上述のfunc2にとても重い処理が記述されている状態で、funcにfunc2をいっぱい記述してみます。

inline int func2(int x)
{
    // ここに
    // 記述量の多い処理が
    // 複数行にわたって
    // いっぱい
    // 書かれているとする
    return x + x;
}

int func()
{
    int example = 2;
    example = func2(example);
    example = func2(example);
    example = func2(example);
    example = func2(example);
    example = func2(example);
    example = func2(example);
    example = func2(example);
    return example;
}

この状態でコンパイルすると、funcの中にfunc2の内容が何度も繰り返して記述されるため、
コンパイル後の情報量がとても多くなってしまいます。

このようにインライン化は小さな処理単位には向いていますが、大きい処理には不向きです。

大きい処理はインライン化されない?

下記の記事を見ると、C++では大きい処理はそもそもインライン化されないようです。

yttm-work.jp

C++の記事なのでUnityのシェーダにも当てはまるかはわかりませんが、
このような処理を行うコンパイラもあるようです。

参考

wisdom.sakura.ne.jp

yttm-work.jp

qiita.com