この記事は、https://www.oneapi.io/spec/ で 2023年9月14日に公開された『oneAPI 1.3 Provisional Specification Rev. 1』 (HTML、PDF) をベースにしています。原文は2000 ページ近くあり、翻訳の時間とリソースも限られるため、全文翻訳ではなく、記事形式で区切った仕様とその解説を提供することにしました。
この回では、『oneAPI 1.3 Provisional Specification Rev. 1』の「oneDAL」の「Array」の節を取り上げています。
配列
配列は oneDAL のデータにおける単純な概念です。次のようなストレージを表します。
- 内部に割り当てられたデータ、または外部データへの参照を保持します。データは、1 つの同種および連続したメモリーブロックとして構成されます。
- メモリーブロックのサイズ情報が含まれます。
- 不変または可変データを表現します。
- データの状態を不変から可変に変更する機能を提供します。
- データの所有権情報を保持します (「データ所有権の要件」の節を参照)。
- データの所有権情報は複数の配列で共有できます。データをコピーすることなく、別の配列から新しい配列を作成できます。
使用例
次のリストは、配列 API の簡単な例と、基本的な使用シナリオを示します。
#include <CL/sycl.hpp> #include <iostream> #include <string> #include "oneapi/dal/array.hpp" using namespace oneapi; void print_property(const std::string& description, const auto& property) { std::cout << description << ": " << property << std::endl; } int main() { sycl::queue queue { sycl::default_selector() }; constexpr std::int64_t data_count = 4; const float data[] = { 1.0f, 2.0f, 3.0f, 4.0f }; // ユーザー定義の不変メモリーから配列を作成 auto arr_data = dal::array<float>::wrap(data, data_count); // 内部に割り当てられたメモリーから配列を作成 auto arr_ones = dal::array<float>::full(queue, data_count, 1.0f); print_property("Is arr_data mutable", arr_data.has_mutable_data()); // false print_property("Is arr_ones mutable", arr_ones.has_mutable_data()); // true // データをコピーせずに arr_data から新しい配列を作成 (所有権情報を共有) dal::array<float> arr_mdata = arr_data; print_property("arr_mdata elements count", arr_mdata.get_count()); // equal to data_count print_property("Is arr_mdata mutable", arr_mdata.has_mutable_data()); // false /// arr_mdata 内のデータを新しい可変メモリーブロックにコピー /// arr_data は、元のデータポインターを参照します arr_mdata.need_mutable_data(queue); print_property("Is arr_data mutable", arr_data.has_mutable_data()); // false print_property("Is arr_mdata mutable", arr_mdata.has_mutable_data()); // true queue.submit([&](sycl::handler& cgh){ auto mdata = arr_mdata.get_mutable_data(); auto cones = arr_ones.get_data(); cgh.parallel_for<class array_addition>(sycl::range<1>(data_count), [=](sycl::id<1> idx) { mdata[idx[0]] += cones[idx[0]]; }); }).wait(); std::cout << "arr_mdata values: "; for(std::int64_t i = 0; i < arr_mdata.get_count(); i++) { std::cout << arr_mdata[i] << ", "; } std::cout << std::endl; return 0; }
データ所有権の要件
配列は、メモリーブロックの管理に関して次の要件を満たす必要があります
配列には以下が保持されます。
- サイズ
count
の不変データブロックへのポインター。 - サイズ
count
の可変データブロックへのポインター。
- サイズ
配列が可変データを表現する場合、両方のプロパティーが同じメモリーブロックを指している必要があります。
配列が不変データを表現する場合、可変データブロックへのポインターは
nullptr
でなければなりません。配列は、共有所有権セマンティクスによって、保存されたデータブロックの有効期間を管理します。
- 複数の配列オブジェクトが同じデータブロックを所有することがあります。
- 次のいずれかの状況で、配列は所有権を放棄します。
- データブロックを所有する配列が破棄されたとき
- データブロックを所有する配列に、
operator=
またはreset()
を使用して別のメモリーブロックが割り当てられたとき
- 所有権を解放する配列がデータブロックを所有する最後のオブジェクトである場合、所有権を解放するとデータブロックの割り当てが解除されます。
- データブロックの割り当ては、構築時に配列に提供されるデリーター・オブジェクトを使用して解除されます。デリーター・オブジェクトが提供されていない場合、配列は内部メモリー割り当てメカニズムに対応するデフォルトの割り当て解除関数を呼び出します。
データブロックを管理するポインターが、
reset()
によって別のポインターに置き換えられると、ポインターを監視する配列はその所有権を放棄し、別のポインター指すデータブロックの有効期間の管理を始めます。need_mutable_data() によって配列の状態が不変から可変に変更されると、不変データブロックの所有権が解放され、可変データブロックの有効期間の管理が開始されます。
配列オブジェクトはデータを所有しない場合があります。このような配列はゼロサイズと呼ばれます。
- ゼロサイズの不変および可変データへのポインターは nullptr でなければなりません。
- データブロックのサイズカウントは 0 になります。
実装の注意点
一般的な配列の実装は次のように構成されます。
配列クラスには次のメンバー関数があります。
- 不変データブロックへのポインター。
- 可変データブロックへのポインター。
- 共有所有権のセマンティクスを実装する所有権構造体へのポインター。
- データブロックのサイズカウント。
所有権構造体は、以下を格納するオブジェクトです。
- 不変または可変データブロックへのポインター。
- デリーター・オブジェクト。
- 参照カウント (関連するデータブロックを所有する配列オブジェクトの数)。
配列がポインター
p
とデリーターd
によって表わされるデータブロックの有効期間の管理を開始すると、所有権構造体オブジェクトを作成し、p
とd
で初期化します。所有権構造体の参照カウントは 1 になります。配列オブジェクトが所有権を放棄すると、所有権構造体の参照カウントがデクリメントされます。
- カウントがゼロになると、所有権構造体はメモリーブロックの割り当てを解除し、配列は所有権構造体を破棄します。
- カウントがゼロより大きい限り、所有権構造体は破棄されません。
配列オブジェクトのコピーが作成されると、所有権構造体の参照カウントはインクリメントされ、同じ所有権構造体へのポインターが作成されたコピーに割り当てられます。配列クラスのほかのメンバー変数は、そのままコピーされます。
注: 配列の要件を満たす限り、任意の実装方法を選択できます。
プログラミング・インターフェイス
この節のすべてのタイプと関数は、oneapi::dal
名前空間で宣言され、oneapi/dal/array.hpp
ヘッダーファイルをインクルードして使用できます。
すべての array
クラスメソッドは、いくつかのグループに分類できます。
- 外部、可変、不変メモリーから配列を作成する際に使用されるコンストラクター。
- データを別の配列と共有する配列を作成する際に使用されるコンストラクターと代入演算子。
- 配列を別の外部メモリーブロックに再割り当てする際に使用される
reset()
メソッドのグループ。 - 内部的に割り当てられたメモリーブロックを再割り当てする際に使用される
reset()
メソッドのグループ。 - データへのアクセスに使用されるメソッド。
- 外部メモリー、または新しいオブジェクト内に配列を割り当てることで、配列を作成する簡略された方法を提供する静的メソッド。
template <typename Data> class array { public: using data_t = Data; static array<Data> empty(const sycl::queue& queue, std::int64_t count, const sycl::usm::alloc& alloc = sycl::usm::alloc::shared); template <typename Element> static array<Data> full(sycl::queue& queue, std::int64_t count, Element&& element, const sycl::usm::alloc& alloc = sycl::usm::alloc::shared); static array<Data> zeros(sycl::queue& queue, std::int64_t count, const sycl::usm::alloc& alloc = sycl::usm::alloc::shared); template <typename ExtData> static array<Data> wrap(ExtData* data, std::int64_t count, const std::vector<sycl::event>& dependencies = {}); array(); array(const array<Data>& other); array(array<Data>&& other); template <typename ExtData, typename Deleter> explicit array(const sycl::queue& queue, ExtData* data, std::int64_t count, Deleter&& deleter, const std::vector<sycl::event>& dependencies = {}); template <typename RefData, typename ExtData> explicit array(const array<RefData>& ref, ExtData* data, std::int64_t count); array<Data> operator=(const array<Data>& other); array<Data> operator=(array<Data>&& other); const Data* get_data() const noexcept; bool has_mutable_data() const noexcept; Data* get_mutable_data() const; array& need_mutable_data(sycl::queue& queue, const sycl::usm::alloc& alloc = sycl::usm::alloc::shared); std::int64_t get_count() const noexcept; std::int64_t get_size() const noexcept; const Data& operator[](std::int64_t index) const noexcept; void reset(); void reset(const sycl::queue& queue, std::int64_t count, const sycl::usm::alloc& alloc = sycl::usm::alloc::shared); template <typename ExtData, typename Deleter> void reset(ExtData* data, std::int64_t count, Deleter&& deleter, const std::vector<sycl::event>& dependencies = {}); template <typename RefData, typename ExtData> void reset(const array<RefData>& ref, ExtData* data, std::int64_t count); };
テンプレート、パブリックメソッド、コンストラクターについては、こちら (英語) を参照してください。
法務上の注意書き
The content of this oneAPI Specification is licensed under the Creative Commons Attribution 4.0 International License (英語). Unless stated otherwise, the sample code examples in this document are released to you under the MIT license (英語).
This specification is a continuation of Intel’s decades-long history of working with standards groups and industry/academia initiatives such as The Khronos Group*, to create and define specifications in an open and fair process to achieve interoperability and interchangeability. oneAPI is intended to be an open specification and we encourage you to help us make it better. Your feedback is optional, but to enable Intel to incorporate any feedback you may provide to this specification, and to further upstream your feedback to other standards bodies, including The Khronos Group SYCL* specification, please submit your feedback under the terms and conditions below. Any contribution of your feedback to the oneAPI Specification does not prohibit you from also contributing your feedback directly to other standard bodies, including The Khronos Group under their respective submission policies.
By opening an issue, providing feedback, or otherwise contributing to the specification, you agree that Intel will be free to use, disclose, reproduce, modify, license, or otherwise distribute your feedback at its sole discretion without any obligations or restrictions of any kind, including without limitation, intellectual property rights or licensing obligations.
This document contains information on products, services and/or processes in development. All information provided here is subject to change without notice.
© Intel Corporation. Intel、インテル、Intel ロゴ、その他のインテルの名称やロゴは、Intel Corporation またはその子会社の商標です。
* その他の社名、製品名などは、一般に各社の表示、商標または登録商標です。