---
# System prepended metadata

title: 'A点から見てB点がn以内に入っているかどうかをCPU専用命令(Avx,Ssse)などを使用して最適化する実験記録'
tags: [Intringic, Unity]

---

###### tags: `Intringic` `Unity`
# A点からB点間がn以内に入っているかどうかをCPU専用命令(Avx,Ssse)などを使用して最適化する実験記録

## はじめに
Physics-Overlap00関数を大量に使用する機会があったが、事前に座標を取得して(動いたオブジェクトのみ更新)自分で計算したほうがある程度の数までは早いことが分かったので、できるだけある程度の個数を増やそうとする試みです。

## 検証するコード
*コードは省略しています

1. ふつうのコード
```csharp
Vector2 zero = Vector2.zero;
for (int i = 0; i < array.Length; i++)
{
    if (Vector2.Distance(array[i] , zero) <= 5.0f)
    {
        result[i] = array[i];
    }
}
```

2. ふつうのコード(SqrMagnitude)
```csharp
Vector2 zero = Vector2.zero;
for (int i = 0; i < arrat.Length; i++)
{
    if (Vector2.SqrMagnitude(array[i] - zero) <= 25.0f)
    {
        result[i] = array[i];
    }
}
```
3. 専用命令のコード
*いろいろ省略してます
```csharp
unsafe public static void WithinDistance(Span<float> selfX,Span<float> selfY,
            in Vector2 target,float threshold, Span<float> resultX,Span<float> resultY, out int resultLen)
{
    fixed (float* selfXPtr = selfX)
    {
        fixed (float* selfYPtr = selfY)
        {
            fixed (float* resultXPtr = resultX)
            {
                fixed (float* resultYPtr = resultY)
                {
                    WithinDistance_burst(selfXPtr, selfYPtr, target, threshold, selfX.Length, resultXPtr, resultYPtr, out resultLen);
                }
            }
        }
    }
    
    /*
    fixed (float* selfXPtr = selfX, selfYPtr = selfY, resultXPtr = resultX, resultYPtr = resultY)
    {
        WithinDistance_burst(selfXPtr, selfYPtr, target, threshold, selfX.Length, resultXPtr, resultYPtr, out resultLen);
    }*/
}
[BurstCompile(CompileSynchronously = true)]
private unsafe static void WithinDistance_burst
    (float* selfX, float* selfY,in Vector2 target, 
    float threshold,int size, float* resultX, float* resultY, out int resultLen)
{
    resultLen = 0;
    if (Avx.IsAvxSupported)
    {
        int mod = size % 8;

        v256 selfX_256 = new v256(0f);
        v256 selfY_256 = new v256(0f);
        v256 targetX_256 = mm256_set1_ps(target.x);
        v256 targetY_256 = mm256_set1_ps(target.y);

        float threshold_pow2 = threshold * threshold;
        v256 threshold_256 = mm256_set1_ps(threshold_pow2);

        for (int i = 0; i < size - mod; i += 8)
        {
            selfX_256 = mm256_loadu_ps(selfX + i);
            selfY_256 = mm256_loadu_ps(selfY + i);
            v256 num = mm256_sub_ps(selfX_256, targetX_256);
            v256 num2 = mm256_sub_ps(selfY_256, targetY_256);
            v256 numPow2 = mm256_mul_ps(num, num);
            v256 num2Pow2 = mm256_mul_ps(num2, num2);
            v256 add = mm256_add_ps(numPow2, num2Pow2);

            v256 mask = mm256_cmp_ps(add, threshold_256, 2);

            
            resultLen += v256NL.mm256_maskstore_packv2_ps(mask, selfX_256, selfY_256,
                resultX + resultLen, resultX + resultLen);
        }
    }

}
```
3. 専用命令のコード(外で条件分岐を行う)
2のコードを改造し、Burst外で条件分岐を行うようにする。
4. 専用命令のコード(結果をインデックスで受け取る)
2のコードを改造し、結果をインデックスで受け取るようにする。

## 検証結果
4000の座標が入った配列を1000回ループさせて、それを実行順番や値を100回実行させてその平均をとっています。なお結果は4000回計算した結果です。
![](https://i.imgur.com/KztsnIa.png)

## まとめ
Physics2D-Overlap00関数を計測したとき500ns＋αくらいなので1回あたりが2500/4000=0.625nsだとすると800を超えない限りは全あたりする方が速いことがなんとなくわかりました。(すべての座標を総当たりした場合も関数も1つの点当たり1回使うので500ns * n回)

なお、専用命令を使用しない場合だと1回の計算で18nsもかかってしまうので27を超えている場合はPhysics2D-Overlap00関数を使用したほうがいい事が分かりました。(専用命令で記述するのはいろいろリスクがあるのでたまにしかやらないならこちらの方がいい)


## 追記
値の左詰めと格納をLUTを作成し(それにより処理自体の条件分岐は0個になりました)それに変えたところ1200nsまで速度が向上しました。
つまり現在の専用命令が対応している環境で比較した場合は1700くらいまでは速いということが分かりました。
ちなみに今回は4000の配列で計測していますが、40000とかでやると速度の差はまとめを書いた時の最速より10倍速いです。
