SIMDを使っての計算はSystem.Numerics.Vectorクラスと、これとは別にSystem.Runtime.Intrinsicsってのもあるみたいで試してみた
環境
.NET FrameworkだとSystem.Numericsの参照の追加とかあってめんどくさいけど、.NET Coreならラク
参考にしたところ
habr.com
今回はここからコピペしたりして試した
IntrinsicsはNumerics.Vectorと同じくらい速い
Numerics.Vectorに似ている
ポインタを使う
Vector256を使う
AVXをサポートしていない古い(2010年くらいの)CPUでは動かないっぽい
C#で説明しているところがほとんどないC++ばかり
ポインタは久しぶりに使うことになった、設定は以前の記事、このとき以来かな
追加するusing
System.Runtime.IntrinsicsとSystem.Runtime.Intrinsics.X86を追加
今回の目的はbyte配列の合計値を求める、なので起動時にbyte配列作成
34~38行目でbyte配列作成、値は0~255までの連番、要素数は256個、これをMyArrayって名前をつけたところ(27行目)に入れておく
値からVector256作成
横に長過ぎるので切って表示すると
Vector256のCreateに値を渡すと作成できる、実行して一時停止で中を見ると
値を1つ指定した場合は、その値で埋まって、型は適当なものになった
型を指定してZeroをつけると、その型の0で埋まった、180行目
それ以外の173~178行目は指定した型によって値の個数が決まっているみたいで
値の個数 |
型 |
32 |
Vector256<byte> |
16 |
Vector256<short> |
8 |
Vector256<int> |
4 |
Vector256<long> |
これは固定なのかなあ、Numerics.Vectorと同じ感じになっている
これ以外の個数で作ろうとするとエラーになる
var x = Vector256.Create(1, 1);//これはエラー
配列からVector作成は
System.Runtime.Intrinsics.AvxクラスのLoadVector256を使う
これの引数がポインタ、byte*ってアスタリスクがついているからbyte型のポインタ
ポインタを使うにはunsafeもつけて(159行目)、161行目のfixedはアドレスの固定?これでaa(配列)のメモリアドレスが固定されて安心してポインタを使えるとかそういうの、で、byte型ポインタの変数pに配列aaを指定、これをLoadVector256に渡して見ると
Vector256<byte>型ができた、値は配列の先頭から32個分の値
0~31が入っている
ポインタの値を+1したのを渡すと
配列の2番めから32個分の値、1~32までのVector256<byte>ができた
ポインタの値を+32だと
32番めからの32個分
これで配列からVectorは作成できたのでVector同士の足し算は
System.Runtime.Intrinsics.X86.Avx2のAdd
a,b2つのVectorをAddで足し算はAvx2.Add(a, b)、これで
ペア同士が足し算された値のVectorが返ってくる、返ってくるVectorの型も同じなので、足し算の結果が型の最大値を超えるとオーバーフローでおかしな値になる。このへんはNumerics.Vectorと同じだねえ
148行目で250で埋めたVectorを作って、これと連番の配列から作ったVectorを足し算すると
v |
250 |
250 |
250 |
250 |
250 |
250 |
250 |
250 |
250 |
250 |
tempV |
0 |
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
total |
250 |
251 |
252 |
253 |
254 |
255 |
0 |
1 |
2 |
3 |
|
|
|
|
|
|
|
256 |
257 |
258 |
259 |
オーバーフロー
なのでbyte型より大きな値が使えるintとかlong型のVectorで計算する必要がある
byte型配列から他の整数型Vectorを作成
これにはAvx2にConvertToVector256Intっていう丁度いいのがあった、これで
byte型配列からshort、Int、long型のVectorが作成できる、すごい!Numerics.Vectorにはこれがなかったんだよねえ
これを使ってさっきオーバーフローしてたのを少し変えたのが
129行目でint型に変換してVectorを作成
v |
250 |
250 |
250 |
250 |
250 |
250 |
250 |
250 |
250 |
tempV |
0 |
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
total |
250 |
251 |
252 |
253 |
254 |
255 |
256 |
257 |
258 |
Int型Vectorなので要素数は8に減っているけど、256以上になっても正しい値になっている。
これでVectorの足し算はできたので、次はVectorの中の値を合計したいけど、そういうメソッドはないみたいで、ググってみたところではAvxクラスのStoreっていうメソッドで、普通の配列に値をコピーしてから合計していた。
Avx.Store
用意した配列のポインタとVectorを渡すと配列に値が入る
//Vectorから配列に変換というか値をコピーその1
100で埋めたInt型Vectorの値の合計
あらかじめVectorの要素数と同じサイズ同じ型の配列を用意して(100行目)、これのポインタとVectorを渡す(103行目)、あとは普通にForやLINQのSumとかで足し算すればいいってことになる
fixedの意味がわかっていないので念のために使ってみたけど必要ないかも?
もう一つの方法はstackallocってのを使って配列のポインタにしていた
これだとLINQのSumで合計できないからForで回す、多分こっちのほうが速い
計算結果は同じ、こっちのほうがいいのかなあ
ここまでのを全部合わせてできたのが
渡したbyte型配列の合計を返す
大まかな方法はNumerics.Vectorとほとんど同じだねえ
0~255の連番の配列を渡すので結果は(255+0)*(255-0+1)/2=32640になるはず
Vector同士のたし算が終わったとろこ
Vectorの中の値を合計し終わったところ、もとの配列の要素数256はInt型Vectorの要素数8で割り切れて、あまりの要素はないので合計値は32640で出ている
出た
あとはNumerics.Vectorと実際に比較してみてどっちが速いかだねえ、Intrinsicsのほうが少し速いみたいなんだけど、ポインタを使うのがめんどくさいので、少し速いくらいならNumerics.Vectorを使うかなあ、でもメソッドの種類はIntrinsicsのが多い、すごい多い、この辺を使えたら便利そうなんだけど、日本語で説明しているところがないので使い方がほとんどわからない
画像処理を早くしたくて色々ググってるとC++、OpenCV、GPUとかの単語が出てくるけど難しそう、それでもいろいろな方法があるのはいいねえ
今回のアプリは合計値を表示するだけだからあんまり意味ないけど
ファイル名は、20200223_Intrinsics.zip
github.com
<Window xClass="_20200223_Intrinsics.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlnsx="http://schemas.microsoft.com/winfx/2006/xaml"
xmlnsd="http://schemas.microsoft.com/expression/blend/2008"
xmlnsmc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlnslocal="clr-namespace:_20200223_Intrinsics"
mcIgnorable="d"
Title="MainWindow" Height="100" Width="614">
<Grid>
<DockPanel>
<TextBlock xName="tb1" FontSize="40" Text="text" VerticalAlignment="Center" HorizontalAlignment="Center"/>
</DockPanel>
</Grid>
</Window>
using System.Linq;
using System.Windows;
using System.Runtime.Intrinsics;
using System.Runtime.Intrinsics.X86;
namespace _20200223_Intrinsics
{
<summary>
</summary>
public partial class MainWindow : Window
{
private byte[] MyArray;
public MainWindow()
{
InitializeComponent();
this.Title = this.ToString();
MyArray = new byte[256];
for (int i = 0; i < 256; i++)
{
MyArray[i] = (byte)i;
}
Test1VectorCreate();
Test2Load(MyArray);
Test3AddOverflow(MyArray);
Test4Convert(MyArray);
Test5Add(MyArray);
Test6Add(MyArray);
Test7AvxStore();
Test8AvxStore();
tb1.Text = $"0から255まで連番の合計 = {Test9Add(MyArray).ToString("N0")}";
}
private unsafe int Test9Add(byte[] aa)
{
var v = Vector256<int>.Zero;
int simdLength = Vector256<int>.Count;
int i;
fixed (byte* p = aa)
{
for (i = 0; i < aa.Length; i += simdLength)
{
Vector256<int> vv = Avx2.ConvertToVector256Int32(p + i);
v = Avx2.Add(v, vv);
}
}
int* temp = stackalloc int[simdLength];
Avx.Store(temp, v);
int total = 0;
for (int k = 0; k < simdLength; k++)
{
total += temp[k];
}
for (; i < aa.Length; i++)
{
total += aa[i];
}
return total;
}
private unsafe void Test8AvxStore()
{
Vector256<int> v = Vector256.Create(100);
int simdLength = Vector256<int>.Count;
int* temp = stackalloc int[simdLength];
Avx.Store(temp, v);
int total = 0;
for (int i = 0; i < simdLength; i++)
{
total += temp[i];
}
}
private unsafe void Test7AvxStore()
{
Vector256<int> v = Vector256.Create(100);
int simdLength = Vector256<int>.Count;
int[] temp = new int[simdLength];
fixed (int* p = temp)
{
Avx.Store(p, v);
}
int total = temp.Sum();
}
private unsafe void Test6Add(byte[] aa)
{
Vector256<int> total = Vector256<int>.Zero;
fixed (byte* ptrA = aa)
{
for (int i = 0; i < aa.Length; i += Vector256<int>.Count)
{
Vector256<int> tempV = Avx2.ConvertToVector256Int32(ptrA + i);
total = Avx2.Add(total, tempV);
}
}
}
private unsafe void Test5Add(byte[] aa)
{
Vector256<int> v = Vector256.Create((int)250);
Vector256<int> total;
fixed (byte* ptrA = aa)
{
Vector256<int> tempV = Avx2.ConvertToVector256Int32(ptrA);
total = Avx2.Add(v, tempV);
}
}
private unsafe void Test4Convert(byte[] aa)
{
fixed (byte* ptrA = aa)
{
var v1 = Avx2.ConvertToVector256Int16(ptrA);
var v2 = Avx2.ConvertToVector256Int32(ptrA);
var v3 = Avx2.ConvertToVector256Int64(ptrA);
}
}
private unsafe void Test3AddOverflow(byte[] aa)
{
Vector256<byte> v = Vector256.Create((byte)250);
Vector256<byte> total;
fixed (byte* ptrA = aa)
{
Vector256<byte> tempV = Avx.LoadVector256(ptrA);
total = Avx2.Add(v, tempV);
}
}
private unsafe void Test2Load(byte[] aa)
{
fixed (byte* p = aa)
{
var vv = Avx.LoadVector256(p);
vv = Avx.LoadVector256(p + 1);
vv = Avx.LoadVector256(p + 2);
vv = Avx.LoadVector256(p + 32);
}
}
private void Test1VectorCreate()
{
Vector256<long> a = Vector256.Create(111, 1, 1, 1);
Vector256<int> b = Vector256.Create(1322, 1, 1, 1, 1, 1, 1, 1);
Vector256<short> c = Vector256.Create(54, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 16);
Vector256<sbyte> d = Vector256.Create(87, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 16, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 32);
Vector256<double> e = Vector256.Create(1, 1, 1, 1f);
Vector256<float> f = Vector256.Create(32, 1, 1, 1f, 1, 1, 1, 1);
var g = Vector256<int>.Zero;
var h = Vector256.Create(2);
var i = Vector256.Create(2f);
var j = Vector256.Create(2d);
var k = Vector256.Create((byte)2);
}
}
}
参照したところ
ufcpp.net
docs.microsoft.com
見てもほとんどわからん
関連記事
次回は2日後
前回は3日前
2年前…