yohhoyの日記

技術的メモをしていきたい日記

OpenMP 5.0仕様リリース

2018年11月 OpenMP 5.0仕様リリース記事 OpenMP 5.0 Is A Major Leap Forward より抄訳。

OpenMP仕様バージョン5.0はOpenMP ARB、主要なコンピュータハードウェア/ソフトウェアベンダのグループ、そしてOpenMPコミュニティのユーザによって共同開発されました。いくつかの小さな改善に加えて、改訂仕様は下記の主要な機能追加を含みます:

  • アクセラレータデバイスのフルサポート。ホストシステムとコプロセッサバイス間の統合共有メモリ(unified shared memory)を必要とする機構、デバイス固有の機能実装を利用する能力、暗黙的データマッピングのより良い制御、実行時にデバイスオフロードを優先する能力を含むことで、OpenMPアクセラレータデバイスを完全にサポートします。さらに、オフロードの反転、暗黙の関数生成、オブジェクト指向データ構造を簡単にコピーする能力もサポートします。
  • デバッグ改善とパフォーマンス解析。新しい2種類のツールインタフェースにより、直感的なデバッグと深いパフォーマンス解析をサポートするサードパーティ製ツール開発が可能となります。
  • 最新のC, C++, Fortranバージョンサポート。いまやOpenMPFortran 2008、C11、C++17の重要な機能をサポートします。
  • 完全に記述的なloopコンストラクトのサポート。loopコンストラクトは、特定の処理系を強制することなく、コンパイラによるループ最適化を促します。このコンストラクトはコンパイラに対して、他のOpenMPディレクティブよりも特定ターゲット向けに良い実装を選択する自由度を与えます。*1
  • マルチレベル・メモリシステム。帯域幅メモリのように、データを異なる種類のメモリ上に配置するメモリ確保機構を利用できます。新しいOpenMP機能によって、NUMA的なモダンHPCシステムの扱いが容易になります。*2
  • ポータビリティの改善。declare variantディレクティブや新しいメタ・ディレクティブにより、プログラマコンパイル時にOpenMPプラグマとユーザコードを合わせることで、パフォーマンス・ポータビリティの向上が可能になります。*3

関連URL

*1:loopコンストラクト:OpenMP5.0で追加された汎用ループ並列化指示構文。

*2:ヘテロジニアス・メニーコアシステム向けのメモリ確保・解放関数が追加される。OpenMP標準規格では5種類のメモリ空間(デフォルト/大容量/定数プール/広帯域幅/低遅延)が事前定義される。

*3:特定のOpenMPコンテキスト(CPU/GPU/FPGAの別/アーキテクチャ/ベンダ情報など)向けに、実装を切り替える機能。

新機能は属性 or 構文 or 関数?

プログラミング言語C++標準化プロセスにおける、新機能の対応方針についての考え方。2018年Jacksonville会合におけるEWG(Evolution Working Group)ガイダンス。

  • 構文上はオブジェクト(object)が対象のように見えるとしても、値(value)に関する属性を導入すべきでない。
  • コンパイラマジック関数で実現可能な機能について、コア言語機能(≒新しい構文)追加を行うべきでない。

C++2a(C++20)標準ライブラリに導入される、アドレス値アラインメント情報をコンパイラに与えるstd::assume_aligned関数の場合、当初提案*1では属性(attribute)とされていたが同ガイダンスに従い関数へと変更された。提案文書P1007R2より一部引用(下線部は強調)。

A previous paper [P0886R0] proposed to add this functionality to C++ through a new standard attribute, [[assume_aligned(N)]]. The guidance given by EWG in Jacksonville (2018) was that having this functionality in C++ is desirable, but not as an attribute. We should not introduce an attribute that appertains to values, even though syntactically it appears to appertain to objects. We also should not add a core language feature if this can be done in the library via a "magic" function.

P1007R2 std::assume_aligned, 4 Previous work and committee guidance

クラステンプレートの非テンプレートメンバ関数でSFINAE

クラステンプレートのテンプレートパラメータに基づき、非テンプレートなメンバ関数オーバーロードでSFINAEを実現する方法。

template<typename T, typename U>
struct X {
  // テンプレートパラメータT, Uによるメンバ関数オーバーロード
  void mf(const T&) { ... }  // A)
  void mf(const U&) { ... }  // B)
};

// NG: メンバ関数A), B)が同一シグネチャとなる
X<int, int> x;

SFINAEは関数テンプレートパラメータ推論時にのみ機能する。下記コードB)は非テンプレートメンバ関数のためill-formed。

template<typename T, typename U>
struct X {
  void mf(const T&) { ... }  // A)

  // 戻り値型でSFINAEするつもりが...
  std::enable_if_t<!std::is_same_v<T, U>>
  mf(const U&) { ... }  // B)
  // NG: "メンバ関数戻り値型が存在しない"状態となりコンパイルエラー
};

X<int, int> x;

強引にSFINAEを有効化するため、B)をメンバ関数テンプレートとして定義する。パターン1では非型テンプレートパラメータAlwaysTruestd::enable_if条件式に含めることで、パターン2ではクラステンプレートパラメータT, Uメンバ関数テンプレートパラメータT1, U1のデフォルト値とすることで、メンバ関数オーバーロード解決時にSFINAEを引き起こす。

template<typename T, typename U>
struct X {
  void mf(const T&) { ... }  // A)

  // メンバ関数テンプレートとして定義する
#if パターン1
  template<bool AlwaysTrue = true>
  std::enable_if_t<!std::is_same_v<T, U> && AlwaysTrue>
  mf(const U&) { ... }  // B) T==Uのときは定義されない
#elif パターン2
  template<
    typename T1 = T, typename U1 = U,
    typename = std::enable_if_t<!std::is_same_v<T1, U1>>
  >
  void mf(const U&) { ... }  // B) T1==U1のときは定義されない
#endif
};

X<int, int> x;
x.mf(42);  // OK: A)を呼び出す

おまけ:C++2a(C++20) Conceptsで導入されるrequires節を利用すると、SFINAEに頼らない関数オーバーロード選択が可能となる。(→id:yohhoy:20170904

// C++2a
template<typename T, typename U>
struct X {
  void mf(const T&) { ... }  // A)

  void mf(const U&)
    requires !std::is_same_v<T, U>
  { ... }  // B) T==Uのときは定義されない
};

X<int, int> x;
x.mf(42);  // OK: A)を呼び出す

関連URL

プロセス終了コードの有効範囲

プロセス終了コードの有効範囲についてメモ。

  • POSIX: 0〜255。整数値のうち下位8ビットのみ有効。(例: 値257は終了コード1と解釈される)
  • Windows: 0〜4294967295。32ビット整数値。

POSIX規格(IEEE Std 1003.1-2008, 2016 Ed.)より一部引用。

If the new state is terminated:

  • The low-order 8 bits of the status argument that the process passed to _Exit(), _exit(), or exit(), or the low-order 8 bits of the value the process returned from main()
    Note that these 8 bits are part of the complete value that is used to set the si_status member of the siginfo_t structure provided by waitid()
  • Whether the process terminated due to the receipt of a signal that was not caught and, if so, the number of the signal that caused the termination of the process
2.13. Status Information

関連URL

randの既定シード値は1

C標準ライブラリ提供の擬似乱数生成関数randでは、既定のシード値は 1 と定義される。

#include <stdlib.h>

int main()
{
  // 暗黙にsrand(1)相当でシード値を設定
  int x = rand();
}

C11 7.22.2.2/p2より引用(下線部は強調)。

The srand function uses the argument as a seed for a new sequence of pseudo-random numbers to be returned by subsequent calls to rand. If srand is then called with the same seed value, the sequence of pseudo-random numbers shall be repeated. If rand is called before any calls to srand have been made, the same sequence shall be generated as when srand is first called with a seed value of 1.

関連URL

アトミック、ときどきロックフリー

C/C++標準ライブラリ提供のアトミック変数atomic<T>, atomic_Tでは、ロックフリー(lock-free)性判定マクロATOMIC_*_LOCK_FREE*1 が3状態 Never/Sometimes/Always を取りうる。

C++11策定当時の提案文書N2427によれば、“ロックリーに振る舞う可能性あり”(ATOMIC_*_LOCK_FREE == 1)選択肢の提供根拠は下記の通り:

  • 動的リンクライブラリのバージョンアップなどで、将来的にはロックフリーに振る舞う可能性を考慮する。
  • やむを得ない “不適切なアライメントをもつアトミック変数” の存在を許容する。
    • 型レベルでのロックフリー保証を諦めることで、適正アライメントのアトミック変数のみを考慮とした効率的な実装が可能となる。

Lock-Free Property

The proposal provides run-time lock-free query functions rather than compile-time constants because subsequent implementations of a platform may upgrade locking operations with lock-free operations, so it is common for systems to abstract such facilities behind dynamic libraries, and we wish to leave that possibility open. Furthermore, we recommend that implementations without hardware atomic support use that technique.

The proposal provides lock-free query functions on individual objects rather than whole types to permit unavoidably misaligned atomic variables without penalizing the performance of aligned atomic variables.

Because consistent use of operations requires that all operations on a type must use the same protocol, all operations are lock-free or none of them are. That is, the lock-free property applies to whole objects, not individual operations.

N2427 C++ Atomic Types and Operations

C++標準規格では、ロックフリー実行(lock-free executions)が定義されている。C++17 4.7.2/2より引用。

Executions of atomic functions that are either defined to be lock-free (32.8) or indicated as lock-free (32.5) are lock-free executions.

  • If there is only one thread that is not blocked (3.6) in a standard library function, a lock-free execution in that thread shall complete. [Note: Concurrently executing threads may prevent progress of a lock-free execution. For example, this situation can occur with load-locked store-conditional implementations. This property is sometimes termed obstruction-free. -- end note]
  • When one or more lock-free executions run concurrently, at least one should complete. [Note: It is diffcult for some implementations to provide absolute guarantees to this effect, since repeated and particularly inopportune interference from other threads may prevent forward progress, e.g., by repeatedly stealing a cache line for unrelated purposes between load-locked and store-conditional instructions. Implementations should ensure that such effects cannot indefinitely delay progress under expected operating conditions, and that such anomalies can therefore safely be ignored by programmers. Outside this document, this property is sometimes termed lock-free. -- end note]

関連URL

*1:*=BOOL, CHAR, CHAR16_T, CHAR32_T, WCHAR_T, INT, LONG, LLONG, POINTERのいずれか。例:atomic<int>, atomic_int 型にはマクロ ATOMIC_INT_LOCK_FREE が対応。

hardware_{destructive,constructive}_interference_size

C++17で標準ライブラリ <new> ヘッダに追加された hardware_destructive_interference_size, hardware_constructive_interference_size について。

hardware_destructive_interference_size
False-Sharing発生を防ぐために必要となる、最小のメモリアドレス距離。2つの変数を異なるキャッシュラインに載せるためのアライメント情報として用いる。
hardware_constructive_interference_size
同一キャッシュラインに載りうる(True-Sharing)、最大のオブジェクトサイズ。複数変数アクセスの時間的局所性が高いときに、アドレス配置の確認情報として用いる。

対象プロセッサのL1キャッシュラインサイズ(相当)を、コンパイル時定数として取得可能。具体的な値は処理系定義(implementation-defined)とされる。通常、両定数値は同一となる。例:x86アーキテクチャでは値64。WebAssemblyなどコンパイル時に対象アーキテクチャが定まらない場合を想定し、保守的に振舞えるよう目的別に分離定義される。

C++17 21.6.5よりExampleコード片を引用(一部改変)。

#include <new>

// hardware_destructive_interference_size利用例
struct keep_apart {
  alignas(hardware_destructive_interference_size) atomic<int> cat;
  alignas(hardware_destructive_interference_size) atomic<int> dog;
};

// hardware_constructive_interference_size利用例
struct together {
  atomic<int> dog;
  int puppy;
};
struct kennel {
  // Other data members...
  alignas(sizeof(together)) together pack;
  // Other data members...
};
static_assert(sizeof(together) <= hardware_constructive_interference_size);

2018年8月現在、MSVC 14.11(VisualStudio 2017 15.3)*1+/std:c++17オプション でのみ実装を確認*2GCC(trunk)/libstdc++*3, Clang(trunk)/libc++*4は未対応*5

おまけ:システムプログラミング言語であるRustにも同様の定数値が提案されている。

どうでもいいメモ:C++17現在、hardware_constructive_interference_sizeatomic_compare_exchange_strong_explicit と並んでC++標準ライブラリ内で最も長い識別子名となっている。39文字。

関連URL