2
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 1 year has passed since last update.

C# .NETのVector classで条件分岐(if..then..else)

Last updated at Posted at 2022-11-04

はじめに

.NETのVector classを利用すると、C#でCPUのSSEやAVXなどを活用した高速化が実現できる。このとき、条件分岐を含む処理は、Vector.ConditionalSelectを用いて記述できるが、Microsoft Docsの説明では記述例が不足していると思うので、実例を示す。

Vector化する前の処理

例として、HLSで用意された画像のピクセル情報から、RGBのピクセル情報を生成する処理を取り上げる。

HLS; Hue(色相), Lightness(明度), Saturation(彩度)
RGB; R(Red), G(Green), B(Blue)

処理では、色相の範囲により、RGBの各値の計算方法の相違により、条件分岐を行う。

  • 色相(hue)はPCCS(日本色研配色体系)の定義により、0~24の値をとる。
  • cMax, CMin, cDeltaは、欄外で計算済。
  • Vector化したときの並列度向上のため、doubleではなく、floatを選択。
CPUによる1ピクセルごとの処理
float h = (float)hue;
float p;
float r;
float g;
float b;

if (h <= 4.0f)
{
    p = 4.0f;
    r = cMax;
    g = cMin;
    b = ((p - h) / 4.0f) * cDelta + cMin;
}
else if (h <= 8.0f)
{
    p = 4.0f;
    r = cMax;
    g = ((h - p) / 4.0f) * cDelta + cMin;
    b = cMin;
}
else if (h <= 12.0f)
{
    p = 12.0f;
    r = ((p - h) / 4.0f) * cDelta + cMin;
    g = cMax;
    b = cMin;
}
else if (h <= 16.0f)
{
    p = 12.0f;
    r = cMin;
    g = cMax;
    b = ((h - p) / 4.0f) * cDelta + cMin;
}
else if (h <= 20.0f)
{
    p = 20.0f;
    r = cMin;
    g = ((p - h) / 4.0f) * cDelta + cMin;
    b = cMax;
}
else
{
    p = 20.0f;
    r = ((h - p) / 4.0f) * cDelta + cMin;
    g = cMin;
    b = cMax;
}

Vector化した後の処理

条件判定Vectorの作成

  • 何度も使うVectorはclass変数で用意し、method呼び出し毎に生成しない。
class変数で準備
private Vector<float> _p4 = new Vector<float>(4f);
private Vector<float> _p8 = new Vector<float>(8f);
private Vector<float> _p12 = new Vector<float>(12f);
private Vector<float> _p16 = new Vector<float>(16f);
private Vector<float> _p20 = new Vector<float>(20f);
  • hvec5は、色相(hue)を収めたVector。
条件判定Vector
Vector<Int32> isHlte4 = Vector.LessThanOrEqual(hvec5, _p4);
Vector<Int32> isHlte8 = Vector.LessThanOrEqual(hvec5, _p8);
Vector<Int32> isHlte12 = Vector.LessThanOrEqual(hvec5, _p12);
Vector<Int32> isHlte16 = Vector.LessThanOrEqual(hvec5, _p16);
Vector<Int32> isHlte20 = Vector.LessThanOrEqual(hvec5, _p20);

Vector<Int32> isHgt4 = Vector.GreaterThan(hvec5, _p4);
Vector<Int32> isHgt8 = Vector.GreaterThan(hvec5, _p8);
Vector<Int32> isHgt12 = Vector.GreaterThan(hvec5, _p12);
Vector<Int32> isHgt16 = Vector.GreaterThan(hvec5, _p16);
Vector<Int32> isHgt20 = Vector.GreaterThan(hvec5, _p20);

Vector<Int32> isHlte8gt4 = Vector.BitwiseAnd(isHlte8, isHgt4);
Vector<Int32> isHlte12gt8 = Vector.BitwiseAnd(isHlte12, isHgt8);
Vector<Int32> isHlte16gt12 = Vector.BitwiseAnd(isHlte16, isHgt12);
Vector<Int32> isHlte20gt16 = Vector.BitwiseAnd(isHlte20, isHgt16);

色相の上限と下限をVector.LessThanOrEqaulVector.GreaterThanで判定し、Vector.BitwiseAndで合成する。結果として得られるベクトルの要素は、偽の場合は0、真の場合は0以外が設定される(手許のVS2022で実行したところ-1)。

条件ごとの事前計算

Vectorの各要素が、直列処理のif文、else文のどの条件に合致するかは様々なので、全ての場合を事前に計算する。ここでは、色相(Hue)の範囲ごとに、減算(Subtract)、除算(Divide)、乗算(Multiply)、加算(Add)を行う。

  • cdelta2は、直列処理のcDeltaに相当する値。
  • 変数を使い回しせず増やしているのは、デバッグの便を考えたもの。
条件ごとの事前計算
Vector<float> bvec4 = Vector.Subtract(_p4, hvec5);
Vector<float> bvec42 = Vector.Divide(bvec4, _p4);
Vector<float> bvec43 = Vector.Multiply(bvec42, cdelta2);
Vector<float> bvec44 = Vector.Add(bvec43, cmin);

Vector<float> gvec8 = Vector.Subtract(hvec5, _p4);
Vector<float> gvec82 = Vector.Divide(gvec8, _p4);
Vector<float> gvec83 = Vector.Multiply(gvec82, cdelta2);
Vector<float> gvec84 = Vector.Add(gvec83, cmin);

Vector<float> rvec12 = Vector.Subtract(_p12, hvec5);
Vector<float> rvec122 = Vector.Divide(rvec12, _p4);
Vector<float> rvec123 = Vector.Multiply(rvec122, cdelta2);
Vector<float> rvec124 = Vector.Add(rvec123, cmin);

Vector<float> bvec16 = Vector.Subtract(hvec5, _p12);
Vector<float> bvec162 = Vector.Divide(bvec16, _p4);
Vector<float> bvec163 = Vector.Multiply(bvec162, cdelta2);
Vector<float> bvec164 = Vector.Add(bvec163, cmin);

Vector<float> gvec20 = Vector.Subtract(_p20, hvec5);
Vector<float> gvec202 = Vector.Divide(gvec20, _p4);
Vector<float> gvec203 = Vector.Multiply(gvec202, cdelta2);
Vector<float> gvec204 = Vector.Add(gvec203, cmin);

Vector<float> rvec24 = Vector.Subtract(hvec5, _p20);
Vector<float> rvec242 = Vector.Divide(rvec24, _p4);
Vector<float> rvec243 = Vector.Multiply(rvec242, cdelta2);
Vector<float> rvec244 = Vector.Add(rvec243, cmin);

それぞれのブロックの最後のVectorに、個々の条件ごとの計算結果が入る。

ConditionalSelectを用いた条件ごとの事前計算結果の取捨選択

Vector.ConditionalSelectは、第1引数の判定ベクトルの個々の要素の値に従い、真なら第2引数、偽なら第3引数の値を選択し、結果のVectorを返す。?:三項演算子に近い動作をする。

ここでは、先に用意した条件判定Vectorを用い、事前計算結果のVectorを、Vectorの各要素ごとに選択して結果のVectorを生成する。

  • _vUndefは、未定義値用のVector
  • cminは、直列処理のcMinに相当するVector
  • cmax2は、直列処理のcMaxに相当するVector
  • bv,gv,rvは、BGR(RGBの各要素)に相当するVector
ConditionalSelectを用いた取捨選択
// if (h <= 4.0f)
Vector<float> bv = Vector.ConditionalSelect(isHlte4, bvec44, _vUndef);
Vector<float> gv = Vector.ConditionalSelect(isHlte4, cmin, _vUndef);
Vector<float> rv = Vector.ConditionalSelect(isHlte4, cmax2,_vUndef);

// else if (h <= 8.0f)
Vector<float> bv2 = Vector.ConditionalSelect(isHlte8gt4, cmin, bv);
Vector<float> gv2 = Vector.ConditionalSelect(isHlte8gt4, gvec84, gv);
Vector<float> rv2 = Vector.ConditionalSelect(isHlte8gt4, cmax2, rv);

// else if (h <= 12.0f)
Vector<float> bv3 = Vector.ConditionalSelect(isHlte12gt8, cmin, bv2);
Vector<float> gv3 = Vector.ConditionalSelect(isHlte12gt8, cmax2, gv2);
Vector<float> rv3 = Vector.ConditionalSelect(isHlte12gt8, rvec124, rv2);

// else if (h <= 16.0f)
Vector<float> bv4 = Vector.ConditionalSelect(isHlte16gt12, bvec164, bv3);
Vector<float> gv4 = Vector.ConditionalSelect(isHlte16gt12, cmax2, gv3);
Vector<float> rv4 = Vector.ConditionalSelect(isHlte16gt12, cmin, rv3);

// else if (h <= 20.0f)
Vector<float> bv5 = Vector.ConditionalSelect(isHlte20gt16, cmax2, bv4);
Vector<float> gv5 = Vector.ConditionalSelect(isHlte20gt16, gvec204, gv4);
Vector<float> rv5 = Vector.ConditionalSelect(isHlte20gt16, cmin, rv4);

// else
Vector<float> bv6 = Vector.ConditionalSelect(isHgt20, cmax2, bv5);
Vector<float> gv6 = Vector.ConditionalSelect(isHgt20, cmin, gv5);
Vector<float> rv6 = Vector.ConditionalSelect(isHgt20, rvec244, rv5);

各条件は重ねて適用する必要があるので、ブロックごとの結果を次のブロックの入力に使用している。ここでは、最終結果は、bv6,gv6,rv6に入る。

デバッグ例

VisualStudio 2022をデバッグモードで動作させ、ConditionalSelectが終了した時点の変数表を下図に示す。

ConditionalSelect.png

Windowの色名Orange(#FFFFA5)のピクセルの明度を増やした結果をRGBに戻す処理で、Vectorは4要素からなり、色相Vector(hvec5)の各要素の値は6.6ほど。
LessThanOrEqualの処理では、4以下を表すVector(isHlte4)のみが全ての要素が偽。GreaterThanの処理では、4より大を表すVector(isHgt4)のみが全ての要素が真。この2つを合成した結果、4より大きく8以下を表すVector(isHlte8gt4)の要素が真になる。

ConditionalSelect2.png

この結果、RGBのG(Green)の値についていえば、計算結果のVector(gv6)には、事前計算結果のVector(gvec84)の各値が設定されている。

高速化の度合い

処理例を見てわかるとおり、全ての条件について計算するなど演算量は少なくなく、並列化の効果を減殺しないか気になる。実際に、計測してみると、1024×640(約65万)ピクセルの画像で、次の通り。

  • 直列処理 200~300ms
  • Vector処理(8並列, AVX2あり) 70~80ms

それなりの効果は計測できるが、実際のアプリでは、SoftwareBitmapの処理などが重く、体感できる量は小さくなる。手間に見合うかどうかは、アプリの性質次第。

考慮点

  • 計算式を変形して、method呼び出し毎に変化しない部分を事前に計算することで、一層の高速化が期待できる場合がある。ただし、除算を先に行い有効桁数を下げることや、オーバーフローの発生に留意が必要になる。
  • 処理例では、デバッグの便を考慮し、変数の使い回しをせず、methodの呼び出し毎に多数の変数を生成しているが、高速化のためには改善の余地がある。
2
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
2
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?