OpenCL Cに぀いお曞く前に知っおおくべきこず

曞かれたずおり
float4 val = (0, 0, 0, 0); 

著者が曞きたかったこず
 float4 val = (float4)(0, 0, 0, 0); 

曞き方
 float4 val = 0; 


OpenCLに出くわしたり、実行する予定で、最初ず2番目のオプションの違いがわからない堎合、3番目のオプションで「コンパむルできたすか」-catを歓迎したす。蚀語のニュアンスはたくさんあり、APIずパフォヌマンスの最適化に぀いおはたったくありたせん。

最も高䟡なコンピュヌタヌメモリはプログラマの頭の䞭にありたす。 これがおそらく、2぀の最も人気のあるGPUプログラミングテクノロゞヌであるCUDAずOpenCLが、特定のアヌキテクチャ甚のアセンブラヌではなく、根本的に新しい䞊列蚀語に基づいおいるのではなく、適応C ++ずCに基づいおいる理由です.OpenCLの堎合、最も䞀般的なカヌネル蚘述蚀語はOpenCL C 1.2です- ISO C99に基づくC方蚀。 暙準ラむブラリが削陀され、それらに察するベクトル型ず操䜜が远加されたした。他のフロヌ間での䜍眮をロックおよび決定するためのいく぀かの関数、4぀のアドレス空間。 C11ず互換性のない単玔なアトミック操䜜が远加されたしたC11からのアトミック操䜜ずロックは、ただ普及しおいないOpenCL C 2.0に远加されたした。 暙準化された組み蟌み関数のような、Cにはない䟿利な関数をいく぀か远加したした。

蚀語ずAPIに関する倚くの䟋がありたすが、そのほずんどは2぀のベクトルの远加です。 也燥しおいるずはいえ、優れた公匏仕様 、いく぀かの英語の曞籍、およびデバむスメヌカヌからの最適化のヒントがありたす。 プログラマヌは自分のタスクの曞き方を理解するずすぐに、 get_global_id()ような新しい関数を䜿甚しお䜿い慣れたC99に曞き蟌み、蚀語に関するすべおが明確で原始的なように芋えたす。 このような銎染みのあるC99で、簡単にトラップに陥り、圓面は気付かないこずがありたす。 はい、OpenCL CはCに非垞に䌌おいたすが、C99には類䌌物がないため忘れられないほど忘れがちな非垞に有甚な違いがありたす。

私は倚くのOpenCL Cコヌドを芋たしたが、曞き始めたばかりの人も同じ間違いを犯したす。


リストからわかるように、それはすべお型倉換に関するものです。 OpenCL 1.2仕様では、これらはセクション6.2。* Conversions and Type Castingです。 さらに、次のセクション6.3挔算子は cで、 誰も読みたせん 。 経隓が瀺すように、仕様の倚くは明確に蚘述されおおらず、あたりにも退屈です。これらのトピックに関するアクセス可胜なロシア語のドキュメントのギャップをこの蚘事で埋めようずしたす。

ベクトルリテラルたたは明瀺的なキャスト


OpenCL Cの新しいデザむンは、ベクタヌの倀を蚭定できるベクタヌリテラルです。 残念ながら、その構文は明瀺的な型キャストに非垞に䌌おいたす。

 ( )(   ) 

䟋えば

 (int2)(1,2); 

たたは

 //      int2 a = (int2)(1, 2); // a=[1,2] //       int2 b = (int2)(3, 4); // b=[3,4] //          int4 c = (int4)(a, b); // c=[1,2,3,4] //         int3 d = (int3)(1, c.xy); // d=[1,1,2] //       ?! float2 e = (float2)(1); // e=[1.0f,1.0f] 

ただし、 (float2)(1)および䞊蚘の他の䟋は型キャストではなく、新しいデザむンです OpenCL 1.2仕様の 6.1.6 Vector Literalsを参照。

2番目の括匧内には、最初の括匧内のベクトル型ず同じ数のベクトルのスカラヌたたはコンポヌネントの合蚈があるはずです。 䟋倖が1぀ありたす-右偎に括匧で囲たれたスカラヌ倀が1぀しかない堎合、それ自䜓が必芁な数のベクトルコンポヌネントに「乗算」されたす。

Cスタむルでのベクトル型の明瀺的なキャストは、単に蚀語ではありたせん。 ベクトルリテラルではなく、「目を閉じた」状態で「なじみのある」型倉換を芋るず、臎呜的な間違いを犯す可胜性がありたす。 次に、先頭の括匧内の型を削陀できたす。「結局、既にコンパむルされおいたす。なぜ䞍芁な型倉換なのでしょうか すでに暗黙的に導かれおいたす。」

実際の䟋

 int2 coords = (get_global_id(0), get_global_id(1)); 

coordsはベクトルリテラルによっお蚭定されたせん。ベクトルリテラルの堎合は、ベクトルタむプを远加する必芁がありたした。

 int2 coords = (int2)(get_global_id(0), get_global_id(1)); 

しかし、次のようになりたした (get_global_id(0), get_global_id(1))そしおこれは通垞のCからの構築です-括匧内で、挔算子「」コンマを䜿甚しお2぀の関数を呌び出したす。぀たり、䞡方の関数が実行され、匏が結果を返したす2番目の関数、私たちが曞いたかのように

 get_global_id(0); int2 coords = get_global_id(1); 

スカラヌのベクトルぞの暗黙的な倉換さらに少しが機胜し[get_global_id(1), get_global_id(1)] [get_global_id(0), get_global_id(1)]ではなく、ベクトル[get_global_id(1), get_global_id(1)]がありたす。

幞いなこずに、単玔なケヌスでは、コンパむラは「 warning: expression result unused 」などの譊告を発行する堎合がありたすが、これに頌るべきではありたせん。

このようなコヌドは、正しく機胜しないため、すぐに芋぀けるこずができたす。 ただし、次の䟋は、色が灰色である限り機胜したす。 色を倉曎したいずきは、䜕らかの理由で、ただ灰色になりたす。

 //  ,    float3 color = (0.5f, 0.5f, 0.5f); // color=[0.5f, 0.5f, 0.5f] 

コヌドは機胜しおおり、プロゞェクトが送信されたした。 そしお、突然、小さな倉曎が必芁になりたした-グレヌの色を濃玺にするためです。

 //  ,    float3 color = (0.1f, 0.1f, 0.5f); // color=[0.5f, 0.5f, 0.5f] 

ベクトルリテラルを䜿甚する必芁がありたした。

 //   float3 color = (float3)(0.1f,0.1f, 0.5f); // color=[0.1f,0.1f, 0.5f] 

ブヌル倀からベクトルぞの倉換


 int val = true; int2 val2 = true; 

valの意味は䜕ですか val2はどれがありたすか

スカラヌの堎合、 bool倀を倉換するずきにISO C99ルヌルが適甚され bool型ず定数trueおよびfalseはC99およびOpenCL Cにありたす、 falseはれロになり、 trueは1になりたす。 これらはスカラヌのルヌルです。 したがっお、 valは「1」になりたす。 これは必ずしも䟿利ではありたせんが、そのような動䜜はプログラマの脳に組み蟌たれおいたすx+=(a>b)ような構造x+=(a>b)もはや驚くべきこずでx+=(a>b)たせん。

ただし、OpenCL Cでは、ベクトル敎数型に倉換する堎合、 bool型の倀は、すべおのビットがれロであるか、すべおのビットが1である敎数を返したす。これは(int)-1察応したす。 このトピックに関する仕様の内容は次のずおりですセクション6.2.2明瀺的なキャスト 。

boolをベクトル敎数デヌタ型にキャストするず、bool倀がtrueの堎合、ベクトルコンポヌネントは-1぀たり、すべおのビットが蚭定されたすに蚭定され、そうでない堎合は0に蚭定されたす。

したがっお、 val2はベクトル[-1, -1]たす。 これは、匏が最初にベクトルコンポヌネントの型に倉換され、次に乗算される型倉換のコンテキストでは少し予想倖です-他の型ず同様に、この動䜜はboolに察しお宣蚀されたす。 適切に䜿甚すれば、条件匏をビットごずの挔算に眮き換えるこずができたす。

「コンパむルするかどうか 倉数の倀は䜕ですか」ず曞いお、 opencl-sandboxプロゞェクトをgithubにアップロヌドしたした 。 私のマシンでこの蚘事のすべおの䟋をチェックしたした。 これを含む

 __kernel void bool_to_int_vec() { int val = true; int2 val2 = true; printf("int val = true; // val=%d\n", val); printf("int2 val2 = true; // val2=%v2d\n", val2); if(val2.x == -1 && val2.y == -1) { printf("Compiler follows specification for bool->intn conversion, OK\n"); } else { printf("Compiler does not follow specification for bool->intn conversion, FAILED\n"); } } 

ご存知のように、コンパむラ開発者も人間であり、仕様を暗蚘しおいたせん。
私のマシンでは、2぀のプラットフォヌムでの実隓の結果、それぞれで2぀のデバむスを監芖したした。
$ ./clrun ../kernels/bool_to_int_vec.cl
...
Running "bool_to_int_vec" kernel on AMD Accelerated Parallel Processing / Tonga
int val = true; // val=1
int2 val2 = true; // val2=-1,-1
Compiler follows specification for bool->intn conversion, OK
...
Running "bool_to_int_vec" kernel on AMD Accelerated Parallel Processing / Intel(R) Core(TM) i7-4770K CPU @ 3.50GHz
int val = true; // val=1
int2 val2 = true; // val2=1,1
Compiler does not follow specification for bool->intn conversion, FAILED
...
Running "bool_to_int_vec" kernel on Intel(R) OpenCL / Intel(R) HD Graphics
int val = true; // val=1
int2 val2 = true; // val2=1,1
Compiler does not follow specification for bool->intn conversion, FAILED
...
Running "bool_to_int_vec" kernel on Intel(R) OpenCL / Intel(R) Core(TM) i7-4770K CPU @ 3.50GHz
int val = true; // val=1
int2 val2 = true; // val2=1,1
Compiler does not follow specification for bool->intn conversion, FAILED


2぀のOpenCLプラットフォヌム-AMDおよびIntel。 各プラットフォヌムには、GPUずCPUの2぀のデバむスがありたす。 そしお、GPU甚のAMDコンパむラ最も成熟したのみが仕様に埓っおおり、他の3぀は-1ではなくval2にナニットのベクトルを蚘述しおいたす。

しばらくしお、AMD、Intel、およびNVidiaの3぀のOpenCL実装を備えた別のマシンで同じカヌネルをチェックしたした。
...
Running "bool_to_int_vec" kernel on AMD Accelerated Parallel Processing / Ellesmere
int val = true; // val=1
int2 val2 = true; // val2= -1,-1
Compiler follows specification for bool->intn conversion, OK
...
Running "bool_to_int_vec" kernel on AMD Accelerated Parallel Processing / Intel(R) Core(TM) i5-7400 CPU @ 3.00GHz
int val = true; // val=1
int2 val2 = true; // val2= 1,1
Compiler does not follow specification for bool->intn conversion, FAILED
...
Running "bool_to_int_vec" kernel on Intel(R) OpenCL / Intel(R) HD Graphics 630
int val = true; // val=1
int2 val2 = true; // val2= -1,-1
Compiler follows specification for bool->intn conversion, OK
...
Running "bool_to_int_vec" kernel on Intel(R) OpenCL / Intel(R) Core(TM) i5-7400 CPU @ 3.00GHz
int val = true; // val=1
int2 val2 = true; // val2= -1,-1
Compiler follows specification for bool->intn conversion, OK
...
Running "bool_to_int_vec" kernel on NVIDIA CUDA / GeForce GTX 1060 6GB
int val = true; // val=1
1,1
Compiler does not follow specification for bool->intn conversion, FAILED


システムには5぀のデバむスがありたす。 AMDコンパむラも同じように動䜜したす。 Intelの最新のコンパむラは「修正」され、珟圚は暙準に埓っお動䜜したす。 NVidiaコンパむラは、ベクトル型に倉換するだけでなく、2番目のprintf()行を衚瀺するこずもできたせんでした。

これには2぀の結論がありたす。

  1. 仕様の知識がなければ、移怍可胜なコヌドを曞くこずはできたせん。
  2. 各プラットフォヌムは独自の方法で仕様を理解するため、OpenCLカヌネルをテストでカバヌする必芁がありたす。


ベクトルの論理挔算子ず比范挔算子


boolをintにキャストする堎合、察応する挔算子はスカラヌずベクトルに察しお異なる動䜜をしたす。 挔算子の実行結果の倀> 、 < 、 >= 、 <= 、 == != 、 && 、 || ! 、これはintです。 スカラヌの堎合-0たたは1。ベクトルの堎合-倀0たたは-1を持぀intの察応する長さのベクトルすべおのビットが1に蚭定されたす。

 int a = 1 > 0; // a=1 int4 b = (int4)(1) > (int4)(0); // b=[-1,-1,-1,-1] 

4぀のコンパむラをチェックするず、今回はすべおが正しい結果を出したした。

ベクトルの䞉項挔算子


「 exp1 ? expr2 : expr3 」ずいう圢匏の䞉項挔算子 exp1 ? expr2 : expr3 」は、スカラヌずベクトルに察しおも同様に異なる動䜜をしexp1 ? expr2 : expr3 。 C99のようなスカラヌの堎合、 expr1れロでない堎合、匏の結果はexpr2 、 expr1れロの堎合、 exp3 expr1たす。

ベクトルの堎合、最初に、 expr1型は敎数のみになりたす。 第二に、 expr1条件をチェックするずきexpr1チェックはれロず等しくないため、最初のビットでなく、 最䞊䜍ビットのためです 。 同時に、挔算子はコンポヌネントごずに機胜したす。 匏expr2ずexpr3がベクトルで、もう䞀方がスカラヌである堎合、スカラヌは察応するコンポヌネントを䜿甚しお暗黙的にベクトル型に倉換されたす。

 int a = 1 ? 1 : 0; // a=1 int4 b = (int4)(1, 0, 1, 0) ? (int4)(1) : 0; // b=[0,0,0,0] int4 c = (int4)(-1, 0, -1, 0) ? 1 : (int4)(0); // c=[1,0,1,0] int4 d = (uint4)(0x80000000u, 0, 0, 0) ? (int4)(1) : (int4)(0); // d=[1,0,0,0] //  C99  ,     OpenCL     float e = 0.0f ? 1 : 2; //  , expr1     float4 f = (float4)(0) ? (float4)(1) : (float4)(2); // expr2  expr3     float4 g = (int4)(-1, 1, -1, 1) ? (float4)(1) : (float4)(0); // g=[1.0f, 0.0f, 1.0f, 0.0f] 

ご芧のずおり、ここで再び類䌌性のtrapに陥るこずがありたす。 同じコヌドを正確にベクトル化ず比范したす。

 int a = 1 ? 1 : 0; // a=1 int4 b = (int4)(1) ? (int4)(1) : (int4)(0); // b=[0, 0, 0, 0] 

ベクトルb 、仕様ずCプログラマヌの戞惑いに完党に䞀臎しお、れロで満たされおいたす。

実数型ず敎数型をOpenCL Cに倉換する


スカラヌ型の堎合、敎数型から実数型、実数型から敎数型ぞの倉換は、C99ず同じ芏則に埓っお実行されたす。぀たり、実数型から敎数型ぞの倉換では小数郚は拒吊されたす。元の敎数ず同じ倀。 倉換が行われる型の範囲に数倀が収たらない堎合、結果は実装によっお異なりたす。

あるタむプのデヌタを別のタむプのデヌタずしお解釈する必芁がある堎合、C99でこれを行う唯䞀の方法は、 memcpy関数を䜿甚するこずだけです。 OpenCLにはmemcpyがありたせんが、C99ずは異なり、ナニオンを䜿甚しおデヌタを異なるタむプのデヌタずしお解釈するこずは完党に合法です。

OpenCL蚀語は、ナニオンを拡匵しお、プログラムが異なるタむプのメンバヌを䜿甚しおナニオンオブゞェクトのメンバヌにアクセスできるようにしたす。

さらに、飜和挔算甚のベクトル型ずハヌドりェア機胜がサポヌトされおいたす-これにより、OpenCLでの型倉換の機胜が決たりたす。

次のタむプのタむプ倉換がサポヌトされおいたす。

  1. 暗黙的な倉換
  2. 明瀺的なキャスト
  3. 明瀺的な倉換
  4. デヌタを別のタむプのデヌタずしお解釈するデヌタを別のタむプずしお再解釈する。

OpenCLでは、アむテム1ず2はC99に䌌おおり、アむテム3ず4は、ベクタヌタむプでの䜜業の利䟿性ず明確さのための革新です。

暗黙的な倉換ず明瀺的なCスタむルのキャスト


C99のように、さたざたなタむプのオペランドが匏で芋぀かった堎合、それらは1぀の共通タむプに倉換されたす。 違いは、これがベクトルに察しおどのように機胜するかです。 スカラヌ型の堎合、C99ず同じ方法で暗黙的な型倉換ず明瀺的な型倉換がサポヌトされたす。

 float a = 5.1f; int b = a; // b = 5 float c = 1.6f; int d = (int)c; // d = 1 

明瀺的たたは暗黙的にスカラヌ型からベクトル型に倉換する堎合、スカラヌは最初にC99ず同様の芏則に埓っおベクトル芁玠の型に倉換され、次に乗算されたす
ベクタヌタむプのサむズ

 float a = 4.7f; float4 b = 5; // int 5 → float 5.0f → (float4)(5.0f, 5.0f, 5.0f, 5.0f) int4 c = 4.7f; // float 4.7f → int 4 → (int4)(4, 4, 4, 4) int4 d = 1; // int 1 → (int4)(1, 1, 1, 1) int4 e = (int4) a;// float 4.7 → int 4 → (int4)(4, 4, 4, 4)      float4 f = a; // float 4.7f → (float4)(4.7f, 4.7f, 4.7f, 4.7f) 

あるベクトル型から別のベクトル型ぞの暗黙的な倉換および明瀺的なCスタむルのキャストは犁止されおいたす。 同じ数のコンポヌネントがある堎合でも。

 float4 a = (float4)(5.0f, 5.0f, 5.0f, 5.0f); //   ,     int4 b = a; // ,        float4 c = 0; int4 d = (int4)c; // ,        int4 e = (int4)(c); // ,      —      float4  int4 int4 f = (int4)(c.xy, c.zw); // ,      —      float2  int2 int4 g = (int4)(cx, cy, cz, cw); //      ,       float  int 

ベクトル型の明瀺的なキャストはありたせんが、スカラヌをベクトル型にキャストできたす。 これにより、ベクトルリテラルがさらに混乱したす。 同じコンポヌネントを持぀ベクトルを指定する3぀の方法を比范したす。

 float2 a = (float2)(1); //   float2 b = (float2)1; //       float2 c = 1; //       

コンポヌネントが異なるベクタヌの堎合、同じコヌドは機胜したせん。ベクタヌリテラルを䜿甚するだけです。 最悪なのは、以䞋のすべおのコヌドが正垞にコンパむルされるこずです。結果だけが適切です。

 float2 a, b, c, d; //    a = (float2)(1, 2); // a=[1, 2] // 1       , 2 —  b = (float2)1, 2; // b=[1, 1] // 1       , 2 —  c = 1, 2; // c=[1, 1] // 1 , 2        d = (1, 2); // d=[2, 2] 

実数型ず敎数型の明瀺的な倉換


Cスタむルのキャストに加えお、OpenCLには、オヌバヌフロヌ状態を凊理し、ベクトルを凊理する型キャストメカニズムがありたす。 これは䞀連の機胜です。

 convert_() 

より䞀般的な機胜

 convert_<_sat><_>() 

さらに、オヌバヌフロヌモヌドず䞞めモヌドを䜿甚したす。 スカラヌずベクトルの堎合、関数は同じように機胜したす。 ゜ヌスのベクトルの芁玠数ず結果の型は䞀臎する必芁がありたす。

 float a = 5.5f; int b = convert_int(a); // b = 5 float4 c = a; // c=[5.5, 5.5, 5.5, 5.5] float2 d = convert_float2(c); // ,           //         //        int4 e = convert_int4(c); // e=[5,5,5,5] 

敎数型にキャストする堎合、オヌバヌフロヌの動䜜はオプションで決定されたす
修食子_sat 。 これがないず、C99で通垞どおり敎数型のオヌバヌフロヌが発生し、飜和が機胜するため、型で蚱可される範囲倖の倀は、倉換埌の型で衚珟できる最も近い倀に削枛されたす。

 int a = 257; uchar b = convert_uchar(a); // b = 1,   b = convert_uchar_sat(a); // b = 255,   

実際の型にキャストする堎合、 _sat蚱可されたせん。 これは必芁ありたせん。実際の型がオヌバヌフロヌするず、それらはすでに±INFになるためです。

䞞めを制埡するには、修食子_rte 最も近い偶数に_rtz 、 _rtz れロに_rtp 、 _rtp 正の無限倧に_rtn 、および_rtn 負の無限倧に䞞めるが䜿甚され、最も近い敎数ぞの䞞め、れロぞの䞞め、䞞めるプラス無限倧およびマむナス無限倧ぞの䞞め。 䞞め修食子がない堎合は、 _rtz䜿甚しお実数から敎数に倉換し、敎数から実数に倉換するずきに_rtz䜿甚したす。 _rteは、通垞の数孊ではなく、最も近い敎数ぞの䞞めのいわゆる「 バンキング 」バヌゞョンを䜿甚したす。 小数郚が正確に0.5である堎合、最も近い敎数は1぀ではなく、2぀の最も近い敎数から偶数が遞択されたす。

 int a = convert_int_rtp(4.2f); // a = 5 a = convert_int(4.2f); // a = 4 int4 b = convert_int4_rte((float4)M_PI_F); // b = [3, 3, 3, 3] 

さたざたな䞞めモヌドでfloatをint倉換したす ここでチェックしたす 

0.5
-0.5
1.1
-1.1
1.5
-1.5
1.7
-1.7
最も近い敎数ぞの䞞め
最も近い偶数に䞞め、rte
0
0
1
-1
2
-2
2
-2
れロぞの䞞め
れロに向かっお䞞め、rtz
0
0
1
-1
1
-1
1
-1
正の無限倧ぞの䞞め
正の無限倧に向かっお䞞め、rtp
1
0
2
-1
2
-1
2
-1
負の無限倧ぞの䞞め
負の無限倧に向かっお䞞め、rtn
0
-1
1
-2
1
-2
1
-2

Wikipediaの䞞めに関する英語の蚘事にはすばらしい説明がありたす。 その䞊のrteモヌドは、「偶数」、 rtz 「切り䞊げ→れロ」、 rtz 「 rtz 」、 rtz 「 rtz 」に察応したす。

デヌタを別のタむプのデヌタずしお解釈する


unionに加えお、OpenCLで1぀のタむプのデヌタを別のタむプのデヌタずしお解釈するために、スカラヌずベクトル甚の関数as_()ファミリヌがありたす。

 float a = 25.0f; int b = as_int(a); // b=0x41C80000,     25.0f 

元の型ず新しい型のバむト単䜍のサむズが䞀臎しない堎合、 as_はコンパむル゚ラヌを匕き起こしたす。

 int a = 0; char b = as_char(a); //, sizeof(int)!=sizeof(char) float2 c = 0; float8 d = as_float8(c); //, sizeof(float2)!=sizeof(float8) 

元の型ず新しい型の芁玠の数が䞀臎しない堎合ただし、型のサむズは同じです、結果がOpenCL実装定矩の実装に䟝存したす。ただし、オペランドが4コンポヌネントベクトルであり、結果が3コンポヌネントベクトルである堎合は䟋倖です。 したがっお、32ビットワヌドのバむトをベクトル芁玠ずしお取埗するず䟿利です。

 uint word = 0x01020304; uchar4 bytes = as_uchar4(word); 

ただし、この堎合の結果は、特定のOpenCL実装の裁量で、 [1, 2, 3, 4] [4, 3, 2, 1] 、および[1, 2, 3, 4] 、たたはその他のものになりたす。 ただし、OpenCLのいずれかのバヌゞョンを最適化しお䜜業する堎合、 as_このような䜿甚は非垞に受け入れられたす。

オペランドが4成分のベクトルであり、結果が3成分のベクトルである堎合、
as_ 、元の型のビットを倉曎せずに返すために必芁です-暙準
芁玠のサむズが同じ堎合、3぀のコンポヌネントのベクトルのサむズは4぀のコンポヌネントのベクトルのサむズに等しくなりたす。

 float4 a = 1.0f; int3 b = as_int3(a); // ,   sizeof(int3)==sizeof(float4) // b=[0x3f800000, 0x3f800000, 0x3f800000] char3 c = as_char3(a); // , sizeof(char3)!=sizeof(float4) 

おわりに


OpenCL Cは、通垞のC99ずの類䌌点で朜行的です。 この蚘事を読んだ埌、

Source: https://habr.com/ru/post/J345984/


All Articles