トップ回答者
動的配列をrestrict(amp)制限下のクラスメンバとして使用する方法

質問
-
C++ AMPの質問です。
動的配列をクラスメンバ―として使用したいのですが、ポインタはAMPの制限上使用することができません。
目的の達成の為に以下の4つのケースを考えたのですが、ケース1以外はコンパイルが通らず、またケース1も静的にサイズを決定する必要があるため実際的ではありません。
//case1 class hoge{ public: int arr[10]; //ok } //case2 class hoge{ public: int *arr; //error! pointer is not allowed. } //case3 class hoge{ public: hoge(int size):arr(size){} concurrency::array<int> arr; //error! concurrecy::array can't use as class member. } //case4 class hoge{ public: hoge(int* ptr,int size):arr(size,ptr){} concurrency::array_view<int> arr; //error! concurrency::array_view can't use as class member too. } ////// //case1,2 hoge arr[10]; //case3 hoge arr[10]={1,2,3...}; //case4 int *a(new int[10]),*b(new int[10]),*c(new int[10]).... hoge arr[10]={ {a,10}, {b,10}, {c,10}... } concurrency::array<hoge> view(10,arr); //compile succeed only case1
どこかで確保した配列をconcurrency::array_viewで参照するcase4,配列をGPUメモリに確保してしまうcase3が動けば文句はないのですが、どうやら不可能みたいで困っています。
なにか解決策はないでしょうか。
回答
-
結局何のためにジャグ配列(?)のようなデータ構造を使用したいのかいまいち分かりませんでしたが、GPU側に直接ジャグ配列を作るのではなく、単にCPU側で可変個のViewを一元管理して、カーネルコードではいくつかの配列のみView経由で参照したいだけなのであれば、下記のような方法もあります。
#include <amp.h> #include <amp_graphics.h> #include <vector> #include <cstdio> #include <cassert> using namespace concurrency; using namespace concurrency::graphics; using MyFloat2ArrayView = array_view<float_2>; int main() { const int height = 3; const int widthArray[height] = { 16, 4, 8 }; std::vector<std::vector<float_2>> hostArrayArray(height); for (int y = 0; y < height; ++y) { const int width = widthArray[y]; hostArrayArray[y].resize(width); for (int x = 0; x < width; ++x) { hostArrayArray[y][x] = float_2(float(x), float(y)); } } std::vector<std::unique_ptr<MyFloat2ArrayView>> viewArray(height); for (int y = 0; y < height; ++y) { viewArray[y] = std::make_unique<MyFloat2ArrayView>(hostArrayArray[y]); } for (int y = 0; y < height; ++y) { auto& view = *viewArray[y]; parallel_for_each(view.extent, [=](index<1> idx) restrict(amp) { view[idx] *= 2.0f; }); } for (int y = 0; y < height; ++y) { viewArray[y]->synchronize(); const int width = widthArray[y]; assert(width == hostArrayArray[y].size()); for (int x = 0; x < width; ++x) { const auto val = hostArrayArray[y][x]; printf("[%02d, %02d] = (%4.1f, %4.1f)\n", x, y, val.x, val.y); } } return 0; }
もしジャグ配列中の参照したい第1ランクのインデックスが、カーネル外ではなくカーネル内で決まるような場合は、上記の方法では対処できず、やはり矩形配列を使う必要があります。また、カーネルの1ディスパッチ中において同時に使用できるSRVやUAVのスロット数には制約があるので、I/Oアクセスの効率性だけでなくスケーラビリティという観点からも矩形配列のほうが有利です。
ID3D11DeviceContext::CSSetShaderResources method (Windows)
ID3D11DeviceContext::CSSetUnorderedAccessViews method (Windows)
Direct3D feature levels (Windows)
いずれにせよ、C++ AMPに対して過剰な幻想を抱く前に、まずローレベルAPIを通じてGPUのハードウェア特性や制約、GPGPUの原理的・本質的な部分に関して学習を進めてから、そのうえで高レベルAPIに手を出すようにしたほうがよいと思われます。C++ AMPはGPGPUのローレベルAPIを使って開発した経験のあるプログラマーがデータ並列プログラミングの開発効率を上げるために用いる道具であり、ある程度バックエンド側の実装を予想できる事前知識がないと厳しいのではないでしょうか。
すべての返信
-
質問をするときは最低限のマナーとして、ちゃんと環境情報(OS、IDE、コンパイラーのバージョンなど)を詳しく書くようにしてください。C++ AMPも、初期版であるVS2012実装と、VS2013以降の実装(v1.2)とで仕様が異なります。
array_viewがrestrict(amp)制限下のクラスで使えないわけではありません。array_viewはもともとC++ AMPにおいてポインターのような役割を果たします。Visual C++実装(DirectCompute実装)ではShader Resource View (SRV) やUnordered Access View (UAV) で実現されています。
using namespace concurrency; void DoMyAmpClassTest(array_view<float>& src, array_view<float>& dst, int length) { parallel_for_each( dst.extent, [=](index<1> idx) restrict(amp) { //dst[idx[0]] = 2.0f * src[idx[0]]; class MyAmpClass { public: MyAmpClass(array_view<float> ary, int length) restrict(amp) : m_array(ary) , m_length(length) {} array_view<float> m_array; int m_length; }; MyAmpClass srcWrapper(src, length); MyAmpClass dstWrapper(dst, length); dstWrapper.m_array[idx[0]] = 2.0f * srcWrapper.m_array[idx[0]]; }); }
restrict(amp)制限下のコードはVC++実装の場合、いったんDirect3Dシェーディング言語であるHLSLにコンバートされてからGPU向けの中間コードにコンパイルされる仕組みになっているため、HLSLの制約下でしかコードを書くことができません。ポインターの直接利用ではなくViewを介する必要があるのも、バックエンドの抽象化のためです。
- 編集済み sygh 2016年1月6日 17:45
-
回答ありがとうございます。
環境については、 Win8,VS2015及びUbuntu/kalmar6.0の両方で動作するのが望ましいです。
array_viewが使えないというのは、array_viewをメンバに含むクラスをarray_viewのテンプレート型に適用できないという意味です。(恐らくはcase2と同じ類のエラー)
つまるところarray_viewのテンプレート型引数に渡されるクラス内で宣言される配列を、動的に確保する方法がないか知りたのですが、concurrency::arrayがクラスメンバにできない以上、難しいだろうとは推測しています。
-
>array_viewが使えないというのは、array_viewをメンバに含むクラスをarray_viewのテンプレート型に適用できないという意味です。
>つまるところarray_viewのテンプレート型引数に渡されるクラス内で宣言される配列を、動的に確保する方法がないか知りたのですが、
要するにジャグ配列を作りたいということですか? であれば不可能です。下記の「解説」にも明記されてあるように、矩形配列のみがサポートされます。
array<T,N> 型は、アクセラレータや CPU などの特定の位置にある、密で正規の (ジャグ配列でない) N 次元の配列を表します。配列の要素のデータ型は T で、ターゲット アクセラレータと互換性のある型である必要があります。
また、CUDA/OpenCL/DirectComputeのようにローレベルのGPGPU APIを使ったことがあれば分かると思いますが、C++ AMPにおいてもコンテナの要素型として利用できるのはプリミティブな値型(組み込み型あるいは組み込み型を組み合わせた集成体)のみです。現行のGPUプログラミングはCPUプログラミングほど柔軟ではありませんし、そもそもデータ密度や局所性に劣るジャグ配列はGPUには不向きです。
次善の譲歩案としては、第2ランクにおける各配列の最大要素数Mをあらかじめ決めておいて、
array<float, 2> dataArray(N, M);
array<int, 1> lengthArray(N);
のようなデータ用の矩形配列と各配列のアクティブ要素数を管理する配列を定義し、その範囲内でやりくりする、といった方法が考えられます。
現行のC++ AMPはCPUの論理やデータ型をGPUにそのまま持ち込めるような魔法の杖ではなく、バックエンドの抽象化やカーネルコードのインライン記述を可能にするといった機能しか持っていません。C++ AMPをやる前に、まず一度CUDA/OpenCL/DirectComputeを経験してGPGPUの現実と向き合っておくと、C++ AMPでは何ができて何ができないのかを見極めることが容易になると思います。
- 編集済み sygh 2016年1月7日 13:48
-
結局何のためにジャグ配列(?)のようなデータ構造を使用したいのかいまいち分かりませんでしたが、GPU側に直接ジャグ配列を作るのではなく、単にCPU側で可変個のViewを一元管理して、カーネルコードではいくつかの配列のみView経由で参照したいだけなのであれば、下記のような方法もあります。
#include <amp.h> #include <amp_graphics.h> #include <vector> #include <cstdio> #include <cassert> using namespace concurrency; using namespace concurrency::graphics; using MyFloat2ArrayView = array_view<float_2>; int main() { const int height = 3; const int widthArray[height] = { 16, 4, 8 }; std::vector<std::vector<float_2>> hostArrayArray(height); for (int y = 0; y < height; ++y) { const int width = widthArray[y]; hostArrayArray[y].resize(width); for (int x = 0; x < width; ++x) { hostArrayArray[y][x] = float_2(float(x), float(y)); } } std::vector<std::unique_ptr<MyFloat2ArrayView>> viewArray(height); for (int y = 0; y < height; ++y) { viewArray[y] = std::make_unique<MyFloat2ArrayView>(hostArrayArray[y]); } for (int y = 0; y < height; ++y) { auto& view = *viewArray[y]; parallel_for_each(view.extent, [=](index<1> idx) restrict(amp) { view[idx] *= 2.0f; }); } for (int y = 0; y < height; ++y) { viewArray[y]->synchronize(); const int width = widthArray[y]; assert(width == hostArrayArray[y].size()); for (int x = 0; x < width; ++x) { const auto val = hostArrayArray[y][x]; printf("[%02d, %02d] = (%4.1f, %4.1f)\n", x, y, val.x, val.y); } } return 0; }
もしジャグ配列中の参照したい第1ランクのインデックスが、カーネル外ではなくカーネル内で決まるような場合は、上記の方法では対処できず、やはり矩形配列を使う必要があります。また、カーネルの1ディスパッチ中において同時に使用できるSRVやUAVのスロット数には制約があるので、I/Oアクセスの効率性だけでなくスケーラビリティという観点からも矩形配列のほうが有利です。
ID3D11DeviceContext::CSSetShaderResources method (Windows)
ID3D11DeviceContext::CSSetUnorderedAccessViews method (Windows)
Direct3D feature levels (Windows)
いずれにせよ、C++ AMPに対して過剰な幻想を抱く前に、まずローレベルAPIを通じてGPUのハードウェア特性や制約、GPGPUの原理的・本質的な部分に関して学習を進めてから、そのうえで高レベルAPIに手を出すようにしたほうがよいと思われます。C++ AMPはGPGPUのローレベルAPIを使って開発した経験のあるプログラマーがデータ並列プログラミングの開発効率を上げるために用いる道具であり、ある程度バックエンド側の実装を予想できる事前知識がないと厳しいのではないでしょうか。