2010/05/31(月)最適化実験+1
クラスライブラリ側がVector3(float, float, float)を要求するとき、
それをラップするクラスはどうするべきかと考えていた。
さらに、内部の型が違う場合について調べてみた。
前提。
struct AI, AF, AD と、同じ内容のBI, BF, BD を用意。
Aはテストコードが参照しているクラスライブラリ(別アセンブリ)
Bはテストコードと同じアセンブリに属している。
public struct BI
{
public BI(int p1, int p2) { this.p1 = p1; this.p2 = p2; }
public int p1, p2;
public static implicit operator AI(BI b) { return new AI((int)b.p1, (int)b.p2); }
public static implicit operator AF(BI b) { return new AF((int)b.p1, (int)b.p2); }
public static implicit operator AD(BI b) { return new AD((int)b.p1, (int)b.p2); }
}
// 以下同文。
// I,F,Dはそれぞれ、int, float, doubleの意。
public class class1 {
public void Input(AI a) { }
public void Input(AF a) { }
public void Input(AD a) { }
}
10億回の実行に掛かる時間を測った。
// 1406ms 空ループ
class1.Input(new AI(1, 1)); // 3936ms
class1.Input(new AI(i, i)); // 3802ms
class1.Input((AI)new BI(1, 1)); // 3283ms
class1.Input((AI)new BI(i, 1)); // 3276ms
class1.Input((AI)new BI(i, i)); // 3279ms
class1.Input((AI)new BI(i, i + 1)); // 4520ms(+7)
class1.Input((AF)new BI(1, 1)); // 33495ms
class1.Input((AF)new BI(i, 1)); // 33583ms
class1.Input((AF)new BI(i, i)); // 33583ms
class1.Input((AF)new BI(i, i + 1)); // 33583ms
class1.Input((AD)new BI(1, 1)); // 15811ms
class1.Input((AD)new BI(i, 1)); // 14001ms
class1.Input((AD)new BI(i, 100)); // 12095ms
class1.Input((AD)new BI(i, i + 1)); // 18945ms
class1.Input((AI)new BF(1, 1)); // 3271ms
class1.Input((AI)new BF(13, 19)); // 5806ms
class1.Input((AI)new BF(i, 1)); // 36269ms (+33)
class1.Input((AI)new BF(i, i)); // 67801ms (+31)
class1.Input(new AF(1, 1)); // 17748ms
class1.Input(new AF(i, i)); // 34385ms(+17)
class1.Input((AF)new BF(1, 1)); // 13957ms(-4)
class1.Input((AF)new BF(i, 1)); // 22150ms(+8)
class1.Input((AF)new BF(i, i)); // 33285ms(+11)
class1.Input((AF)new BF(i, i + 1)); // 32387ms(-1)
class1.Input((AD)new BF(1, 1)); // 15849ms(-2)
class1.Input((AD)new BF(i, 1)); // 28739ms(+13)
class1.Input((AD)new BF(i, i)); // 47168ms(+19)
class1.Input((AD)new BF(i, i + 1)); // 47009ms(+0)
class1.Input(new AD(1, 1)); // 16492ms
class1.Input(new AD(i, i)); // 23109ms(+7)
class1.Input((AI)new BD(1, 1)); // 3267ms
class1.Input((AI)new BD(i, 1)); // 23384ms(+20)
class1.Input((AI)new BD(i, i)); // 39822ms(+17)
class1.Input((AI)new BD(i, i + 1)); // 39961ms(+0)
class1.Input((AF)new BD(1, 1)); // 15844ms
class1.Input((AF)new BD(i, 1)); // 22159ms(+7)
class1.Input((AF)new BD(i, i)); // 31647ms(+9)
class1.Input((AF)new BD(i, i + 1)); // 31796ms(+0)
class1.Input((AD)new BD(1, 1)); // 16136ms
class1.Input((AD)new BD(i, 1)); // 13660ms(-3)
class1.Input((AD)new BD(i, 100)); // 12857ms(-1)
class1.Input((AD)new BD(i, 10000)); // 12115ms(-1)
class1.Input((AD)new BD(i, i)); // 17753ms(+6)
class1.Input((AD)new BD(i, i + 1)); // 18023ms(+0)
class1.Input((AD)new BD(i, i + 10000)); // 17757ms(+0)
class1.Input((AD)new BD(i, i + 10000.0)); // 20932ms(+2)
考察
よくわからない現象
class1.Input(new AI(1, 1)); // 3936ms class1.Input((AI)new BI(1, 1)); // 3283ms class1.Input(new AF(1, 1)); // 17748ms class1.Input((AF)new BF(1, 1)); // 13957ms class1.Input(new AD(1, 1)); // 16492ms class1.Input((AD)new BD(1, 1)); // 16136ms
doubleについては誤差でも、intとfloatはなんで後者の方が速いの?
よくわからない現象
class1.Input((AD)new BI(i, 1)); // 14001ms class1.Input((AD)new BI(i, 100)); // 12095ms class1.Input((AD)new BD(i, 1)); // 13660ms class1.Input((AD)new BD(i, 100)); // 12857ms class1.Input((AD)new BD(i, 10000)); // 12115ms
???
おもしろい
class1.Input((AI)new BI(1, 1)); // 3283ms class1.Input((AI)new BF(1, 1)); // 3271ms class1.Input((AI)new BD(1, 1)); // 3267ms
一回、実数型に変換してるのに速度がかわんない。
とにかく実数が遅いです……?
class1.Input(new AI(1, 1)); // 3936ms class1.Input(new AF(1, 1)); // 17748ms class1.Input(new AD(1, 1)); // 16492ms
特に計算をしているわけでもなく、
値のコピーをしてるだけのはずなのに、
なんでこんなに時間が掛かるのかしらー。
// Input<T>(T p1, T p2) { } を定義してみた。
class1.Input(1, 1); // 1518ms
class1.Input(1f, 1f); // 1525ms
class1.Input(1d, 1d); // 1534ms
メソッドコール自体サプレスされてる?
virtualにしたらそれぞれ+70秒くらい掛かるようになったので、
測りたい部分に関してはちゃんと測れてるのかな。
実験その2
それぞれに、new BI(i, 1)を渡して、1000万回の実行時間を計り、10倍した。
// 参考
// class1.Input((AI)new BI(i, 1)); // 3276ms
// class1.Input((AF)new BI(i, 1)); // 33583ms
// class1.Input((AD)new BI(i, 1)); // 14001ms
public int CalcIII(AI a) { return a.X + a.Y; } // 2867ms(-409)
public float CalcFFF(AF a) { return a.X + a.Y; } // 49700ms (+16117)
public float CalcFDD(AD a) { return (float)(a.X + a.Y); } // 22220ms (+8219)
public double CalcDDD(AD a) { return a.X + a.Y; } // 15010ms (+1009)
public double CalcDFD(AF a) { return (double)a.X + (double)a.Y; } // 55140ms (+21557)
public double CalcDFF(AF a) { return (float)a.X + (float)a.Y; } // 61410ms (+27827)
public double CalcDDF(AD a) { return (float)a.X + (float)a.Y; } // 30570ms (+16569)
public double CalcDDF2(AD a) { return (float)((float)a.X + (float)a.Y); } // 58360ms (+44359)
public double Calc1(AD a) { return 1; } // 19470ms (+5469)
処理が増えてるのに実行時間が短くなる不思議。
DDFとDDF2はどう違うんだ……。
結論
とにかくintからの変換コストが高いようなので、それを変えようがない今はどうもfloat float float を持った方がいいみたいか。
それにしても、実数型構造体はなんか妙に遅すぎる気がする。
型変換にも結構時間が掛かる、Vector2(float, float)を1M回渡すのに、18ms。1万回で0.18ms。
テクスチャを描くのには、座標変換行列とか中心座標とか色とかもあるから、実際1msくらい掛かるのかしら。ふーん……。
もしこれにint型の構造体を使うと約倍のコストが掛かる計算?
これなら、利便性を重視した方がよいかな。
仮にfloatを要求される場合でも、doubleを渡してdoubleで計算してからfloatに詰めて返す方が安い様に見える。
引数にfつけるのめんどくさいと思うし、構造体のコンストラクタは全部doubleで持ってもいいかしら、とかとか。
そうすると前の実験的には、sturct Bが彼方にあるときが問題になるのかな。
class1.Input((AF)new BF(1.2f, 3.4f)); // 32450ms class1.Input((AD)new BD(1.2, 3.4)); // 34770ms class1.Input((AF)new BD(1.2, 3.4)); // 43640ms class1.CalcFFF(new BF(1.2f, 3.4f)); // 49740ms class1.CalcFDD(new BD(1.2, 3.4)); // 47400ms class1.CalcFFF(new BD(1.2, 3.4)); // 60130ms
ちょっとおまけがつくぐらいでどっちでもよさげ?
まぁ、ボトルネックになってるわけでもあるまいし、
割とどうでもいい、という結論しか出ないのだなぁ。
利便性で考えよ。