プラグマ

プリプロセッサディレクティブは、コンパイラと相互作用するための方法である。コンパイラに特別な情報を提供したり、コンパイラから情報を取得したりするために使用される。テンプレート以外のコードでも有用だが、pragma(msg)はテンプレートのデバッグ時に役立つ。

各コンパイラベンダーは、以下の必須のpragmaディレクティブに加えて、独自の ディレクティブを自由に導入することができる。

pragma(msg)

コンパイル時にstderrにメッセージを表示する。コンパイルされたプログラムの実行時にはメッセージは表示されない。

例えば、テンプレートパラメータの型を、おそらくデバッグ中に公開するために、次のpragma(msg)が使用されている。

import std.string;

void func(A, B)(A a, B b) {
    pragma(msg, format("Called with types '%s' and '%s'",
                       A.stringof, B.stringof));
    // ...
}

void main() {
    func(42, 1.5);
    func("hello", 'a');
}
型'int'および'double'で呼び出された場合
型'string'および'char'で呼び出された場合
pragma(lib)

コンパイラに、プログラムを特定のライブラリにリンクするよう指示する。これは、システムにすでにインストールされているライブラリにプログラムをリンクする最も簡単な方法だ。

例えば、次のプログラムは、コマンドラインでライブラリを指定しなくても、curlライブラリにリンクされる。

import std.stdio;
import std.net.curl;

pragma(lib, "curl");

void main() {
    // この章を取得する
    writeln(get("ddili.org/ders/d.en/pragma.html"));
}
pragma(inline)

関数をインライン化すべきかどうかを指定する。

すべての関数呼び出しには、ある程度のパフォーマンスコストがかかる。関数呼び出しには、関数への引数の渡し、呼び出し元への戻り値の返送、および関数の呼び出し元を記憶するためのいくつかの管理情報の処理が含まれる。これにより、関数の呼び出し元が関数の呼び出し元を記憶し、関数の呼び出し元が関数の呼び出し元を記憶することで、関数の呼び出し元が関数の呼び出し元を記憶することで、関数の呼び出し元が関数の呼び出し

このコストは、通常、呼び出し元および呼び出し先が実行する実際の作業コストに比べれば、ごくわずかである。しかし、場合によっては、特定の関数を呼び出すという行為自体が、プログラムのパフォーマンスに測定可能な影響を与えることがある。これは、関数本体が比較的高速で、何度も繰り返される短いループから呼び出される場合に特に発生しやすい。

次のプログラムは、ループから小さな関数を呼び出し、戻り値が条件を満たす場合にカウンタを加算する。

import std.stdio;
import std.datetime.stopwatch;

// ボディが短い関数:
ubyte compute(ubyte i) {
    return cast(ubyte)(i * 42);
}

void main() {
    size_t counter = 0;

    StopWatch sw;
    sw.start();

    // 何度も繰り返す短いループ:
    foreach (i; 0 .. 100_000_000) {
        const number = cast(ubyte)i;

        if (compute(number) == number) {
            ++counter;
        }
    }

    sw.stop();

    writefln("%s milliseconds", sw.peek.total!"msecs");
}

このコードは、std.datetime.stopwatch.StopWatchを使用して、ループ全体の実行時間を測定している:

674ミリ秒

-inlineコンパイラスイッチは、関数インライン化と呼ばれるコンパイラ最適化を実行するようにコンパイラに指示する。

dmd deneme.d -w -inline
Bash

関数がインライン化されると、その本体は呼び出された場所にコードに挿入され、実際の関数呼び出しは発生しない。以下は、インライン化後にコンパイラがコンパイルする同等のコードだ。

// compute()がインライン化されている場合のループと同等:
foreach (i; 0 .. 100_000_000) {
    const number = cast(ubyte)i;

    const result = cast(ubyte)(number * 42);
    if (result == number) {
        ++counter;
    }
}
D

このプログラムをテストしたプラットフォームでは、関数呼び出しを削除することで実行時間が約 40% 短縮された。

407ミリ秒

関数のインライン化は大きなメリットがあるように見えるが、すべての関数呼び出しに適用できるわけではない。そうすると、インライン化された関数の本体がコードが大きくなりすぎて、CPUの命令キャッシュに収まらなくなるからだ。その結果、コードの速度が低下してしまう可能性がある。そのため、どの関数呼び出しをインライン化するかは、通常、コンパイラに任されている。

ただし、コンパイラがこの決定を支援する方が有益な場合もある。inlineプリプロセッサディレクティブは、コンパイラのインライン化決定に指示を出し:

これらのプラグマは、それらが記述されている関数に影響を与えるだけでなく、スコープやコロンと組み合わせて複数の関数に影響を与えることもできる。

pragma(inline, false) {
    // このスコープで定義された関数はインライン化すべきではない
    // ...
}

int foo() {
    pragma(inline, true);  // この関数はインライン化すべきだ
    // ...
}

pragma(inline, true):
// このセクションで定義された関数はインライン化すべきだ
// ...

pragma(inline):
// このセクションで定義された関数は、-inlineコンパイラスイッチに応じて、
// インライン化すべきか、インライン化すべきでないか決まる
// ...
D

プログラムの実行速度を向上させる別のコンパイラスイッチとして、 がある。これはコンパイラに最適化アルゴリズムをさらに実行するように指示する。ただし、これらのアルゴリズムは多くの時間を要するため、プログラムの実行速度向上はコンパイル速度の低下を伴う。-O

pragma(startaddress)

プログラムの開始アドレスを指定する。開始アドレスは通常、Dランタイム環境によって割り当てられるため、このプリミティブを使用する可能性はほとんどない。

pragma(mangle)

シンボルを、デフォルトのネームマングリング方法とは異なる方法でネームマングリングすることを指定する。ネームマングリングとは、リンカが関数とその呼び出し元を識別する方法のことだ。このプラグマは、DコードがDのキーワードであるライブラリ関数を呼び出す必要がある場合に役立つ。

例えば、Cライブラリにoverrideという関数がある場合、overrideはDのキーワードであるため、Dからこの関数を呼び出すには別の名前を使用する必要がある。ただし、リンカが関数を識別できるように、その別の名前もライブラリ内の実際の関数名としてマングリングする必要がある。

/* Cライブラリに'override'という名前の関数があった場合、
 * Dからその関数は'c_override'という名前で、
 * 実際の関数名としてマングリングされて呼び出すことができる: */
pragma(mangle, "override")
extern(C) string c_override(string);

void main() {
    /* Dコードでは、この関数はc_override()として呼び出されるが、
     * リンカは、正しいCライブラリ名
     * 'override'でその関数を見つける: */
    auto s = c_override("hello");
}