Math.Powを使うと遅くなる状況がある
アプリダウンロード先とコードはギットハブ
アプリファイル名:20200217_MathPow.zip
アプリ作成と計測環境
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 遅い"とかでググったら
ここからのリンクで
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 = ""; } } } }
関連記事
次回は翌日
前回は6日前
これを書き直すのかあ