SinとかSqrt等の算術演算と掛け算の処理負荷を比べてみた
https://qiita.com/geekdrums/items/1a6e7480f23c9ffbcfc0
上記記事で気になった点がありましたので、自分も試してみました。
気になったのは以下の三つの点です。
-
UnityのProfilerのコストがかかっている
-- (Profilerを動かしている最中は、Profilerにて記録されている関数の呼び出し時に計測コードが挟まっているため、関数呼び出しが遅くなっている) -
計測したい処理と比べて、ループ自体のコストがおおきい
-- (例えば掛け算一つだと、多分ループ自体の方が処理コストが高い) -
コンパイラによる簡単な最適化がかかるだけで処理が消えてしまいそう
-- (計算結果を使うようにしてあげないと処理の中身が空になったりしうる)
ということで、上記点を考慮して修正した簡単な検証コードを作成し計測しました。
関数 | 時間(ミリ秒) | Multとの倍率 |
---|---|---|
Mult | 255ms | 1.00倍 |
Sin | 2587ms | 10.15倍 |
Sqrt | 821ms | 3.22倍 |
Atan | 2902ms | 11.38倍 |
Exp | 2131ms | 8.36倍 |
Pow | 3532ms | 13.85倍 |
Pow2 | 3546ms | 13.91倍 |
以下計測コード
Quiitaのテーブルが出力されるようにしています
(2019/02/22 10:35 少し気になる点があったので修正。計測結果は大差なし)
public class MathTest
{
// using文を使って範囲を計測できるようにした時間計測クラス
class Stopwatch : System.IDisposable
{
static double baseTime = -1;
System.Diagnostics.Stopwatch sw;
string memberName;
public Stopwatch([System.Runtime.CompilerServices.CallerMemberName] string memberName = "")
{
this.memberName = memberName;
sw = new System.Diagnostics.Stopwatch();
sw.Start();
}
public void Dispose()
{
sw.Stop();
if (baseTime < 0) baseTime = sw.ElapsedMilliseconds;
Debug.Log($"| {memberName} | {sw.ElapsedMilliseconds}ms | {sw.ElapsedMilliseconds / baseTime:F2}倍 |");
}
}
// 最適化で消えにくいようにpublic static変数で指定しておく
public static int count = 10000000;
public static double firstValue = 0.9999254686; // 掛け続けてもInfinityにならない値に修正
double Mult()
{
double f = firstValue;
using (new Stopwatch())
for (var i = 1; i < count; i++)
{
f = f * f; // ループ文の影響を薄くするため、とりあえず8回手で複製する
f = f * f; // 計算結果が最適化で消えないよう過去の値を使う
f = f * f;
f = f * f;
f = f * f;
f = f * f;
f = f * f;
f = f * f;
}
return f;
}
double Sin()
{
double f = firstValue;
using (new Stopwatch())
for (var i = 1; i < count; i++)
{
f = Math.Sin(f);
f = Math.Sin(f);
f = Math.Sin(f);
f = Math.Sin(f);
f = Math.Sin(f);
f = Math.Sin(f);
f = Math.Sin(f);
f = Math.Sin(f);
}
return f;
}
double Sqrt()
{
double f = firstValue;
using (new Stopwatch())
for (var i = 1; i < count; i++)
{
f = Math.Sqrt(f);
f = Math.Sqrt(f);
f = Math.Sqrt(f);
f = Math.Sqrt(f);
f = Math.Sqrt(f);
f = Math.Sqrt(f);
f = Math.Sqrt(f);
f = Math.Sqrt(f);
}
return f;
}
double Pow()
{
double f = firstValue;
using (new Stopwatch())
for (var i = 1; i < count; i++)
{
f = Math.Pow(f, i);
f = Math.Pow(f, i);
f = Math.Pow(f, i);
f = Math.Pow(f, i);
f = Math.Pow(f, i);
f = Math.Pow(f, i);
f = Math.Pow(f, i);
f = Math.Pow(f, i);
}
return f;
}
double Pow2()
{
double f = firstValue;
using (new Stopwatch())
for (var i = 1; i < count; i++)
{
f = Math.Pow(f, 2);
f = Math.Pow(f, 2);
f = Math.Pow(f, 2);
f = Math.Pow(f, 2);
f = Math.Pow(f, 2);
f = Math.Pow(f, 2);
f = Math.Pow(f, 2);
f = Math.Pow(f, 2);
}
return f;
}
double Atan()
{
double f = firstValue;
using (new Stopwatch())
for (var i = 1; i < count; i++)
{
f = Math.Atan(f);
f = Math.Atan(f);
f = Math.Atan(f);
f = Math.Atan(f);
f = Math.Atan(f);
f = Math.Atan(f);
f = Math.Atan(f);
f = Math.Atan(f);
}
return f;
}
double Exp()
{
double f = firstValue;
using (new Stopwatch())
for (var i = 1; i < count; i++)
{
f = Math.Exp(f);
f = Math.Exp(f);
f = Math.Exp(f);
f = Math.Exp(f);
f = Math.Exp(f);
f = Math.Exp(f);
f = Math.Exp(f);
f = Math.Exp(f);
}
return f;
}
public void MainTest()
{
double ret = Mult()
+ Sin()
+ Sqrt()
+ Atan()
+ Exp() // Expは計算途中でInfinityとなるのがちょっと問題かも
+ Pow()
+ Pow2();
Debug.Log(ret); // インライン展開されて消えることがないようにちゃんと戻り値を使う
}
}