午後わてんのブログ

ベランダ菜園とWindows用アプリ作成(WPFとC#)

ドット積を求めるSystem.Numerics.VectorのDotメソッドを使って分散を求めてみた

 

2020/02/19追記ここから

処理時間の基準にしたTest01でMathPowの使い方に問題があることが6日後の

gogowaten.hatenablog.com

↑の記事でわかったので

書き直したのが ↓

gogowaten.hatenablog.com

なのでここから↓はほとんど意味ない

2020/02/19追記ここまで

 

 

結果

f:id:gogowaten:20200212152054p:plain

普通のループやSystem.Numerics.Vector、Vector4を使って分散を求めてみた

値はランダムなbyte型、要素数100万から100回

 

アプリのダウンロード先

20200210_配列の分散、VectorDot.zip

ギットハブ

github.com

 

環境

CPU AMD Ryzen 5 2400G(4コア8スレッド)

MEM DDR4-2666

Window 10 Home 64bit

Visual Studio 2019 Community .NET Core 3.1 WPF C#

 

分散の求め方その1

f:id:gogowaten:20200212114500p:plain

{20, 21, 7, 12}

っていう4個の要素のデータ、これの分散は33.5

 

分散 = 偏差の2乗の平均値

偏差 = 各要素と平均値との差

分散 = 各要素と平均値との差の2乗の平均値

 

データの平均値

{20, 21, 7, 12}の平均値

(20+21+7+12) / 4

= 15

 

偏差

{20, 21, 7, 12}の各偏差は(各要素 - 平均値)なので

{20-15, 21-15, 7-15, 12-15}

= {5, 6, -8, -3}

 

偏差の2乗

{5*5, 6*6, -8*-8, -3*-3}

= {25, 36, 64, 9}

 

分散は偏差の2乗の平均値

(25+36+64+9) / 4

= 33.5

 

System.Numerics.Vector.Dotメソッド

f:id:gogowaten:20200212115205p:plain

ドット積はググってみたけどよくわからんけど

f:id:gogowaten:20200212120659p:plain
2つの同じ要素数の配列(ベクトル?)の各値を掛け算して、それを合計することみたい。掛け算して足し算

 

配列1{1, 3, 5, 7}

配列2{16, 8, 2, 4}

この2つの配列のドット積は78

 

各要素同士の掛け算

{1*16, 3*8, 5*2, 7*4}

={16,24,10,28}

 

合計

{16+24+10+28}

=78

ドット積は78

 

Dotメソッドを使ってみると

f:id:gogowaten:20200212121248p:plain

期待通りの値が返ってきた

 

分散は偏差の2乗の平均、このうち偏差の2乗は掛け算だし、平均を求めるときには合計する。つまり掛け算して足し算、これはドット積(Dotメソッド)と同じ!

 

 

byte型配列の分散を求める

値はランダムなbyte型の配列

平均値は先に計算しておく

//平均値
private double GetAverage(byte[] ary)
{
    long total = 0;
    Parallel.ForEach(Partitioner.Create(0, ary.Length),
        (range) =>
        {
            long subtotal = 0;
            for (int i = range.Item1; i < range.Item2; i++)
            {
                subtotal += ary[i];
            }
            Interlocked.Add(ref total, subtotal);
        });
    return total / (double)ary.Length;
}

型はdoubleにした

ParallelForEachとPartitionerを使ったマルチスレッドは前回の方法

gogowaten.hatenablog.com

 

平均値は

f:id:gogowaten:20200212123200p:plain

アプリの起動時に計算してフィールドに置いておく、32行目

 

普通のループで計算、処理時間は3.4秒

private double Test01_Double_ForLoop(byte[] ary)
{
    //平均との差(偏差)の2乗を合計
    double total = 0;
    for (int i = 0; i < ary.Length; i++)
    {
        total += Math.Pow(ary[i] - MyAverage, 2.0);
    }
    //合計 / 要素数 = 分散
    return total / ary.Length;
}

 

 

System.Numerics.Vector.Dotメソッドを使って計算、処理時間は0.3秒

//平均との差は普通のループで、double型
private double Test10_DoubleVectorDot(byte[] ary)
{
    int simdLength = Vector<double>.Count;
    int lastIndex = ary.Length - (ary.Length % simdLength);
    Vector<double> v;
    double total = 0;
    double[] ii = new double[simdLength];
    for (int i = 0; i < lastIndex; i += simdLength)
    {
        //平均との差の配列
        for (int j = 0; j < simdLength; j++)
        {
            ii[j] = MyAverage - ary[i + j];
        }
        //差の配列からVector作成してドット積
        v = new Vector<double>(ii);
        total += System.Numerics.Vector.Dot(v, v);
    }
    return total / ary.Length;
}

少し問題があって、VectorはVectorCountの値の要素数ごとにしか計算できないから、全要素数がVectorCountで割り切れなかった場合、この余りは別に計算しないと正しい値にならないんだけど、めんどくさいので今回は計算していない

全要素数が10でVectorCountが4のとき、10%4=2で2個のあまりが出る、

 

System.Numerics.Vector.Subtractメソッドと

System.Numerics.Vector.Dotメソッドを使って計算、処理時間は0.3秒

//Vector<double>で計算
private double Test04_DoubleVectorSubtractDot(byte[] ary)
{
    var vAverage = new Vector<double>(MyAverage);
    int simdLength = Vector<double>.Count;
    int lastIndex = ary.Length - (ary.Length % simdLength);
    Vector<double> v;
    var ss = new double[simdLength];
    double total = 0;
    for (int i = 0; i < lastIndex; i += simdLength)
    {
        for (int j = 0; j < simdLength; j++)
        {
            ss[j] = ary[i + j];
        }
        //平均との差
        v = System.Numerics.Vector.Subtract(vAverage, new Vector<double>(ss));
        //差の2乗を合計
        total += System.Numerics.Vector.Dot(v, v);
    }
    return total / ary.Length;
}

Subtractメソッドは要素同士の引き算、これで平均値との差を求めている

ここまでの3つだと普通のループよりもDotメソッドを使ったほうが10倍速い!ってところなんだけど

 

普通のループをint型で計算、処理時間0.07秒

private double Test02_Integer_ForLoop(byte[] ary)
{
    //平均との差の2乗を合計
    long total = 0;
    int average = (int)MyAverage;
    int ii;//ループの外に出したほうが誤差程度に速い
    for (int i = 0; i < ary.Length; i++)
    {
        //total += (int)Math.Pow(ary[i] - average, 2);
        //total += (ary[i] - average) * (ary[i] - average);//こっちのほうが↑より10倍以上速い
        ii = ary[i] - average;
        total += ii * ii;
    }
    //合計 / 要素数 = 分散
    return total / (double)ary.Length;
}

平均値を切り捨ての整数にして、全てをint型で計算したら0.07秒!整数だから正確な分散からは少し違う値になるけど

ランダムなbyte型の値100万の場合

5474.5395 double

5474.7733 int

その差0.2338、5000分の0.2しか違わない、これならint型で十分

じゃあVectorもint型で計算したら

 

//平均との差は普通のループで、int型
private double Test09_IntegerVectorDot(byte[] ary)
{
    int average = (int)MyAverage;
    int simdLength = Vector<int>.Count;
    int lastIndex = ary.Length - (ary.Length % simdLength);
    Vector<int> v;
    long total = 0;
    int[] ii = new int[simdLength];
    for (int i = 0; i < lastIndex; i += simdLength)
    {
        //平均との差を配列に入れる
        for (int j = 0; j < simdLength; j++)
        {
            ii[j] = average - ary[i + j];
        }
        //差の配列からVector作成して2乗和を合計していく
        v = new Vector<int>(ii);
        total += System.Numerics.Vector.Dot(v, v);
    }
    return total / (double)ary.Length;
}

これでの処理時間は0.15秒、double型Vectorより速くなったけど、それでも普通のループで計算したほうが2倍速い

 

引き算もVector

//Vector<int>で計算
private double Test05_IntegerVectorSubtractDot(byte[] ary)
{
    var vAverage = new Vector<int>((int)MyAverage);
    int simdLength = Vector<int>.Count;
    int lastIndex = ary.Length - (ary.Length % simdLength);
    Vector<int> v;
    var ss = new int[simdLength];
    double total = 0;
    for (int i = 0; i < lastIndex; i += simdLength)
    {
        for (int j = 0; j < simdLength; j++)
        {
            ss[j] = ary[i + j];
        }
        //平均との差
        v = System.Numerics.Vector.Subtract(vAverage, new Vector<int>(ss));
        //差の2乗を合計
        total += System.Numerics.Vector.Dot(v, v);
    }
    return total / ary.Length;
}

これでの処理時間は0.18、これも普通のループのほうが速い

 

f:id:gogowaten:20200212142043p:plain

int型での普通のループが速すぎてVectorの出番がない

 

 

分散の求め方その2

mathtrain.jp

ここみると別の求め方があるみたいで

要素の2乗の平均 - 平均の2乗

これで分散になるみたい、説明見ても全然わからんけど

f:id:gogowaten:20200212143654p:plain

たしかに分散になる、すごい

これを使って普通のループで

private double Test11_Double_ForLoop(byte[] ary)
{
    //2乗和
    double total = 0;
    for (int i = 0; i < ary.Length; i++)
    {
        total += Math.Pow(ary[i], 2.0);
    }
    //2乗和の平均
    total /= ary.Length;
    //2乗和の平均 - 平均の2乗
    return total - Math.Pow(MyAverage, 2.0);
}

private double Test12_Integer_ForLoop(byte[] ary)
{

    double total = 0;
    int ii;
    for (int i = 0; i < ary.Length; i++)
    {
        ii = ary[i];
        total += ii * ii;
    }
    total /= ary.Length;

    return total - (MyAverage * MyAverage);
}

上がdouble型、下がint型で計算

結果はそれぞれ求め方その1からその2で

3.36秒から2.84秒

0.070秒から0.085秒

double型は少し速くなったけど、int型では少し遅くなったって、今見たら最後の計算でdouble型になってるからこれが原因かも

return total - (MyAverage * MyAverage);を

return total - ((int)MyAverage * (int)MyAverage);

にしてみたけど処理時間は変わらずだった

 

Vector

//Vector<double>で計算
private double Test14_DoubleVectorDot(byte[] ary)
{
    int simdLength = Vector<double>.Count;
    int lastIndex = ary.Length - (ary.Length % simdLength);
    Vector<double> v;
    var ss = new double[simdLength];
    double total = 0;
    for (int i = 0; i < lastIndex; i += simdLength)
    {
        for (int j = 0; j < simdLength; j++)
        {
            ss[j] = ary[i + j];
        }
        v = new Vector<double>(ss);
        total += System.Numerics.Vector.Dot(v, v);
    }
    return (total / ary.Length) - (MyAverage * MyAverage);
}

//Vector<int>で計算
private double Test15_IntegerVectorDot(byte[] ary)
{
    int simdLength = Vector<int>.Count;
    int lastIndex = ary.Length - (ary.Length % simdLength);
    Vector<int> v;
    var ss = new int[simdLength];
    double total = 0;
    for (int i = 0; i < lastIndex; i += simdLength)
    {
        for (int j = 0; j < simdLength; j++)
        {
            ss[j] = ary[i + j];
        }
        v = new Vector<int>(ss);
        total += System.Numerics.Vector.Dot(v, v);
    }
    return (total / ary.Length) - (MyAverage * MyAverage);
}

0.343から0.332秒 double型

0.154から0.187秒 int型

これもint型が少し速くなったけど最後の平均の2乗がdouble型で計算しているせいかも?

どちらにしてもあんまり変わらない結果

 

Widenメソッドも使って

//Vector<byte>をWidenでVector<ushort>にしてドット積計算はオーバーフローだったので
//Vector<ushort>からさらにVector<uint>にしてドット積
private double Test19_Byte_ushort_uintVectorDot(byte[] ary)
{
    int simdLength = Vector<byte>.Count;
    int lastIndex = ary.Length - (ary.Length % simdLength);
    Vector<byte> v;
    double total = 0;
    Vector<ushort> v1; Vector<ushort> v2;
    Vector<uint> vv1; Vector<uint> vv2; Vector<uint> vv3; Vector<uint> vv4;
    for (int i = 0; i < lastIndex; i += simdLength)
    {
        v = new Vector<byte>(ary, i);
        System.Numerics.Vector.Widen(v, out v1, out v2);
        System.Numerics.Vector.Widen(v1, out vv1, out vv2);
        System.Numerics.Vector.Widen(v2, out vv3, out vv4);
        total += System.Numerics.Vector.Dot(vv1, vv1);
        total += System.Numerics.Vector.Dot(vv2, vv2);
        total += System.Numerics.Vector.Dot(vv3, vv3);
        total += System.Numerics.Vector.Dot(vv4, vv4);
    }
    return (total / ary.Length) - (MyAverage * MyAverage);
}

最初のVectorはbyte型配列から直接作成できるbyte型のVector、これをWidenメソッドでushort型Vectorに変換、これにもWidenメソッドを使ってuint型Vectorに変換してドット積

結果は0.107秒、速くなったけど、これでも普通のint型ループの0.070秒にはかなわなかった

 

System.Numerics.Vector4クラス

Vector4クラスはfloat型の値を4つで色々計算できるみたいで、SubtractやDotメソッドもある、これを使って普通の分散

//Vector4
private double Test08_FloatVector4(byte[] ary)
{
    int lastIndex = ary.Length - (ary.Length % 4);
    var vAverage = new Vector4((float)MyAverage);
    Vector4 v;//= new Vector4();
    double total = 0;
    for (int i = 0; i < lastIndex; i += 4)
    {
        //平均との差
        v = Vector4.Subtract(new Vector4(ary[i], ary[i + 1], ary[i + 2], ary[i + 3]), vAverage);
        //差の2乗を合計
        total += Vector4.Dot(v, v);
    }
    return total / ary.Length;
}

0.101秒!速い

 

Vector4で別の求め方の分散

//Vector4
private double Test18_FloatVector4(byte[] ary)
{
    int lastIndex = ary.Length - (ary.Length % 4);;
    Vector4 v;
    double total = 0;
    for (int i = 0; i < lastIndex; i += 4)
    {
        v = new Vector4(ary[i], ary[i + 1], ary[i + 2], ary[i + 3]);
        total += Vector4.Dot(v, v);
    }
    return (total / ary.Length) - (MyAverage * MyAverage);
}

0.097秒!測定はある程度ブレるから0.1秒台も出るけど0.09秒台もでる、今回のテストでVectorを使った中では最速、float型なのでint型よりも正確な分散のはずなので、速さと正確さの両方を求めるならこれが良さそう

 

 

f:id:gogowaten:20200212152054p:plain

結果をグラフで

f:id:gogowaten:20200212153440p:plain

double型での普通ループの1番と11番は遅すぎるので除外して

f:id:gogowaten:20200212154625p:plain

分散の求め方その1がtest01から10
分散の求め方その2がtest11から20で概ねその2のほうが速い

overflowって付いてる赤いグラフ6,7,16,17番は、計算途中で値がオーバーフローして正確ではない値が返ってくるので、速くても意味ない参考値

Vectorの型による速度は速い順に

byte>>short=int>float>>doubleになった

ただしbyteとshortはオーバーフローするから意味ないし、intだと気分的に誤差が不安なのでfloatがいい、そしてVectorでは最速だったVector4はちょうどfloat型なので、使うとしたら18番かな。整数でいいって場合なら2番

 

f:id:gogowaten:20200212171231p:plain

それにしてもint型で計算する普通のループ(2番)がこんなに速いとは思わなかったねえ、48倍だもんなあ、それでもdouble型同士ならVectorのほうが10倍だし、十分な精度だと思うfloat型ならVector4で35倍だからSIMDを使うSystem.Numerics.Vectorは速いってことで、あと気になるのはVectorだと要素数Vector.Countで割り切れなかった場合のあまりの要素の処理は普通のループですることになるからそれはめんどくさいねえ

 

今回は細切れコードを画像じゃなくて文字で貼り付けてみたけど、画像を貼り付けるほうがラクかも、コピペするときは行頭に無駄なタブがあるから、それを削除して貼り付けるのがめんどくさかった

 

 

MainWindow.xaml

<Window x:Class="_20200210_配列の分散_VectorDot.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
        xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
        xmlns:local="clr-namespace:_20200210_配列の分散_VectorDot"
        mc:Ignorable="d"
        Title="MainWindow" Height="600" Width="614">
  <Grid>
    <StackPanel>
      <StackPanel.Resources>
        <Style TargetType="StackPanel">
          <Setter Property="Margin" Value="2"/>
        </Style>
        <Style TargetType="Button">
          <Setter Property="Width" Value="60"/>
        </Style>
        <Style TargetType="TextBlock">
          <Setter Property="Margin" Value="2,0"/>
        </Style>
      </StackPanel.Resources>
      <TextBlock x:Name="MyTextBlock" Text="text" HorizontalAlignment="Center"/>
      <TextBlock x:Name="MyTextBlockVectorCount" Text="vectorCount" HorizontalAlignment="Center"/>
      <TextBlock x:Name="MyTextBlockCpuThreadCount" Text="threadCount" HorizontalAlignment="Center"/>
      <StackPanel Orientation="Horizontal">
        <Button x:Name="Button1" Content="test1"/>
        <TextBlock x:Name="Tb1" Text="time"/>
      </StackPanel>
      <StackPanel Orientation="Horizontal">
        <Button x:Name="Button2" Content="test2"/>
        <TextBlock x:Name="Tb2" Text="time"/>
      </StackPanel>
      <StackPanel Orientation="Horizontal">
        <Button x:Name="Button3" Content="test3"/>
        <TextBlock x:Name="Tb3" Text="time"/>
      </StackPanel>
      <StackPanel Orientation="Horizontal">
        <Button x:Name="Button4" Content="test4"/>
        <TextBlock x:Name="Tb4" Text="time"/>
      </StackPanel>
      <StackPanel Orientation="Horizontal">
        <Button x:Name="Button5" Content="test5"/>
        <TextBlock x:Name="Tb5" Text="time"/>
      </StackPanel>
      <StackPanel Orientation="Horizontal">
        <Button x:Name="Button6" Content="test6"/>
        <TextBlock x:Name="Tb6" Text="time"/>
      </StackPanel>
      <StackPanel Orientation="Horizontal">
        <Button x:Name="Button7" Content="test7"/>
        <TextBlock x:Name="Tb7" Text="time"/>
      </StackPanel>
      <StackPanel Orientation="Horizontal">
        <Button x:Name="Button8" Content="test8"/>
        <TextBlock x:Name="Tb8" Text="time"/>
      </StackPanel>
      <StackPanel Orientation="Horizontal">
        <Button x:Name="Button9" Content="test9"/>
        <TextBlock x:Name="Tb9" Text="time"/>
      </StackPanel>
      <StackPanel Orientation="Horizontal">
        <Button x:Name="Button10" Content="test10"/>
        <TextBlock x:Name="Tb10" Text="time"/>
      </StackPanel>
      <StackPanel Orientation="Horizontal">
        <Button x:Name="Button11" Content="test11"/>
        <TextBlock x:Name="Tb11" Text="time"/>
      </StackPanel>
      <StackPanel Orientation="Horizontal">
        <Button x:Name="Button12" Content="test12"/>
        <TextBlock x:Name="Tb12" Text="time"/>
      </StackPanel>
      <!--<Border Height="1" Background="Orange" UseLayoutRounding="True"/>-->
      <StackPanel Orientation="Horizontal">
        <Button x:Name="Button13" Content="test13"/>
        <TextBlock x:Name="Tb13" Text="time"/>
      </StackPanel>
      <StackPanel Orientation="Horizontal">
        <Button x:Name="Button14" Content="test14"/>
        <TextBlock x:Name="Tb14" Text="time"/>
      </StackPanel>
      <StackPanel Orientation="Horizontal">
        <Button x:Name="Button15" Content="test15"/>
        <TextBlock x:Name="Tb15" Text="time"/>
      </StackPanel>
      <StackPanel Orientation="Horizontal">
        <Button x:Name="Button16" Content="test16"/>
        <TextBlock x:Name="Tb16" Text="time"/>
      </StackPanel>
      <StackPanel Orientation="Horizontal">
        <Button x:Name="Button17" Content="test17"/>
        <TextBlock x:Name="Tb17" Text="time"/>
      </StackPanel>
      <StackPanel Orientation="Horizontal">
        <Button x:Name="Button18" Content="test18"/>
        <TextBlock x:Name="Tb18" Text="time"/>
      </StackPanel>
      <StackPanel Orientation="Horizontal">
        <Button x:Name="Button19" Content="test19"/>
        <TextBlock x:Name="Tb19" Text="time"/>
      </StackPanel>
      <StackPanel Orientation="Horizontal">
        <Button x:Name="Button20" Content="test20"/>
        <TextBlock x:Name="Tb20" Text="time"/>
      </StackPanel>
      <StackPanel Orientation="Horizontal">
        <Button x:Name="Button21" Content="test21"/>
        <TextBlock x:Name="Tb21" Text="time"/>
      </StackPanel>
      <StackPanel Orientation="Horizontal">
        <Button x:Name="Button22" Content="test22"/>
        <TextBlock x:Name="Tb22" Text="time"/>
      </StackPanel>
      <StackPanel Orientation="Horizontal">
        <Button x:Name="Button23" Content="test23"/>
        <TextBlock x:Name="Tb23" Text="time"/>
      </StackPanel>
      <StackPanel Orientation="Horizontal">
        <Button x:Name="Button24" Content="test24"/>
        <TextBlock x:Name="Tb24" Text="time"/>
      </StackPanel>
      <StackPanel Orientation="Horizontal">
        <Button x:Name="Button25" Content="test25"/>
        <TextBlock x:Name="Tb25" Text="time"/>
      </StackPanel>



    </StackPanel>
  </Grid>
</Window>

 

MainWindow.xaml.cs

using System;
using System.Threading.Tasks;
using System.Windows;
using System.Windows.Controls;
using System.Diagnostics;
using System.Numerics;
using System.Threading;
using System.Collections.Concurrent;

namespace _20200210_配列の分散_VectorDot
{
    /// <summary>
    /// Interaction logic for MainWindow.xaml
    /// </summary>
    public partial class MainWindow : Window
    {
        private byte[] MyByteAry;
        //private int[] MyIntAry;
        //private long[] MyLongAry;
        private const int LOOP_COUNT = 100;
        private const int ELEMENT_COUNT = 1_000_000;
        private double MyAverage;

        public MainWindow()
        {
            InitializeComponent();



            MyTextBlock.Text = $"byte型配列の値の分散、要素数{ELEMENT_COUNT.ToString("N0")}の分散を{LOOP_COUNT}回求める";
            MyTextBlockVectorCount.Text = $"Vector<long>.Count = {Vector<long>.Count}";
            string str = $"VectorCount : Long={Vector<long>.Count}, Double={Vector<double>.Count}, int={Vector<int>.Count}, flort={Vector<float>.Count}, short={Vector<short>.Count}, byte={Vector<byte>.Count}";
            MyTextBlockVectorCount.Text = str;
            MyTextBlockCpuThreadCount.Text =$"CPUスレッド数:{Environment.ProcessorCount.ToString()} thread";
            //MyIntAry = Enumerable.Range(1, ELEMENT_COUNT).ToArray();//連番値
            //MyIntAry = Enumerable.Repeat(1, ELEMENT_COUNT).ToArray();//全値1
            //MyLongAry = new long[MyIntAry.Length];//long型配列作成
            //MyIntAry.CopyTo(MyLongAry, 0);
            MyInitialize();


            Button1.Click += (s, e) => MyExe(Test01_Double_ForLoop, Tb1, MyByteAry);
            Button2.Click += (s, e) => MyExe(Test02_Integer_ForLoop, Tb2, MyByteAry);
            Button3.Click += (s, e) => MyExe(Test03_FloatVectorSubtractDot, Tb3, MyByteAry);
            Button4.Click += (s, e) => MyExe(Test04_DoubleVectorSubtractDot, Tb4, MyByteAry);
            Button5.Click += (s, e) => MyExe(Test05_IntegerVectorSubtractDot, Tb5, MyByteAry);
            Button6.Click += (s, e) => MyExe(Test06_ByteVectorSubtractDot_Overflow, Tb6, MyByteAry);
            Button7.Click += (s, e) => MyExe(Test07_ShortVectorSubtractDot_Overflow, Tb7, MyByteAry);
            Button8.Click += (s, e) => MyExe(Test08_FloatVector4, Tb8, MyByteAry);
            Button9.Click += (s, e) => MyExe(Test09_IntegerVectorDot, Tb9, MyByteAry);
            Button10.Click += (s, e) => MyExe(Test10_DoubleVectorDot, Tb10, MyByteAry);

            Button11.Click += (s, e) => MyExe(Test11_Double_ForLoop, Tb11, MyByteAry);
            Button12.Click += (s, e) => MyExe(Test12_Integer_ForLoop, Tb12, MyByteAry);
            Button13.Click += (s, e) => MyExe(Test13_FloatVectorDot, Tb13, MyByteAry);
            Button14.Click += (s, e) => MyExe(Test14_DoubleVectorDot, Tb14, MyByteAry);
            Button15.Click += (s, e) => MyExe(Test15_IntegerVectorDot, Tb15, MyByteAry);
            Button16.Click += (s, e) => MyExe(Test16_ByteVectorDot_Overflow, Tb16, MyByteAry);
            Button17.Click += (s, e) => MyExe(Test17_ShortVectorDot_Overflow, Tb17, MyByteAry);
            Button18.Click += (s, e) => MyExe(Test18_FloatVector4, Tb18, MyByteAry);

            Button19.Click += (s, e) => MyExe(Test19_Byte_ushort_uintVectorDot, Tb19, MyByteAry);
            Button20.Click += (s, e) => MyExe(Test20_Byte_ushort_uintVectorDot, Tb20, MyByteAry);
        }
        private void MyInitialize()
        {
            MyByteAry = new byte[ELEMENT_COUNT];
            var r = new Random();
            r.NextBytes(MyByteAry);
            //要素の平均値
            //MyByteAry = new byte[] { 20, 21, 7, 12 };
            MyAverage = GetAverage(MyByteAry);
        }

        private double Test01_Double_ForLoop(byte[] ary)
        {
            //平均との差(偏差)の2乗を合計
            double total = 0;
            for (int i = 0; i < ary.Length; i++)
            {
                total += Math.Pow(ary[i] - MyAverage, 2.0);
            }
            //合計 / 要素数 = 分散
            return total / ary.Length;
        }
        private double Test02_Integer_ForLoop(byte[] ary)
        {
            //平均との差の2乗を合計
            long total = 0;
            int average = (int)MyAverage;
            int ii;//ループの外に出したほうが誤差程度に速い
            for (int i = 0; i < ary.Length; i++)
            {
                //total += (int)Math.Pow(ary[i] - average, 2);
                //total += (ary[i] - average) * (ary[i] - average);//こっちのほうが↑より10倍以上速い
                ii = ary[i] - average;
                total += ii * ii;
            }
            //合計 / 要素数 = 分散
            return total / (double)ary.Length;
        }

        //Vector<float>で計算
        private double Test03_FloatVectorSubtractDot(byte[] ary)
        {
            var vAverage = new Vector<float>((float)MyAverage);
            int simdLength = Vector<float>.Count;
            int lastIndex = ary.Length - (ary.Length % simdLength);
            Vector<float> v;
            var ss = new float[simdLength];
            double total = 0;
            for (int i = 0; i < lastIndex; i += simdLength)
            {
                for (int j = 0; j < simdLength; j++)
                {
                    ss[j] = ary[i + j];
                }
                //平均との差
                v = System.Numerics.Vector.Subtract(vAverage, new Vector<float>(ss));
                //差の2乗を合計
                total += System.Numerics.Vector.Dot(v, v);
            }
            return total / ary.Length;
        }

        //Vector<double>で計算
        private double Test04_DoubleVectorSubtractDot(byte[] ary)
        {
            var vAverage = new Vector<double>(MyAverage);
            int simdLength = Vector<double>.Count;
            int lastIndex = ary.Length - (ary.Length % simdLength);
            Vector<double> v;
            var ss = new double[simdLength];
            double total = 0;
            for (int i = 0; i < lastIndex; i += simdLength)
            {
                for (int j = 0; j < simdLength; j++)
                {
                    ss[j] = ary[i + j];
                }
                //平均との差
                v = System.Numerics.Vector.Subtract(vAverage, new Vector<double>(ss));
                //差の2乗を合計
                total += System.Numerics.Vector.Dot(v, v);
            }
            return total / ary.Length;
        }

        //Vector<int>で計算
        private double Test05_IntegerVectorSubtractDot(byte[] ary)
        {
            var vAverage = new Vector<int>((int)MyAverage);
            int simdLength = Vector<int>.Count;
            int lastIndex = ary.Length - (ary.Length % simdLength);
            Vector<int> v;
            var ss = new int[simdLength];
            double total = 0;
            for (int i = 0; i < lastIndex; i += simdLength)
            {
                for (int j = 0; j < simdLength; j++)
                {
                    ss[j] = ary[i + j];
                }
                //平均との差
                v = System.Numerics.Vector.Subtract(vAverage, new Vector<int>(ss));
                //差の2乗を合計
                total += System.Numerics.Vector.Dot(v, v);
            }
            return total / ary.Length;
        }

        //Vector<byte>で計算、ドット積でオーバーフロー
        private double Test06_ByteVectorSubtractDot_Overflow(byte[] ary)
        {
            var vAverage = new Vector<byte>((byte)MyAverage);
            int simdLength = Vector<byte>.Count;
            int lastIndex = ary.Length - (ary.Length % simdLength);
            Vector<byte> v;
            double total = 0;

            for (int i = 0; i < lastIndex; i += simdLength)
            {
                //平均との差は、byte型にはマイナスがないから間違った値になる
                v = System.Numerics.Vector.Subtract(vAverage, new Vector<byte>(ary, i));
                //差の2乗を合計
                total += System.Numerics.Vector.Dot(v, v);//オーバーフロー
            }
            return total / ary.Length;
        }

        //Vector<short>で計算はドット積でオーバーフロー
        private double Test07_ShortVectorSubtractDot_Overflow(byte[] ary)
        {
            var vAverage = new Vector<short>((short)MyAverage);
            int simdLength = Vector<short>.Count;
            int lastIndex = ary.Length - (ary.Length % simdLength);
            Vector<short> v;
            long total = 0;
            var ss = new short[simdLength];
            for (int i = 0; i < lastIndex; i += simdLength)
            {
                for (int j = 0; j < simdLength; j++)
                {
                    ss[j] = ary[i + j];
                }
                //平均との差
                v = System.Numerics.Vector.Subtract(vAverage, new Vector<short>(ss));
                //差の2乗を合計
                total += System.Numerics.Vector.Dot(v, v);//オーバーフロー
            }
            return (double)total / ary.Length;
        }

        //Vector4
        private double Test08_FloatVector4(byte[] ary)
        {
            int lastIndex = ary.Length - (ary.Length % 4);
            var vAverage = new Vector4((float)MyAverage);
            Vector4 v;//= new Vector4();
            double total = 0;
            for (int i = 0; i < lastIndex; i += 4)
            {
                //平均との差
                v = Vector4.Subtract(new Vector4(ary[i], ary[i + 1], ary[i + 2], ary[i + 3]), vAverage);
                //差の2乗を合計
                total += Vector4.Dot(v, v);
            }
            return total / ary.Length;
        }

        //平均との差は普通のループで、int型
        private double Test09_IntegerVectorDot(byte[] ary)
        {
            int average = (int)MyAverage;
            int simdLength = Vector<int>.Count;
            int lastIndex = ary.Length - (ary.Length % simdLength);
            Vector<int> v;
            long total = 0;
            int[] ii = new int[simdLength];
            for (int i = 0; i < lastIndex; i += simdLength)
            {
                //平均との差を配列に入れる
                for (int j = 0; j < simdLength; j++)
                {
                    ii[j] = average - ary[i + j];
                }
                //差の配列からVector作成して2乗和を合計していく
                v = new Vector<int>(ii);
                total += System.Numerics.Vector.Dot(v, v);
            }
            return total / (double)ary.Length;
        }

        //平均との差は普通のループで、double型
        private double Test10_DoubleVectorDot(byte[] ary)
        {
            int simdLength = Vector<double>.Count;
            int lastIndex = ary.Length - (ary.Length % simdLength);
            Vector<double> v;
            double total = 0;
            double[] ii = new double[simdLength];
            for (int i = 0; i < lastIndex; i += simdLength)
            {
                //平均との差の配列
                for (int j = 0; j < simdLength; j++)
                {
                    ii[j] = MyAverage - ary[i + j];
                }
                //差の配列からVector作成してドット積
                v = new Vector<double>(ii);
                total += System.Numerics.Vector.Dot(v, v);
            }
            return total / ary.Length;
        }





        #region 分散 = 2乗和の平均 - 平均の2乗
        //分散の意味と求め方、分散公式の使い方
        //https://sci-pursuit.com/math/statistics/variance.html



        private double Test11_Double_ForLoop(byte[] ary)
        {
            //2乗和
            double total = 0;
            for (int i = 0; i < ary.Length; i++)
            {
                total += Math.Pow(ary[i], 2.0);
            }
            //2乗和の平均
            total /= ary.Length;
            //2乗和の平均 - 平均の2乗
            return total - Math.Pow(MyAverage, 2.0);
        }

        private double Test12_Integer_ForLoop(byte[] ary)
        {

            double total = 0;
            int ii;
            for (int i = 0; i < ary.Length; i++)
            {
                ii = ary[i];
                total += ii * ii;
            }
            total /= ary.Length;

            return total - (MyAverage * MyAverage);
        }

        //float
        private double Test13_FloatVectorDot(byte[] ary)
        {
            int simdLength = Vector<float>.Count;
            int lastIndex = ary.Length - (ary.Length % simdLength);
            Vector<float> v;
            var ss = new float[simdLength];
            double total = 0;
            for (int i = 0; i < lastIndex; i += simdLength)
            //配列を作成してVector作成してドット積
            {
                for (int j = 0; j < simdLength; j++)
                {
                    ss[j] = ary[i + j];
                }
                v = new Vector<float>(ss);
                total += System.Numerics.Vector.Dot(v, v);
            }
            //2乗和の平均 - 平均の2乗
            return (total / ary.Length) - (MyAverage * MyAverage);
        }


        //Vector<double>で計算
        private double Test14_DoubleVectorDot(byte[] ary)
        {
            int simdLength = Vector<double>.Count;
            int lastIndex = ary.Length - (ary.Length % simdLength);
            Vector<double> v;
            var ss = new double[simdLength];
            double total = 0;
            for (int i = 0; i < lastIndex; i += simdLength)
            {
                for (int j = 0; j < simdLength; j++)
                {
                    ss[j] = ary[i + j];
                }
                v = new Vector<double>(ss);
                total += System.Numerics.Vector.Dot(v, v);
            }
            return (total / ary.Length) - (MyAverage * MyAverage);
        }

        //Vector<int>で計算
        private double Test15_IntegerVectorDot(byte[] ary)
        {
            int simdLength = Vector<int>.Count;
            int lastIndex = ary.Length - (ary.Length % simdLength);
            Vector<int> v;
            var ss = new int[simdLength];
            double total = 0;
            for (int i = 0; i < lastIndex; i += simdLength)
            {
                for (int j = 0; j < simdLength; j++)
                {
                    ss[j] = ary[i + j];
                }
                v = new Vector<int>(ss);
                total += System.Numerics.Vector.Dot(v, v);
            }
            return (total / ary.Length) - (MyAverage * MyAverage);
        }

        //Vector<byte>で計算、ドット積でオーバーフロー
        private double Test16_ByteVectorDot_Overflow(byte[] ary)
        {
            int simdLength = Vector<byte>.Count;
            int lastIndex = ary.Length - (ary.Length % simdLength);
            Vector<byte> v;
            double total = 0;

            for (int i = 0; i < lastIndex; i += simdLength)
            {
                v = new Vector<byte>(ary, i);
                total += System.Numerics.Vector.Dot(v, v);//オーバーフロー
            }
            return (total / ary.Length) - (MyAverage * MyAverage);
        }

        //Vector<short>で計算はドット積でオーバーフロー
        private double Test17_ShortVectorDot_Overflow(byte[] ary)
        {
            int simdLength = Vector<short>.Count;
            int lastIndex = ary.Length - (ary.Length % simdLength);
            Vector<short> v;
            long total = 0;
            var ss = new short[simdLength];
            for (int i = 0; i < lastIndex; i += simdLength)
            {
                for (int j = 0; j < simdLength; j++)
                {
                    ss[j] = ary[i + j];
                }
                v = new Vector<short>(ss);
                total += System.Numerics.Vector.Dot(v, v);//オーバーフロー
            }
            return (total / ary.Length) - (MyAverage * MyAverage);
        }

        //Vector4
        private double Test18_FloatVector4(byte[] ary)
        {
            int lastIndex = ary.Length - (ary.Length % 4);;
            Vector4 v;
            double total = 0;
            for (int i = 0; i < lastIndex; i += 4)
            {
                v = new Vector4(ary[i], ary[i + 1], ary[i + 2], ary[i + 3]);
                total += Vector4.Dot(v, v);
            }
            return (total / ary.Length) - (MyAverage * MyAverage);
        }



        //Vector<byte>をWidenでVector<ushort>にしてドット積計算はオーバーフローだったので
        //Vector<ushort>からさらにVector<uint>にしてドット積
        private double Test19_Byte_ushort_uintVectorDot(byte[] ary)
        {
            int simdLength = Vector<byte>.Count;
            int lastIndex = ary.Length - (ary.Length % simdLength);
            Vector<byte> v;
            double total = 0;
            Vector<ushort> v1; Vector<ushort> v2;
            Vector<uint> vv1; Vector<uint> vv2; Vector<uint> vv3; Vector<uint> vv4;
            for (int i = 0; i < lastIndex; i += simdLength)
            {
                v = new Vector<byte>(ary, i);
                System.Numerics.Vector.Widen(v, out v1, out v2);
                System.Numerics.Vector.Widen(v1, out vv1, out vv2);
                System.Numerics.Vector.Widen(v2, out vv3, out vv4);
                total += System.Numerics.Vector.Dot(vv1, vv1);
                total += System.Numerics.Vector.Dot(vv2, vv2);
                total += System.Numerics.Vector.Dot(vv3, vv3);
                total += System.Numerics.Vector.Dot(vv4, vv4);
            }
            return (total / ary.Length) - (MyAverage * MyAverage);
        }

        //↑と同じ、インライン化しただけ
        private double Test20_Byte_ushort_uintVectorDot(byte[] ary)
        {
            int simdLength = Vector<byte>.Count;
            int lastIndex = ary.Length - (ary.Length % simdLength);
            Vector<byte> v;
            double total = 0;
            for (int i = 0; i < lastIndex; i += simdLength)
            {
                v = new Vector<byte>(ary, i);
                System.Numerics.Vector.Widen(v, out Vector<ushort> v1, out Vector<ushort> v2);
                System.Numerics.Vector.Widen(v1, out Vector<uint> vv1, out Vector<uint> vv2);
                System.Numerics.Vector.Widen(v2, out Vector<uint> vv3, out Vector<uint> vv4);
                total += System.Numerics.Vector.Dot(vv1, vv1);
                total += System.Numerics.Vector.Dot(vv2, vv2);
                total += System.Numerics.Vector.Dot(vv3, vv3);
                total += System.Numerics.Vector.Dot(vv4, vv4);
            }
            return (total / ary.Length) - (MyAverage * MyAverage);
        }


        #endregion









        //平均値
        private double GetAverage(byte[] ary)
        {
            long total = 0;
            Parallel.ForEach(Partitioner.Create(0, ary.Length),
                (range) =>
                {
                    long subtotal = 0;
                    for (int i = range.Item1; i < range.Item2; i++)
                    {
                        subtotal += ary[i];
                    }
                    Interlocked.Add(ref total, subtotal);
                });
            return total / (double)ary.Length;
        }










        private void MyExe(Func<byte[], double> func, TextBlock tb, byte[] ary)
        {
            double total = 0;
            var sw = new Stopwatch();
            sw.Start();
            for (int i = 0; i < LOOP_COUNT; i++)
            {
                total = func(ary);
            }
            sw.Stop();
            tb.Text = $"処理時間:{sw.Elapsed.TotalSeconds.ToString("00.000")}秒  分散 = {total.ToString("F4")}  {System.Reflection.RuntimeReflectionExtensions.GetMethodInfo(func).Name}";
        }



    }

}

 

関連記事

次回は6日後

gogowaten.hatenablog.com

 

前回は昨日

gogowaten.hatenablog.com