C言語/unsequenced
unsequenced属性
[編集]C23標準で新たに導入された関数型属性は、コンパイラに関数の特性に関する追加情報を提供し、最適化の機会を広げるための機能です。これらの属性は、関数呼び出しの実行順序を緩和できる場合に、コンパイラがより効率的なコードを生成することを可能にします。
関数型属性の基本概念
[編集]C23では、関数型に適用できる2つの標準属性が導入されました:
[[unsequenced]](非順序付け)[[reproducible]](再現可能)
これらの属性は関数の振る舞いに関して特定の保証を提供します。特に、関数が副作用を持たない、あるいは外部状態への依存がないことを示します。
構文と使用法
[編集]// 関数宣言に属性を適用 double calculate(double x) [[unsequenced]]; // 関数ポインタに属性を適用 double (*func_ptr)(double) [[unsequenced]]; // 型指定子に属性を適用 typedef double func_t(double) [[reproducible]];
これらの属性は関数宣言子(パラメータリストの閉じ括弧の後)または関数型を持つ型指定子に適用されます。
関数型属性の特性
[編集]C23標準では、関数の振る舞いを特徴づける複数の特性が定義されています:
stateless(状態を持たない)
[編集]関数内や呼び出される関数内で定義される静的ストレージ期間または thread-local ストレージ期間を持つオブジェクトが const であり volatile でない場合、その関数は「stateless」と呼ばれます。
effectless(副作用がない)
[編集]関数呼び出し中にオブジェクトへの格納操作が、呼び出しと同期するオブジェクトの変更のみである場合、その関数呼び出しの評価は「effectless」です。さらに、その操作が観察可能である場合は、関数の一意のポインタパラメータ P が存在し、そのオブジェクトへのアクセスは P に基づく左辺値に対してのみ行われる必要があります。
idempotent(冪等性)
[編集]評価 E の二回目の実行が、結果の値や実行の観察可能な状態を変更せずに元の評価の直後に順序付けられる場合、その評価 E は「idempotent」です。
independent(独立性)
[編集]関数ポインタ値 f が、呼び出されるオブジェクト X について、同じプログラム実行中の f へのすべての呼び出しが同じ値を観察する場合、その関数ポインタ値は「independent」です。
unsequenced属性とreproducible属性
[編集]これらの特性に基づいて、C23では次の2つの標準属性が定義されています:
[[unsequenced]]
[編集]関数が「stateless」、「effectless」、「idempotent」、および「independent」である場合、その関数は「unsequenced」です。この属性は、関数呼び出しがその入力が利用可能になり次第実行され、その結果が使用されるまで遅延される可能性があることを示します。
[[reproducible]]
[編集]関数が「effectless」かつ「idempotent」である場合、その関数は「reproducible」です。この属性は、関数が呼び出されるたびに同じ入力に対して同じ結果を生成し、観察可能な副作用がないことを示します。
実用例
[編集]例1: unsequenced関数
[編集]// 符号付き文字を受け取り、bool値を返す関数 bool tendency(signed char) [[unsequenced]]; // この関数は、抽象マシンの変更可能な状態に依存しません // 同じ引数値での2回の呼び出しは同じ戻り値を生成します // コンパイラは、この関数の呼び出しを結果が必要になる前に順序なしで実行できます
この例では、tendency関数は引数の値のみに依存し、グローバル状態に依存しないことが示されています。同じ引数値での複数回の呼び出しは同じ結果を生成するため、コンパイラは結果をキャッシュしたり、共通部分式を削除したりする最適化を適用できます。
例2: reproducible関数
[編集]// 文字列のハッシュ値を計算する関数 size_t hash(char const[static 32]) [[reproducible]]; // この関数の連続した2回の呼び出しは、同じ戻り値を生成します // 呼び出し中に抽象状態の変更は可能ですが、観察可能ではありません // 他の副作用は発生しません
この例では、hash関数は同じ入力に対して常に同じ出力を生成することが保証されています。内部的には、例えば以前の計算結果をキャッシュするための静的変数を使用するかもしれませんが、それらは外部から観察できない方法で使用されます。
例3: 数学関数のローカルな制約
[編集]#include <math.h> #include <fenv.h> inline double distance(double const x[static 2]) [[reproducible]] { #pragma FP_CONTRACT OFF #pragma FENV_ROUND FE_TONEAREST // sqrtが無効な引数で呼び出されないこと、 // 結果が引数の値のみに依存することを表明 extern double sqrt(double) [[unsequenced]]; return sqrt(x[0]*x[0] + x[1]*x[1]); }
この例では、通常は「unsequenced」ではない標準ライブラリ関数 sqrt に制約を加えています。浮動小数点環境を固定し、負の引数が渡されないことを保証することで、この特定のコンテキストでは sqrt を「unsequenced」として扱えることを示しています。
最適化の機会
[編集]unsequencedとreproducible属性は、コンパイラに次のような最適化の機会を提供します:
グローバル変数の再読み込み削減
[編集]モデルベースのシステムでは、ブロック間のデータフローにグローバル変数を使用することがあります。関数が[[unsequenced]]として宣言されている場合、コンパイラはそのグローバル変数が関数によって変更されないことを前提にできるため、関数呼び出し間で値を再読み込みする必要がなくなります。
関数呼び出しのマージ
[編集]if ((cos(angle1) > cos(angle2)) || (cos(angle1) > cos(angle3))) { // 処理 }
この例では、cos関数が4回呼び出されますが、angle1に対する呼び出しは2回です。cosが[[unsequenced]]として宣言されていれば、コンパイラは最初と3番目の呼び出しをマージして、最初の呼び出しの結果を再利用できます。
既存の実装との関係
[編集]C23のunsequenced属性は、以前からいくつかのコンパイラで提供されていた「const」「pure」機能を標準化したものです。主要なコンパイラでの同等機能は以下の通りです:
| コンパイラ | 機能 | C23相当 |
|---|---|---|
| GCC | attribute((const))
|
[[unsequenced]]
|
| GCC | attribute((pure))
|
[[reproducible]]
|
| Clang | GCCと同じ | |
| WindRiver Diab | #pragma pure_function
|
[[unsequenced]]
|
| WindRiver Diab | #pragma nosideeffects
|
[[reproducible]]
|
| GreenHills MULTI | GCCと同じ |
C23の標準属性は、これらの既存の実装よりも柔軟性があります。特に、GCCのconst属性はポインタパラメータを使用する関数に対しては禁止されていますが、C23の[[unsequenced]]属性はそのような制限がなく、より広範囲の関数に適用できます。
注意点
[編集]- これらの属性は、関数がそれらの特性を持っていることの「表明」であり、コンパイラは必ずしもその正しさを検証するわけではありません。
- 属性は関数型の一部ではなく、プロトタイプの一部ではないため、これらの属性を省略する再宣言や変換は有効で互換性のある型を構成します。
- 宣言された属性を持たない関数定義がその属性を持つ関数宣言や関数ポインタからアクセスされると、動作は未定義となります。
結論
[編集]C23で導入された[[unsequenced]]と[[reproducible]]属性は、コンパイラに関数の特性に関する重要な情報を提供し、特に関数呼び出しの順序付けの緩和に関連する最適化の機会を広げます。これらの属性は、既存のコンパイラで広くサポートされている機能を標準化し、移植性を向上させると同時に、C言語で書かれたシステムのパフォーマンスを向上させる可能性を持っています。