Math.Powを使うと遅くなる状況がある
アプリダウンロード先とコードはギットハブ
アプリファイル名: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#
遅くなる状況は配列やコレクションをループで処理するときに使うと遅くなるみたい?
場合によっては30~80倍の差が出た
MathPow
MathPowの説明を見るとdouble型で計算してdouble型で返すってあるから、intやbyte型を渡してもdouble型で計算するから遅くなる?と思って
int型での2乗
Test01が普通の掛け算での2乗
Test02がPowで2乗
Test03は普通の掛け算での2乗した結果を変数で受け取るようにしただけ
個々のループカウントは1万、それをさらに1回ループで合計1万*1万=100,000,000回は1億回の計算時間は
どれも同じ速度、っていうか本当に1億回も計算してるのってくらい速い。int型だからってのはなさそうなので、以降はdouble型でテスト
double型で
結果は
MathPowのほうが遅く出ているけど誤差
10乗
これでMathPowのほうが遅くなるなら、めんどくさくてもTest06にするけど
結果
掛け算でもMathPowでも同じ速度だった。逆にループでの掛け算は10倍以上遅かった。ここまでだとMathPowは遅くない
配列の値を順番に処理
配列の値を2乗して合計する処理、配列要素数は1万、要素の値はすべて10.0にしてある。なので結果は2乗が10.0*10.0=100で、これが1万個だから、100*10000=1,000,000は100万
結果は
合計値はいいとして、さっきまで遅くはなかったMathPowはかなり遅い結果になった、2.782/0.08=34.775、35倍も時間がかかっている。これは要素の値を10.0から10.1とかの値にしても変わらなかった。これは計算結果を受けるのが関係しているのかも?と
順番に計算はするけど合計はしないで結果は破棄するようにした
結果
余計に差が開いた、2.686/0.033=81.393939、81倍
配列を参照するのが関係している?と思ってSpan<double>にしてみた
時間計測が入っているから長いけど
実際の計算部分は
これ、配列がSpanに変わっただけ、といってもSpanが何なのかよくわかっていない
結果は
配列のときのTest11と変わらずだった、遅い
毎回配列を2回参照するのが関係しているのかもと思って、1回で済むように変数に入れてみるようにした
結果は
関係なかった、MathPow遅い
配列の最初の値だけ抜き出して2乗
あんまり意味なさそうだけど
これなら速度低下はなかった
これで終わり
なんで遅くなるのかはわからなかったけど、わかったことは
配列の値をループで処理するときにべき乗の計算があるときは、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 xClass="_20200217_MathPow.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:_20200217_MathPow"
mcIgnorable="d"
Title="MainWindow" Height="700" Width="614">
<Grid>
<StackPanel>
<StackPanelResources>
<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>
</StackPanelResources>
<TextBlock xName="MyTextBlock" Text="text" HorizontalAlignment="Center"/>
<StackPanel Orientation="Horizontal" HorizontalAlignment="Center">
<Button xName="ButtonAll" Content="一斉テスト" Margin="20,0" Width="120"/>
<TextBlock xName="TbAll" Text="time"/>
<Button xName="ButtonReset" Content="reset" Margin="20,0"/>
</StackPanel>
<StackPanel Orientation="Horizontal">
<Button xName="Button1" Content="test1"/>
<TextBlock xName="Tb1" Text="time"/>
</StackPanel>
<StackPanel Orientation="Horizontal">
<Button xName="Button2" Content="test2"/>
<TextBlock xName="Tb2" Text="time"/>
</StackPanel>
<StackPanel Orientation="Horizontal">
<Button xName="Button3" Content="test3"/>
<TextBlock xName="Tb3" Text="time"/>
</StackPanel>
<Border Height="1" Background="Orange" UseLayoutRounding="True"/>
<StackPanel Orientation="Horizontal">
<Button xName="Button4" Content="test4"/>
<TextBlock xName="Tb4" Text="time"/>
</StackPanel>
<StackPanel Orientation="Horizontal">
<Button xName="Button5" Content="test5"/>
<TextBlock xName="Tb5" Text="time"/>
</StackPanel>
<StackPanel Orientation="Horizontal">
<Button xName="Button6" Content="test6"/>
<TextBlock xName="Tb6" Text="time"/>
</StackPanel>
<StackPanel Orientation="Horizontal">
<Button xName="Button7" Content="test7"/>
<TextBlock xName="Tb7" Text="time"/>
</StackPanel>
<StackPanel Orientation="Horizontal">
<Button xName="Button8" Content="test8"/>
<TextBlock xName="Tb8" Text="time"/>
</StackPanel>
<StackPanel Orientation="Horizontal">
<Button xName="Button9" Content="test9"/>
<TextBlock xName="Tb9" Text="time"/>
</StackPanel>
<StackPanel Orientation="Horizontal">
<Button xName="Button10" Content="test10"/>
<TextBlock xName="Tb10" Text="time"/>
</StackPanel>
<StackPanel Orientation="Horizontal">
<Button xName="Button11" Content="test11"/>
<TextBlock xName="Tb11" Text="time"/>
</StackPanel>
<StackPanel Orientation="Horizontal">
<Button xName="Button12" Content="test12"/>
<TextBlock xName="Tb12" Text="time"/>
</StackPanel>
<StackPanel Orientation="Horizontal">
<Button xName="Button13" Content="test13"/>
<TextBlock xName="Tb13" Text="time"/>
</StackPanel>
<StackPanel Orientation="Horizontal">
<Button xName="Button14" Content="test14"/>
<TextBlock xName="Tb14" Text="time"/>
</StackPanel>
<StackPanel Orientation="Horizontal">
<Button xName="Button15" Content="test15"/>
<TextBlock xName="Tb15" Text="time"/>
</StackPanel>
<StackPanel Orientation="Horizontal">
<Button xName="Button16" Content="test16"/>
<TextBlock xName="Tb16" Text="time"/>
</StackPanel>
<StackPanel Orientation="Horizontal">
<Button xName="Button17" Content="test17"/>
<TextBlock xName="Tb17" Text="time"/>
</StackPanel>
<StackPanel Orientation="Horizontal">
<Button xName="Button18" Content="test18"/>
<TextBlock xName="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>
</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(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
これを書き直すのかあ