午後わてんのブログ

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

C#.NETのMathPowは速くない?べき乗計算は掛け算を繰り返したほうが速い場合もあった

Math.Powを使うと遅くなる状況がある

f:id:gogowaten:20200218103430p:plain

アプリダウンロード先とコードはギットハブ

アプリファイル名:20200217_MathPow.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:20200218120354p:plain

場合によっては30~80倍の差が出た

 

MathPow

f:id:gogowaten:20200218103950p:plain

MathPowの説明を見るとdouble型で計算してdouble型で返すってあるから、intやbyte型を渡してもdouble型で計算するから遅くなる?と思って

 

int型での2乗

f:id:gogowaten:20200218104326p:plain

Test01が普通の掛け算での2乗

Test02がPowで2乗

Test03は普通の掛け算での2乗した結果を変数で受け取るようにしただけ

個々のループカウントは1万、それをさらに1回ループで合計1万*1万=100,000,000回は1億回の計算時間は

f:id:gogowaten:20200218104650p:plain

どれも同じ速度、っていうか本当に1億回も計算してるのってくらい速い。int型だからってのはなさそうなので、以降はdouble型でテスト

 

double型で

f:id:gogowaten:20200218105442p:plain

結果は

f:id:gogowaten:20200218105610p:plain

MathPowのほうが遅く出ているけど誤差

 

10乗

f:id:gogowaten:20200218105810p:plain

これでMathPowのほうが遅くなるなら、めんどくさくてもTest06にするけど

結果

f:id:gogowaten:20200218110019p:plain

掛け算でもMathPowでも同じ速度だった。逆にループでの掛け算は10倍以上遅かった。ここまでだとMathPowは遅くない

 

配列の値を順番に処理

f:id:gogowaten:20200218110447p:plain

配列の値を2乗して合計する処理、配列要素数は1万、要素の値はすべて10.0にしてある。なので結果は2乗が10.0*10.0=100で、これが1万個だから、100*10000=1,000,000は100万

結果は

f:id:gogowaten:20200218110854p:plain

合計値はいいとして、さっきまで遅くはなかったMathPowはかなり遅い結果になった、2.782/0.08=34.775、35倍も時間がかかっている。これは要素の値を10.0から10.1とかの値にしても変わらなかった。これは計算結果を受けるのが関係しているのかも?と

 

f:id:gogowaten:20200218111905p:plain

順番に計算はするけど合計はしないで結果は破棄するようにした

結果

f:id:gogowaten:20200218111946p:plain

余計に差が開いた、2.686/0.033=81.393939、81倍

 

配列を参照するのが関係している?と思ってSpan<double>にしてみた

f:id:gogowaten:20200218112321p:plain

時間計測が入っているから長いけど

実際の計算部分は

f:id:gogowaten:20200218112452p:plain

これ、配列がSpanに変わっただけ、といってもSpanが何なのかよくわかっていない

結果は

f:id:gogowaten:20200218112643p:plain

配列のときのTest11と変わらずだった、遅い

 

毎回配列を2回参照するのが関係しているのかもと思って、1回で済むように変数に入れてみるようにした

f:id:gogowaten:20200218112827p:plain

結果は

f:id:gogowaten:20200218112835p:plain

関係なかった、MathPow遅い

 

配列の最初の値だけ抜き出して2乗

f:id:gogowaten:20200218113356p:plain

あんまり意味なさそうだけど

f:id:gogowaten:20200218113405p:plain

これなら速度低下はなかった

これで終わり

 

なんで遅くなるのかはわからなかったけど、わかったことは

配列の値をループで処理するときにべき乗の計算があるときは、Math.Powをつかわないで、普通に掛け算で処理するほうが速い、その速度差は30倍から80倍

前回の記事はこれを知らずにMathPowを使って書いてしまったので書き直す必要があるってこと

 

未央「全然違うじゃん!」
MathPow「……」(計算中)
未央「言ったよね!?『MathPowはべき乗計算用のメソッド』って!この結果は何?」
MathPow「まだ計算中なので当然の結果です…」
未央「もういいよ!私配列の処理でMathPow使うの辞める!」

 

 2020/02/19追記ここから

".net math.pow 遅い"とかでググったら

someprog.blog.fc2.com

ここからのリンクで

stackoverflow.com

MathPowはなんかいっぱい計算しているから遅いらしい、たしかにいっぱい書いてある、ぜんぜんわからんけど大変そうだってのはわかった

2020/02/19追記ここまで

 

 

<Window x:Class="_20200217_MathPow.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:_20200217_MathPow"
        mc:Ignorable="d"
        Title="MainWindow" Height="700" 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"/>
      <StackPanel Orientation="Horizontal" HorizontalAlignment="Center">
        <Button x:Name="ButtonAll" Content="一斉テスト" Margin="20,0" Width="120"/>
        <TextBlock x:Name="TbAll" Text="time"/>
        <Button x:Name="ButtonReset" Content="reset" Margin="20,0"/>

      </StackPanel>
      <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>
      <Border Height="1" Background="Orange" UseLayoutRounding="True"/>
      <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>
      
      <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>
  </Grid>
</Window>

 

 

 

using System;
using System.Collections.Generic;
using System.Threading.Tasks;
using System.Windows;
using System.Windows.Controls;
using System.Diagnostics;

namespace _20200217_MathPow
{
    /// <summary>
    /// Interaction logic for MainWindow.xaml
    /// </summary>
    public partial class MainWindow : Window
    {
        private const int MY_COUNT = 10_000;
        private const int MY_INTEGER = 3;
        private const double MY_DOUBLE = 3.3;
        private double[] MyArray;
        public MainWindow()
        {
            InitializeComponent();

            this.Title = this.Name;
            MyTextBlock.Text = $"Math.Powは速くない?\nべき乗計算{MY_COUNT.ToString("N0")} * {MY_COUNT.ToString("N0")}回の処理時間";
            MyArray = new double[MY_COUNT];
            var span = new Span<double>(MyArray);
            span.Fill(10.0);

            ButtonAll.Click += async (s, e) => await MyExeAll();
            ButtonReset.Click += (s, e) => MyReset();

            Button1.Click += (s, e) => MyExe(Test01_2乗_int, MY_INTEGER, MY_COUNT, Tb1);
            //Button2.Click += (s, e) => MyExe((a, b) => { for (int i = 0; i < b; i++) { _ = a * a; } }, MY_INTEGER, LOOP_COUNT,Tb2);
            //Button3.Click += (s, e) => MyExe((a, b) => Test01_int_普通の掛け算で2乗(a, b), MY_INTEGER, LOOP_COUNT,Tb3);
            Button2.Click += (s, e) => MyExe(Test02_2乗_int_MathPow, MY_INTEGER, MY_COUNT, Tb2);
            Button3.Click += (s, e) => MyExe(Test03_2乗その2_int, MY_INTEGER, MY_COUNT, Tb3);
            Button4.Click += (s, e) => MyExe(Test04_2乗, MY_DOUBLE, MY_COUNT, Tb4);
            Button5.Click += (s, e) => MyExe(Test05_2乗MathPow, MY_DOUBLE, MY_COUNT, Tb5);
            Button6.Click += (s, e) => MyExe(Test06_10乗, MY_DOUBLE, MY_COUNT, Tb6);
            Button7.Click += (s, e) => MyExe(Test07_10乗MathPow, MY_DOUBLE, MY_COUNT, Tb7);
            Button8.Click += (s, e) => MyExe(Test08_ループで10乗, MY_DOUBLE, MY_COUNT, Tb8);
            Button9.Click += (s, e) => MyExe(Test09_ループで9回掛け算, MY_DOUBLE, MY_COUNT, Tb9);
            Button10.Click += (s, e) => MyExe(Test10_配列2乗和, MyArray, MY_COUNT, Tb10);
            Button11.Click += (s, e) => MyExe(Test11_配列2乗和_MathPow, MyArray, MY_COUNT, Tb11);
            Button12.Click += (s, e) => MyExe(Test12_配列2乗値破棄, MyArray, MY_COUNT, Tb12);
            Button13.Click += (s, e) => MyExe(Test13_配列2乗値破棄_MathPow, MyArray, MY_COUNT, Tb13);
            Button14.Click += (s, e) => Test14_Span2乗和_MathPow(MyArray, Tb14);
            Button15.Click += (s, e) => MyExe(Test15_2乗和その2, MyArray, MY_COUNT, Tb15);
            Button16.Click += (s, e) => MyExe(Test16_2乗和その2_MathPow, MyArray, MY_COUNT, Tb16);
            Button17.Click += (s, e) => MyExe(Test17_配列2乗値破棄その2, MyArray, MY_COUNT, Tb17);
            Button18.Click += (s, e) => MyExe(Test18_配列2乗値破棄その2_MathPow, MyArray, MY_COUNT, Tb18);

        }

        private void Test01_2乗_int(int num, int count)
        {
            for (int i = 0; i < count; i++) { _ = num * num; }
        }
        private void Test02_2乗_int_MathPow(int num, int count)
        {
            for (int i = 0; i < count; i++) { _ = Math.Pow(num, 2); }
        }
        private void Test03_2乗その2_int(int num, int count)
        {
            for (int i = 0; i < count; i++) { var temp = num * num; }
        }

        private void Test04_2乗(double num, int count)
        {
            for (int i = 0; i < count; i++) { _ = num * num; }
        }
        private void Test05_2乗MathPow(double num, int count)
        {
            for (int i = 0; i < count; i++) { _ = Math.Pow(num, 2); }
        }

        private void Test06_10乗(double num, int count)
        {
            for (int i = 0; i < count; i++) { _ = num * num * num * num * num * num * num * num * num * num; }
        }
        private void Test07_10乗MathPow(double num, int count)
        {
            for (int i = 0; i < count; i++) { _ = Math.Pow(num, 10); }
        }
        private void Test08_ループで10乗(double num, int count)
        {
            for (int i = 0; i < count; i++)
            {
                var temp = num;
                for (int k = 0; k < 9; k++)
                {
                    temp *= num;
                }
            }
        }

        private void Test09_ループで9回掛け算(double num, int count)
        {
            for (int i = 0; i < count; i++)
            {
                for (int k = 0; k < 9; k++)
                {
                    _ = num * num;
                }
            }
        }

        private double Test10_配列2乗和(double[] ary)
        {
            double total = 0;
            for (int i = 0; i < ary.Length; i++)
                total += ary[i] * ary[i];
            return total;
        }

        private double Test11_配列2乗和_MathPow(double[] ary)
        {
            double total = 0;
            for (int i = 0; i < ary.Length; i++)
                total += Math.Pow(ary[i], 2.0);
            return total;
        }

        private void Test12_配列2乗値破棄(double[] ary)
        {
            for (int i = 0; i < ary.Length; i++)
                _ = ary[i] * ary[i];
        }

        private void Test13_配列2乗値破棄_MathPow(double[] ary)
        {
            for (int i = 0; i < ary.Length; i++)
                _ = Math.Pow(ary[i], 2);
        }

        private void Test14_Span2乗和_MathPow(Span<double> span, TextBlock textBlock)
        {
            var sw = new Stopwatch();
            sw.Start();
            double total = 0;
            for (int k = 0; k < MY_COUNT; k++)
            {
                total = 0;
                for (int i = 0; i < span.Length; i++)
                {
                    total += Math.Pow(span[i], 2.0);
                }
            }
            sw.Stop();
            this.Dispatcher.Invoke(() =>
            {
                textBlock.Text = $"処理時間:{sw.Elapsed.TotalSeconds.ToString("00.000")}秒 合計値={total} {nameof(Test14_Span2乗和_MathPow)}";
            });

        }

        private double Test15_2乗和その2(double[] ary)
        {
            double total = 0;
            double temp;
            for (int i = 0; i < ary.Length; i++)
            {
                temp = ary[i];
                total += temp * temp;
            }
            return total;
        }
        private double Test16_2乗和その2_MathPow(double[] ary)
        {
            double total = 0;
            double temp;
            for (int i = 0; i < ary.Length; i++)
            {
                temp = ary[i];
                total += Math.Pow(temp, 2.0);
            }
            return total;
        }
        private void Test17_配列2乗値破棄その2(double[] ary)
        {
            double d = ary[0];
            for (int i = 0; i < ary.Length; i++)
                _ = d * d;
        }

        private void Test18_配列2乗値破棄その2_MathPow(double[] ary)
        {
            double d = ary[0];
            for (int i = 0; i < ary.Length; i++)
                _ = Math.Pow(d, 2);
        }








        private void MyExe(Action<int, int> action, int num, int count, TextBlock textBlock)
        {
            var sw = new Stopwatch();
            sw.Start();
            for (int i = 0; i < count; i++)
            {
                action(num, count);
            }
            sw.Stop();

            this.Dispatcher.Invoke(() =>
            {
                textBlock.Text = $"処理時間:{sw.Elapsed.TotalSeconds.ToString("00.000")}秒 {action.Method.Name}";
            });

        }
        private void MyExe(Action<double, int> action, double num, int count, TextBlock textBlock)
        {
            var sw = new Stopwatch();
            sw.Start();
            for (int i = 0; i < count; i++)
            {
                action(num, count);
            }
            sw.Stop();
            this.Dispatcher.Invoke(() =>
            {
                textBlock.Text = $"処理時間:{sw.Elapsed.TotalSeconds.ToString("00.000")}秒 {action.Method.Name}";
            });
        }
        private void MyExe(Func<double[], double> func, double[] ary, int count, TextBlock textBlock)
        {
            var sw = new Stopwatch();
            sw.Start();
            double total = 0;
            for (int i = 0; i < count; i++)
            {
                total = func(ary);
            }
            sw.Stop();
            this.Dispatcher.Invoke(() =>
            {
                textBlock.Text = $"処理時間:{sw.Elapsed.TotalSeconds.ToString("00.000")}秒 合計値={total} {func.Method.Name}";
            });
        }
        private void MyExe(Action<double[]> action, double[] ary, int count, TextBlock textBlock)
        {
            var sw = new Stopwatch();
            sw.Start();
            for (int i = 0; i < count; i++)
            {
                action(ary);
            }
            sw.Stop();
            this.Dispatcher.Invoke(() =>
            {
                textBlock.Text = $"処理時間:{sw.Elapsed.TotalSeconds.ToString("00.000")}秒 {action.Method.Name}";
            });
        }
        

        private async Task MyExeAll()
        {
            var sw = new Stopwatch();
            sw.Start();
            this.IsEnabled = false;
            await Task.Run(() => MyExe(Test01_2乗_int, MY_INTEGER, MY_COUNT, Tb1));
            await Task.Run(() => MyExe(Test02_2乗_int_MathPow, MY_INTEGER, MY_COUNT, Tb2));
            await Task.Run(() => MyExe(Test03_2乗その2_int, MY_INTEGER, MY_COUNT, Tb3));
            await Task.Run(() => MyExe(Test04_2乗, MY_DOUBLE, MY_COUNT, Tb4));
            await Task.Run(() => MyExe(Test05_2乗MathPow, MY_DOUBLE, MY_COUNT, Tb5));
            await Task.Run(() => MyExe(Test06_10乗, MY_DOUBLE, MY_COUNT, Tb6));
            await Task.Run(() => MyExe(Test07_10乗MathPow, MY_DOUBLE, MY_COUNT, Tb7));
            await Task.Run(() => MyExe(Test08_ループで10乗, MY_DOUBLE, MY_COUNT, Tb8));
            await Task.Run(() => MyExe(Test09_ループで9回掛け算, MY_DOUBLE, MY_COUNT, Tb9));
            await Task.Run(() => MyExe(Test10_配列2乗和, MyArray, MY_COUNT, Tb10));
            await Task.Run(() => MyExe(Test11_配列2乗和_MathPow, MyArray, MY_COUNT, Tb11));
            await Task.Run(() => MyExe(Test12_配列2乗値破棄, MyArray, MY_COUNT, Tb12));
            await Task.Run(() => MyExe(Test13_配列2乗値破棄_MathPow, MyArray, MY_COUNT, Tb13));
            await Task.Run(() => Test14_Span2乗和_MathPow(MyArray, Tb14));
            await Task.Run(() => MyExe(Test15_2乗和その2, MyArray, MY_COUNT, Tb15));
            await Task.Run(() => MyExe(Test16_2乗和その2_MathPow, MyArray, MY_COUNT, Tb16));
            await Task.Run(() => MyExe(Test17_配列2乗値破棄その2, MyArray, MY_COUNT, Tb17));
            await Task.Run(() => MyExe(Test18_配列2乗値破棄その2_MathPow, MyArray, MY_COUNT, Tb18));
            this.IsEnabled = true;
            sw.Stop();
            TbAll.Text = $"一斉テスト処理時間:{sw.Elapsed.TotalSeconds.ToString("000.000")}秒";
        }
        private void MyReset()
        {
            var tb = new List<TextBlock>() { Tb1, Tb2, Tb3, Tb4, Tb5, Tb6, Tb7, Tb8, Tb9, Tb10, Tb11, Tb12, Tb13, Tb14, Tb15, Tb16, Tb17, Tb18, TbAll };
            foreach (var item in tb)
            {
                item.Text = "";
            }
        }
    }
}

 

 

関連記事

次回は翌日

gogowaten.hatenablog.com

 

前回は6日前

gogowaten.hatenablog.com

これを書き直すのかあ