2010/05/31(Mon)最適化実験+1

はてブ数 2010/05/31 0:04 プログラミング::C# つーさ

クラスライブラリ側が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

ちょっとおまけがつくぐらいでどっちでもよさげ?

まぁ、ボトルネックになってるわけでもあるまいし、
割とどうでもいい、という結論しか出ないのだなぁ。

利便性で考えよ。