たまたま気になる記事を見つけて流石に mulとsqrtの負荷が同じは無いだろうということで検証してみました
(まさかりOKということなので)
はてなで一度上げましたがせっかくなのでQiitaを使ってみました
SinとかSqrt等の算術演算と掛け算の処理負荷を比べてみた
https://qiita.com/geekdrums/items/1a6e7480f23c9ffbcfc0
2/22 更新
コメントで指摘があったのでJITレベルで正しいコードに変更しました
- 計算に使用する変数をstatic化
- forありなしの結果が別実行だったので同時に出力するように
新しい結果
for文あり
Mult=4959us Sqrt=12772us
SqrtはMulの2.575倍
for文なし
Mult=789us Sqrt=8602us
SqrtはMulの10.901倍
結論から言うと
for文込み
Mult=4863us Sqrt=13664us
SqrtはMulの2.810倍
for文負荷抜き
Mult=446us Sqrt=9460us
SqrtはMulの21.198倍
新たなまさかり歓迎ですmm
気になった点
- UnityのProfiler精度が10usなため誤差が大きいがそれに対しループ数が少ない
- 倍精度積の負荷を見ているがfor文で倍精度の加算も行われている
- そもそもReleaseビルドだと最適化されて中身消えそう
- for文の負荷とか気になる
環境
Unity2018.3.2 .Net4.X
MacBook Pro 2.9 GHz Intel Core i7
検証コード
今回気になったところは乗算と平方根が同じ負荷というところなので、そこに絞って計測しました
public class MathTest : MonoBehaviour
{
public const int count = 1000000;
public const int loop = 100;
public static double f;
IEnumerator Start()
{
yield return new WaitForSeconds(1f);
Test();
}
static void Test()
{
long loopTime = 0L;
long multTime = 0L;
long sqrtTime = 0L;
for (int i = 0; i < loop; i++)
{
loopTime += Loop();
multTime += Mult();
sqrtTime += Sqrt();
}
Debug.Log($"{f}");
Debug.Log($"Loop={loopTime} Mult={multTime} Sqrt={sqrtTime}");
Debug.Log("for文あり");
Debug.Log($"Mult={multTime / loop / 10L}us Sqrt={sqrtTime / loop / 10L}us");
Debug.Log($"SqrtはMulの{(double) sqrtTime / multTime:F3}倍");
Debug.Log("for文なし");
multTime -= loopTime;
sqrtTime -= loopTime;
Debug.Log($"Mult={multTime / loop / 10L}us Sqrt={sqrtTime / loop / 10L}us");
Debug.Log($"SqrtはMulの{(double) sqrtTime / multTime:F3}倍");
}
static long Loop()
{
f = 1.0;
var sw = System.Diagnostics.Stopwatch.StartNew();
for (int i = 0; i < count; i++)
{
f = f;
}
sw.Stop();
return sw.ElapsedTicks;
}
static long Mult()
{
f = 1.00001;
var sw = System.Diagnostics.Stopwatch.StartNew();
for (int i = 0; i < count; i++)
{
f *= f;
}
sw.Stop();
return sw.ElapsedTicks;
}
static long Sqrt()
{
f = 100000.0;
var sw = System.Diagnostics.Stopwatch.StartNew();
for (int i = 0; i < count; i++)
{
f = System.Math.Sqrt(f);
}
sw.Stop();
return sw.ElapsedTicks;
}
}
ILはこんな感じになります
SharpLab
修正後のJITはこちらです
SharpLab
Mult()
L0017: fmul qword [0x27e016a0]
Sqrt()
L0017: fsqrt
差分がここだけになったのでおそらく抜けは無いと思います
ついでにiOS用に出力したil2cppのコードは以下のようになっていて特に抜けはなさそうです
// f *= f;
double L_1 = ((MathTest_tA4D864CB9DFCED1A10B65A009CDBB2F4A99381F9_StaticFields*)il2cpp_codegen_static_fields_for(MathTest_tA4D864CB9DFCED1A10B65A009CDBB2F4A99381F9_il2cpp_TypeInfo_var))->get_f_6();
double L_2 = ((MathTest_tA4D864CB9DFCED1A10B65A009CDBB2F4A99381F9_StaticFields*)il2cpp_codegen_static_fields_for(MathTest_tA4D864CB9DFCED1A10B65A009CDBB2F4A99381F9_il2cpp_TypeInfo_var))->get_f_6();
((MathTest_tA4D864CB9DFCED1A10B65A009CDBB2F4A99381F9_StaticFields*)il2cpp_codegen_static_fields_for(MathTest_tA4D864CB9DFCED1A10B65A009CDBB2F4A99381F9_il2cpp_TypeInfo_var))->set_f_6(((double)il2cpp_codegen_multiply((double)L_1, (double)L_2)));
// f = System.Math.Sqrt(f);
double L_1 = ((MathTest_tA4D864CB9DFCED1A10B65A009CDBB2F4A99381F9_StaticFields*)il2cpp_codegen_static_fields_for(MathTest_tA4D864CB9DFCED1A10B65A009CDBB2F4A99381F9_il2cpp_TypeInfo_var))->get_f_6();
IL2CPP_RUNTIME_CLASS_INIT(Math_tFB388E53C7FDC6FCCF9A19ABF5A4E521FBD52E19_il2cpp_TypeInfo_var);
double L_2 = sqrt(L_1);
((MathTest_tA4D864CB9DFCED1A10B65A009CDBB2F4A99381F9_StaticFields*)il2cpp_codegen_static_fields_for(MathTest_tA4D864CB9DFCED1A10B65A009CDBB2F4A99381F9_il2cpp_TypeInfo_var))->set_f_6(L_2);
気をつけた点は
- 計測にメソッドを通さない
- ticksでの計測
- 十分CPUに同一負荷がかけられるように計測の平均値算出
ここまで回すと割と結果が安定しました
Mult=4863us Sqrt=13664us
SqrtはMulの2.810倍
Mult=446us Sqrt=9460us
SqrtはMulの21.198倍
for文あり
Mult=4959us Sqrt=12772us
SqrtはMulの2.575
for文なし
Mult=789us Sqrt=8602us
SqrtはMulの10.901倍
ちゃんと処理を見直しましたが割としっくり来る結果になってよかったです