午後わてんのブログ

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

int型配列の合計値マルチスレッド編

前回は

シングルスレッドだけでの計算だった

今回はマルチスレッド

1から100万1までの連番の合計を1000回求めるのにかかった時間

合計値の正解は500001500001

計測アプリ

f:id:gogowaten:20200208145231p:plain

ボタンを押して待つだけ、test2とtest11だけは極端に遅い、test5とtest21はオーバーフローで間違った値が表示される

 

アプリダウンロード

20200207_int配列の値の合計マルチスレッド.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#

 

結果をグラフで

f:id:gogowaten:20200208141531p:plain

一番上の赤のグラフが普通のForでループして足し算したものはシングルスレッド、これを基準にして、それ以外はマルチスレッドでいろいろ。

 

f:id:gogowaten:20200208141911p:plain

基準の速度を1としたときの相対速度、最大で13倍も速くなっているのがあるけど、これはめんどくさい条件下での値。それ以外だと3倍くらい速くなった

 

 

 

 

f:id:gogowaten:20200208143403p:plain

基準にするシングルスレッドのFor、普通

 

f:id:gogowaten:20200208143656p:plain

単純にParallelForにするだけだと同時に足し算になったときに値がおかしくなるので、排他処理してくれるInterlockedのAddを使って足し算。

これはものすごく遅くなって、基準の30倍以上の処理時間になった。Interlocked.Addってのがとても重い処理みたいなんだけど必要

 

Interlocked

f:id:gogowaten:20200208144233p:plain

今回だとtotalがこの、複数のスレッドで共有される変数、なんだと思う。それに対して複数スレッドから同時にアクセスできないように排他処理をしてくれるみたい

 

Interlocked.Add

f:id:gogowaten:20200208144243p:plain

排他処理での足し算専用のメソッド、素晴らしい。今回のテストではほとんどのメソッドで使っている。

 

 

f:id:gogowaten:20200208145850p:plain

LINQはAsParallelをつけると自動でマルチスレッドで処理してくれるので楽ちん。基準の3割程度の速度しかないけどAsParallelをつけないSumより3倍速くなっている!

 

ParallelForEachとパーティションローカル変数

docs.microsoft.com

f:id:gogowaten:20200208150717p:plain

Parallel.ForEachを使った方法

 

f:id:gogowaten:20200208151423p:plain

 

docs.microsoft.com

これの

Parallel.ForEach<TSource,TLocal>(

IEnumerable<TSource>,

Func<TLocal>,

Func<TSource,ParallelLoopState,TLocal,TLocal>,

Action<TLocal>)

これが 

private long Test30_ParallelForEach(int[] ary)
{
    long total = 0;
    Parallel.ForEach<int, long>(
        ary,
        () => 0,
        (item, state, subtotal) => { return subtotal += item; },
        (x) => Interlocked.Add(ref total, x));
    return total;
}

 こう

ParallelForEachは処理する配列をパーティションで区切って、それぞれの配列をスレッドに割り当てる感じ?スレッドごとに変数を持つことができて、それがパーティションローカル変数で、それはスレッドごとなので干渉することなく処理できる、つまり排他処理できるってことかなあ。

今回だとパーティションローカル変数にあたるのはsubtotal?

 

第1引数に処理する配列を渡すのはわかる

第2引数は"各タスクのローカルデータの初期状態を返す関数デリゲート"これは変数に初期値を与えるみたいで今回は足し算なので0

第3引数は"2回の反復処理につき1回呼び出されるデリゲート"ここが実際のループの処理みたい、引数は3つあって順番に各要素、ループのステータス、処理の結果用?今回はループのステータスは使っていない、結果用の変数に各要素を足し算しているだけ

第4引数は"各タスクのローカル状態に対して最後の操作を行うデリゲート"ここで集計している。スレッドごとのローカル変数が返ってくるから、それを集計している感じ

 よくわからんけど結果は基準の半分の速度

 

ParallelForとスレッドローカル変数

f:id:gogowaten:20200208163628p:plain

さっきのが

ParallelForEachのパーティションローカル変数

で、これは

ParallelForのスレッドローカル変数

似ている、速度も同じくらいだけど少し速くて基準の0.6倍速

 

Taskでマルチ

f:id:gogowaten:20200208164256p:plain

CPUのスレッド数(8スレッド)で配列を分割してTaskでマルチ、分割にはLINQのSkipとTakeを使用

結果は基準の0.4倍速でイマイチ

 

Partitionerクラスを使ってマルチ

docs.microsoft.com

こちらを参考にして書いてみたのが

f:id:gogowaten:20200208164828p:plain

結果は今回のテストでは一番遅くて基準の30倍以上の処理時間になったけど、これは書き方というかPartitionerの使い方が良くなかったみたい

 

Partitionerクラス

f:id:gogowaten:20200208165542p:plain

 

Createで作成

f:id:gogowaten:20200208165549p:plain

これをParallelForEachの第1引数に渡して

第2引数はまたデリゲートで、その第1引数は区切られた配列のインデックスの最初と最後が入るみたいで、一時停止してみてみると
f:id:gogowaten:20200208165300p:plain

これかなあ

f:id:gogowaten:20200208170230p:plain

こんななってた、ここではこのループの中で直接Interlockedを使って足し算したのが良くなかった、これだと区切ったのが意味なくなっている

 

正しい使い方はこうだった

www.it-swarm.dev

2019/06/15 Theodor Zoulias=サンの投稿をそのままコピペ

f:id:gogowaten:20200208171548p:plain

基準の3倍以上の速度!やっとシングルより速くなった!

さっきのPartitionerを使ったのと違うのは、Forの外側にローカル変数としてsubtotalを置いただけ。これはParallelForとスレッドローカル変数を使った方法に似ているけど、処理速度はぜんぜん違うねえ

これに気を良くしてパーティションの区切り方やParallelのオプションで最大スレッド数とかを変えてみたけど大きな変化はなかったのが

f:id:gogowaten:20200208194521p:plain

このへん、Test70から73は誤差程度の差

f:id:gogowaten:20200208194529p:plain

区切り指定あり+スレッド数指定ありが気持ち遅くて、それ以外は誤差

 

 

System.Numerics.Vector.Add(SIMD)で足し算をTaskでマルチスレッド

f:id:gogowaten:20200208195147p:plain

遅くなった…ParallelForやParallelForEachでローカル変数を使った方法よりも遅い

f:id:gogowaten:20200208195418p:plain

普通のForをTaskでマルチにしたときよりも僅かに遅いとか、かなり残念な結果

 

Partitioner+VectorAdd

f:id:gogowaten:20200208200122p:plain

基準よりは速くなったけどねえ、やっぱりintからlongに変換するところがネックになる。

じゃあ変換しなくてもいいようにlong型配列ならどうなるかなと

f:id:gogowaten:20200208200742p:plain

速くはなったけど2倍にとどまる

じゃあint型配列をint型で集計なら

f:id:gogowaten:20200208200933p:plain

一気に13倍まで速くなったw

前回のシングル同士では5~6倍だったのでマルチスレッド化で、さらに2倍速くなったことになる。合計値がオーバフローしないことがわかっているならこれがいい

 

 

int型配列の合計値

普通のForループ

速度:★★★

書く手間:★★★

普通だけどこれが速い

 

LINQのSum

速度:★(1/10倍~1/3倍)

書く手間:★

Sumだけなら1/10、どうせならAsParallelをつけてマルチスレッドにして1/3倍速

 

ParallelForEach+Partitioner+パーティションローカル変数

速度:★★★★★★★★★★(3倍以上)

書く手間:★★★★★

Partitionerクラスを使って区切った配列をParallelForEachに渡して、スレッドごとの小計はパーティションローカル変数(スレッドローカル変数)を使って、計算自体は普通のForを使う、合計は排他処理のInterlockedAddで行う

手間はかかるけど見合った結果だと思うし、コア数の多いCPUならもっと速くなるはず

 

VectorAdd+ParallelForEach+Partitioner+パーティションローカル変数

速度:★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★(条件付き13倍)

書く手間:★★★★★★★

合計値が配列の型でもオーバフローしないことがわかっているなら、VectorAddを使って計算、それ以外はさっきの3倍速の方法と同じ

 

2020/02/11追記ここから

VectorWiden+VectorAdd+ParallelForEach+Partitioner+パーティションローカル変数

速度:★★★★★★★★★★★★★★★★★★★(6倍以上)

書く手間:★★★★

多分これが一番早いと思います

gogowaten.hatenablog.com

2020/02/11追記ここまで

 

日記

ここ1周間は小学1年生かよってくらい足し算ばかりしていた、いろいろな方法があるねえ

使っているCPUが2年前のミドルローレンジのRyzen 5 2400Gだから、Zen2コアの第3世代RyzenIntelの新し目のCPUだと同じコア数でもVectorAddを使ったところはもっと速いかも?マルチスレッドならコア数が多いほうが速いはずだけどどれくらい速くなるんだろう、たとえばこれとか

pc.watch.impress.co.jp

Windowsベンチマークアプリでも64コア128スレッドには対応が追いついていないのんね、64コアだもんなあ、2400Gの16個分!ってことはレモンだと…?

 

 

コード全部

XAML

<Window x:Class="_20200207_int配列の値の合計マルチスレッド.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:_20200207_int配列の値の合計マルチスレッド"
        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"/>
      <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>

 

 

XAML.cs

using System;
using System.Linq;
using System.Threading.Tasks;
using System.Windows;
using System.Windows.Controls;
using System.Diagnostics;
using System.Collections.Concurrent;
using System.Numerics;
using System.Threading;
//c# — C#で整数の配列を合計する方法
//https://www.it-swarm.dev/ja/c%23/c%EF%BC%83%E3%81%A7%E6%95%B4%E6%95%B0%E3%81%AE%E9%85%8D%E5%88%97%E3%82%92%E5%90%88%E8%A8%88%E3%81%99%E3%82%8B%E6%96%B9%E6%B3%95/968057375/


namespace _20200207_int配列の値の合計マルチスレッド
{
    /// <summary>
    /// Interaction logic for MainWindow.xaml
    /// </summary>
    public partial class MainWindow : Window
    {
        private int[] MyIntAry;
        private long[] MyLongAry;
        private const int LOOP_COUNT = 1000;
        private const int ELEMENT_COUNT = 1_000_001;

        public MainWindow()
        {
            InitializeComponent();

            MyTextBlock.Text = $"配列の値の合計、要素数{ELEMENT_COUNT.ToString("N0")}の合計を{LOOP_COUNT}回求める処理時間";
            MyTextBlockVectorCount.Text = $"Vector<long>.Count = {Vector<long>.Count}";
            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);


            Button1.Click += (s, e) => MyExe(Test0_For, Tb1);
            Button2.Click += (s, e) => MyExe(Test10_ParallelFor, Tb2);////
            Button3.Click += (s, e) => MyExe(Test20_AsParallelSum, Tb3);
            Button4.Click += (s, e) => MyExe(Test30_ParallelForEach, Tb4);
            Button5.Click += (s, e) => MyExe(Test40_ParallelForThreadLocalVariable, Tb5);
            Button6.Click += (s, e) => MyExe(Test4x_ParallelForThreadLocalVariable_Overflow, Tb6);
            Button7.Click += (s, e) => MyExe(Test41_ParallelForThreadLocalVariable, Tb7);
            Button8.Click += (s, e) => MyExe(Test42_ParallelForThreadLocalVariable, Tb8);
            Button9.Click += (s, e) => MyExe(Test43_ParallelForThreadLocalVariable, Tb9);
            Button10.Click += (s, e) => MyExe(Test50_TaskLinqSkipTake, Tb10);
            Button11.Click += (s, e) => MyExe(Test60_ParallelForEachPartitioner, Tb11);////
            Button12.Click += (s, e) => MyExe(Test70_ParallelForEachPartitionerThreadLocalVariable, Tb12);
            Button13.Click += (s, e) => MyExe(Test71_ParallelForEachPartitionerThreadLocalVariable, Tb13);
            Button14.Click += (s, e) => MyExe(Test72_ParallelForEachPartitionerThreadLocalVariable, Tb14);
            Button15.Click += (s, e) => MyExe(Test73_ParallelForEachPartitionerThreadLocalVariable, Tb15);
            Button16.Click += (s, e) => MyExe(Test80_TaskLinqSkipTake_Vector, Tb16);
            Button17.Click += (s, e) => MyExe(Test81_TaskVectorAdd_ForInt, Tb17);
            Button18.Click += (s, e) => MyExe(Test82_TaskVectorAdd_ForLong, Tb18);
            Button19.Click += (s, e) => MyExe(Test90_ParallelForEachPartitioner_Vector, Tb19);
            Button20.Click += (s, e) => MyExe(Test91_ParallelForEachPartitioner_Vector, Tb20);
            Button21.Click += (s, e) => MyExe(Test9x_ParallelForEachPartitionerVector_Overflow, Tb21);

        }

        //普通のfor、シングルスレッド
        private long Test0_For(int[] Ary)
        {
            long total = 0;
            for (int i = 0; i < Ary.Length; i++)
            {
                total += Ary[i];
            }
            return total;
        }
        //Parallel.For(マルチスレッド
        //かなり遅い、必要ない
        private long Test10_ParallelFor(int[] Ary)
        {
            long total = 0;
            Parallel.For(0, Ary.Length, n =>
            {
                //total += Ary[n];//不正確
                Interlocked.Add(ref total, Ary[n]);//遅い
            });
            return total;
        }

        //LINQのSumをマルチスレッドで
        private long Test20_AsParallelSum(int[] ary)
        {
            return ary.AsParallel().Sum(i => (long)i);
        }


        //        方法: パーティション ローカル変数を使用する Parallel.ForEach ループを記述する | Microsoft Docs
        //https://docs.microsoft.com/ja-jp/dotnet/standard/parallel-programming/how-to-write-a-parallel-foreach-loop-with-partition-local-variables
        //Parallel.ForEach、スレッドローカル変数使用版、最初に型宣言
        private long Test30_ParallelForEach(int[] ary)
        {
            long total = 0;
            Parallel.ForEach<int, long>(
                ary,
                () => 0,
                (item, state, subtotal) => { return subtotal += item; },
                (x) => Interlocked.Add(ref total, x));
            return total;
        }
        //Parallel.ForEach、スレッドローカル変数使用版、変数個別に型宣言
        //必要ない、未使用
        private long Test31_ParallelForEach(int[] ary)
        {
            long total = 0;
            Parallel.ForEach(ary,
                () => 0,
                (item, state, subtotal) => { return subtotal += item; },
                (long x) => Interlocked.Add(ref total, x));
            return total;
        }


        //        方法: スレッド ローカル変数を使用する Parallel.For ループを記述する | Microsoft Docs
        //https://docs.microsoft.com/ja-jp/dotnet/standard/parallel-programming/how-to-write-a-parallel-for-loop-with-thread-local-variables
        //Parallel.For、スレッドローカル変数使用版
        private long Test40_ParallelForThreadLocalVariable(int[] ary)
        {
            long total = 0;
            Parallel.For<long>(0, ary.Length,
                () => 0,
                (j, loop, subtotal) => { return subtotal += ary[j]; },
                (x) => Interlocked.Add(ref total, x));
            return total;
        }
        //Parallel.For、スレッドローカル変数使用版、int型のままなので不正確
        //必要ない
        private long Test4x_ParallelForThreadLocalVariable_Overflow(int[] ary)
        {
            long total = 0;
            Parallel.For(0, ary.Length,
                () => 0,
                (j, loop, subtotal) => { return subtotal += ary[j]; },
                (x) => Interlocked.Add(ref total, x));
            return total;
        }
        //Parallel.For、スレッドローカル変数使用版、変数個別に型宣言
        //必要ない
        private long Test41_ParallelForThreadLocalVariable(int[] ary)
        {
            long total = 0;
            Parallel.For(0, ary.Length,
                () => 0,
                (int j, ParallelLoopState loop, long subtotal) => { return subtotal += ary[j]; },
                (x) => Interlocked.Add(ref total, x));
            return total;
        }
        //Parallel.For、スレッドローカル変数使用版、変数個別に型宣言2
        //必要ない
        private long Test42_ParallelForThreadLocalVariable(int[] ary)
        {
            long total = 0;
            Parallel.For(0, ary.Length,
                () => 0,
                (j, loop, subtotal) => { return subtotal += ary[j]; },
                (long x) => Interlocked.Add(ref total, x));
            return total;
        }
        //Parallel.For、スレッドローカル変数使用版
        //ParallelOptionsでスレッド数指定したけど速度や結果に変化なし
        //必要ない
        private long Test43_ParallelForThreadLocalVariable(int[] ary)
        {
            long total = 0;
            ParallelOptions options = new ParallelOptions();
            options.MaxDegreeOfParallelism = Environment.ProcessorCount;//8

            Parallel.For<long>(0, ary.Length,
                options,
                () => 0,
                (j, loop, subtotal) => { return subtotal += ary[j]; },
                (x) => Interlocked.Add(ref total, x));
            return total;
        }



        //Taskでマルチスレッド、CPUスレッド数で配列を分割、それぞれをシングルで処理
        private long Test50_TaskLinqSkipTake(int[] ary)
        {
            long total = 0;
            int cpuThread = Environment.ProcessorCount;
            int windowSize = ary.Length / cpuThread;

            long[] neko = Task.WhenAll(Enumerable.Range(0, cpuThread).Select(x =>
                Task.Run(() =>
                {
                    var ii = ary.Skip(windowSize * x).Take(windowSize).ToArray();//割当範囲作成
                    return Test0_For(ii);//シングルで処理
                }))).GetAwaiter().GetResult();
            total = neko.Sum();
            int lastIndex = ary.Length - (ary.Length % windowSize);
            for (int i = lastIndex; i < ary.Length; i++)
            {
                total += ary[i];
            }
            return total;
        }


        //        方法: 小さいループ本体を高速化する | Microsoft Docs
        //https://docs.microsoft.com/ja-jp/dotnet/standard/parallel-programming/how-to-speed-up-small-loop-bodies
        //Parallel.ForEach、Partitionerで配列を区切って各スレッドに割り当てる?
        //区切り位置指定したけど、しなくても速度に変化なかった
        //かなり遅い
        //必要ない
        private long Test60_ParallelForEachPartitioner(int[] ary)
        {
            long total = 0;
            int windowSize = ary.Length / Environment.ProcessorCount;
            var rangePartitioner = Partitioner.Create(0, ary.Length, windowSize);//

            //over load no6
            Parallel.ForEach(rangePartitioner, (range, loopState) =>
            {
                for (int i = range.Item1; i < range.Item2; i++)
                {
                    Interlocked.Add(ref total, ary[i]);//これなら正確だけど3倍くらい遅い
                    //total += ary[i];//不正確
                }
            });
            return total;
        }



        //        c# — C#で整数の配列を合計する方法
        //https://www.it-swarm.dev/ja/c%23/c%EF%BC%83%E3%81%A7%E6%95%B4%E6%95%B0%E3%81%AE%E9%85%8D%E5%88%97%E3%82%92%E5%90%88%E8%A8%88%E3%81%99%E3%82%8B%E6%96%B9%E6%B3%95/968057375/
        //最速
        //Parallel.ForEachとPartitionerとパーティションローカル変数?
        //Partitionerでの区切り位置指定
        private long Test70_ParallelForEachPartitionerThreadLocalVariable(int[] ary)
        {
            long total = 0;//(合計)
            int windowSize = ary.Length / Environment.ProcessorCount;
            var rangePartitioner = Partitioner.Create(0, ary.Length, windowSize);//

            //over load no6
            Parallel.ForEach(rangePartitioner, (range) =>
            {
                long subtotal = 0;//パーティションごとの集計用(小計)
                for (int i = range.Item1; i < range.Item2; i++)
                {
                    subtotal += ary[i];
                }
                Interlocked.Add(ref total, subtotal);//合計する
            });
            return total;
        }
        //Parallel.ForEachとPartitionerとパーティションローカル変数?
        ////Partitionerでの区切り位置指定+ParallelOptionsでスレッド数指定、これだけは少し遅いかな
        private long Test71_ParallelForEachPartitionerThreadLocalVariable(int[] ary)
        {
            long total = 0;
            int windowSize = ary.Length / Environment.ProcessorCount;
            var rangePartitioner = Partitioner.Create(0, ary.Length, windowSize);//
            var options = new ParallelOptions() { MaxDegreeOfParallelism = Environment.ProcessorCount };

            //over load no6
            Parallel.ForEach(rangePartitioner, options, (range) =>
             {
                 long subtotal = 0;
                 for (int i = range.Item1; i < range.Item2; i++)
                 {
                     subtotal += ary[i];
                 }
                 Interlocked.Add(ref total, subtotal);
             });
            return total;
        }
        //Parallel.ForEachとPartitionerとパーティションローカル変数?
        //区切り位置指定もスレッド数指定もなし、にしたけど誤差
        private long Test72_ParallelForEachPartitionerThreadLocalVariable(int[] ary)
        {
            long total = 0;
            var rangePartitioner = Partitioner.Create(0, ary.Length);//            

            //over load no6
            Parallel.ForEach(rangePartitioner, (range) =>
            {
                long subtotal = 0;
                for (int i = range.Item1; i < range.Item2; i++)
                {
                    subtotal += ary[i];
                }
                Interlocked.Add(ref total, subtotal);
            });
            return total;
        }
        //Parallel.ForEachとPartitionerとパーティションローカル変数?
        //区切り位置指定なし+Optionsでスレッド数指定あり、誤差程度に最速かも?
        private long Test73_ParallelForEachPartitionerThreadLocalVariable(int[] ary)
        {
            long total = 0;
            int windowSize = ary.Length / Environment.ProcessorCount;
            var rangePartitioner = Partitioner.Create(0, ary.Length);//
            var options = new ParallelOptions() { MaxDegreeOfParallelism = Environment.ProcessorCount };

            //over load no6
            Parallel.ForEach(rangePartitioner, options, (range) =>
             {
                 long subtotal = 0;
                 for (int i = range.Item1; i < range.Item2; i++)
                 {
                     subtotal += ary[i];
                 }
                 Interlocked.Add(ref total, subtotal);
             });
            return total;
        }



    

        //VectorAdd、intからlongへの変換
        //int型配列からlong型配列への変換は、計算する分だけをその都度変換
        private long TestVectorAddEach(int[] Ary)
        {
            int simdLength = Vector<long>.Count;
            int lastIndex = Ary.Length - (Ary.Length % simdLength);
            var longAry = new long[simdLength];
            var v = new Vector<long>(longAry);

            for (int j = 0; j < lastIndex; j += simdLength)
            {
                for (int i = 0; i < simdLength; i++)
                {
                    longAry[i] = Ary[j + i];
                }
                v = System.Numerics.Vector.Add(v, new Vector<long>(longAry));
            }

            long total = 0;
            for (int i = 0; i < simdLength; i++)
            {
                total += v[i];
            }
            for (int i = lastIndex; i < Ary.Length; i++)
            {
                total += Ary[i];
            }
            return total;
        }
        //TaskとVector
        //配列の分割はLINQのSkipとTakeを使用
        private long Test80_TaskLinqSkipTake_Vector(int[] ary)
        {
            long total = 0;
            int cpuThread = Environment.ProcessorCount;
            int windowSize = ary.Length / cpuThread;

            long[] neko = Task.WhenAll(Enumerable.Range(0, cpuThread).Select(x =>
                Task.Run(() =>
                {
                    var ii = ary.Skip(windowSize * x).Take(windowSize).ToArray();
                    return TestVectorAddEach(ii);
                }))).GetAwaiter().GetResult();
            total = neko.Sum();
            int lastIndex = ary.Length - (ary.Length % windowSize);
            for (int i = lastIndex; i < ary.Length; i++)
            {
                total += ary[i];
            }
            return total;
        }

        //TaskとVector
        //配列の分割はFor
        private long Test81_TaskVectorAdd_ForInt(int[] ary)
        {
            long total = 0;
            int cpuThread = Environment.ProcessorCount;
            int windowSize = ary.Length / cpuThread;

            long[] neko = Task.WhenAll(Enumerable.Range(0, cpuThread).Select(x =>
                Task.Run(() =>
                {
                    var ii = new int[windowSize];
                    for (int i = 0; i < windowSize; i++)
                    {
                        ii[i] = ary[i + (x * windowSize)];
                    }
                    return TestVectorAddEach(ii);
                }))).GetAwaiter().GetResult();
            total = neko.Sum();
            int lastIndex = ary.Length - (ary.Length % windowSize);
            for (int i = lastIndex; i < ary.Length; i++)
            {
                total += ary[i];
            }
            return total;
        }

        //TaskとVector
        //Forで配列を分割時にlong型に変換
        private long Test82_TaskVectorAdd_ForLong(int[] ary)
        {
            long total = 0;
            int cpuThread = Environment.ProcessorCount;
            int windowSize = ary.Length / cpuThread;

            long[] neko = Task.WhenAll(Enumerable.Range(0, cpuThread).Select(x =>
                Task.Run(() =>
                {
                    var ll = new long[windowSize];
                    var p = x * windowSize;
                    for (int i = 0; i < windowSize; i++)
                    {
                        ll[i] = ary[i + p];
                    }
                    return TestLongVectorAdd(ll);
                }))).GetAwaiter().GetResult();
            total = neko.Sum();
            int lastIndex = ary.Length - (ary.Length % windowSize);
            for (int i = lastIndex; i < ary.Length; i++)
            {
                total += ary[i];
            }
            return total;
        }
        private long TestLongVectorAdd(long[] ary)
        {
            int simdLength = Vector<long>.Count;
            int lastIndex = ary.Length - (ary.Length % simdLength);

            var v = new Vector<long>();
            for (int i = 0; i < lastIndex; i += simdLength)
            {
                v = System.Numerics.Vector.Add(v, new Vector<long>(ary, i));
            }
            long total = 0;
            for (int i = 0; i < simdLength; i++)
            {
                total += v[i];
            }
            for (int i = lastIndex; i < ary.Length; i++)
            {
                total += ary[i];
            }
            return total;
        }

        //
        //Parallel.ForEach+Partitioner+Vector.Addを使って計算
        //int型配列
        private long Test90_ParallelForEachPartitioner_Vector(int[] ary)
        {
            long total = 0;
            int windowSize = ary.Length / Environment.ProcessorCount;
            var rangePartitioner = Partitioner.Create(0, ary.Length, windowSize);//
            int simdLength = Vector<long>.Count;
            //over load no6
            Parallel.ForEach(rangePartitioner, (range) =>
            {
                var v = new Vector<long>();
                var l = new long[simdLength];
                int lastIndex = range.Item2 - (range.Item2 % simdLength);
                for (int i = range.Item1; i < lastIndex; i += simdLength)
                {
                    for (int j = 0; j < simdLength; j++)
                    {
                        l[j] = ary[i + j];
                    }
                    v = System.Numerics.Vector.Add(v, new Vector<long>(l));
                }
                long subtotal = 0;
                for (int i = 0; i < simdLength; i++)
                {
                    subtotal += v[i];
                }
                for (int i = lastIndex; i < range.Item2; i++)
                {
                    subtotal += ary[i];
                }
                Interlocked.Add(ref total, subtotal);
            });
            return total;
        }

        //Parallel.ForEach+Partitioner+Vector.Addを使って計算
        //long型配列
        private long Test91_ParallelForEachPartitioner_Vector(long[] ary)
        {
            long total = 0;
            int windowSize = ary.Length / Environment.ProcessorCount;
            var rangePartitioner = Partitioner.Create(0, ary.Length, windowSize);//
            int simdLength = Vector<long>.Count;
            //var options = new ParallelOptions() { MaxDegreeOfParallelism = 4 };
            //over load no6
            //int ii = 0;
            Parallel.ForEach(rangePartitioner, (range) =>
            {
                var v = new Vector<long>();
                int lastIndex = range.Item2 - (range.Item2 % simdLength);
                for (int i = range.Item1; i < lastIndex; i += simdLength)
                {
                    v = System.Numerics.Vector.Add(v, new Vector<long>(ary, i));
                }
                long subtotal = 0;
                for (int i = 0; i < simdLength; i++)
                {
                    subtotal += v[i];
                }
                for (int i = lastIndex; i < range.Item2; i++)
                {
                    subtotal += ary[i];
                }
                Interlocked.Add(ref total, subtotal);
                //Interlocked.Increment(ref ii);

            });
            return total;
        }

        //Parallel.ForEach+Partitioner+Vector.Addを使って計算
        //int型配列、int型で集計するからオーバーフローする版
        private long Test9x_ParallelForEachPartitionerVector_Overflow(int[] ary)
        {
            long total = 0;
            int windowSize = ary.Length / Environment.ProcessorCount;
            var rangePartitioner = Partitioner.Create(0, ary.Length, windowSize);//
            int simdLength = Vector<int>.Count;
            Parallel.ForEach(rangePartitioner, (range) =>
            {
                int lastIndex = range.Item2 - (range.Item2 % simdLength);
                var v = new Vector<int>();
                for (int i = range.Item1; i < lastIndex; i += simdLength)
                {
                    v = System.Numerics.Vector.Add(v, new Vector<int>(ary, i));
                }
                long subtotal = 0;
                for (int i = 0; i < simdLength; i++)
                {
                    subtotal += v[i];
                }
                for (int i = lastIndex; i < range.Item2; i++)
                {
                    subtotal += ary[i];
                }

                Interlocked.Add(ref total, subtotal);
            });
            return total;
        }






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

    }
}

 

 

関連記事

追記にも書いたけど、次回は3日後

gogowaten.hatenablog.com

 

前回は一昨日

 

 

前々回は2週間前

gogowaten.hatenablog.com