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
ちょっとおまけがつくぐらいでどっちでもよさげ?
まぁ、ボトルネックになってるわけでもあるまいし、
割とどうでもいい、という結論しか出ないのだなぁ。
利便性で考えよ。