プロが教えるわが家の防犯対策術!

 こんにちは、c#初心者です。

 今回は初期化の高速化の方法で悩んでいるので質問させていただきました。

 ライブラリ内でstrring型と同じくらい頻繁に初期化が行われる使い方をされるクラスAを作り、そこからクラスBへ、クラスBからクラスCへ…クラスEといった感じで継承させています。

 ところが、どのクラスもクラスAと同じように頻繁に新しいインスタンスが初期化されるのでコンストラクタの処理時間が一部のパフォーマンスに結構ひびくのですが、継承しているために、クラスCを初期化するだけで、クラスAとクラスBのコンストラクタが呼び出されてしまい、このことが特にクラスEではきついのです。

 確かに呼び出しに取られる時間は少ないとはいえ、もともとコンストラクタでの作業は少ないので、割合としてはそこそこな時間を取っている状態になっています。

 そこで思いついたのが、インターフェイスを利用して継承を使わないという方法です。

 それぞれ、IA, IB, …, IEで対応するインターフェイスを実装。例えばクラスBはIA, IBの二つ、クラスEはIA~IE全てのインターフェイスを実装している状態です。

 他に思いついたのは、上記の方法+クラスB以降を全てクラスAから派生させるという方法です。速度は満点ではありませんが、これならある程度抽象化もできて、余分なコンストラクタ呼び出しも1回で済みます。

 ちなみにこれは現在製作中のライブラリ内で使われているクラスです。末端のアプリケーションなら問題ないような気がするのですが、何しろライブラリなのでこんな手法は如何かと思い質問させていただきました。

 自分が見る限りではコンストラクタ内に他に余分なコードはありませんし、無論ループはありませんが、他に高速化する方法があればそれも教えていただきたいです。

 とりあえず、言語の性能を最大限に引き出すことでの高速化をコンセプトにしているので多言語への乗り換えは無しということでお願いします。

A 回答 (6件)

> 確かに継承は機能拡張のために存在するわけではないのですが、1つ質問です。


> この文章の意図することは、「継承を機能拡張目的で使用してはならない」なのか、
> 「継承を行う場合は機能を拡張してはならない」なのかあいまいです。
その通り、前者です。機能拡張を目的とした継承は行ってはいけません。

> 「鳥」というクラスに属するもののほとんどは泳げませんが、「鳥」から派生した「水鳥」は
> 普通泳ぐことが出来ます。これは機能拡張の一種とみなす事ができるのでは。
確かにその通りです。しかし、この継承は水鳥を鳥の一種であることを表すことを目的とするものであって、泳ぐという機能を追加することを目的としたものでありません。したがって、この継承は問題ありません。

> それに関してもう一つ、「鳥 ならば 飛べる」としてしまうと、「ダチョウ」や「孔雀」は
> 飛べないので例外となり、ややこしくなるために、このような、例えわずかな例外に関しても
> 対応できるように「鳥 ならば 飛べる」というような式は作ってはならないということで
> 間違いないでしょうか?
イエスでありノーです。プログラム/システムが対象とするモデルによって異なります。
たとえば、鳥の生態を正確にシミュレーションするシステムならば「鳥 ならば 飛べる」と
一概に決めてしまうのは問題があります。一方、鳥のフライトシミュレーションシステムならば
「鳥 ならば 飛べる」として設計してしまってかまいません。

>> 許されない。
> ですが、何となく、前者の案のみに回答されている感じがするので再度質問です。
ちがう、両方ともだ。
継承関係を構築するということには何らかの設計意図があるはずです。その変更はそのまま
設計意図の変更を意味します。本来の設計意図を決定しておきながらパフォーマンス向上のために
その意図を変更してしまっては問題が発生します。
だから、パフォーマンス向上を目的とした継承関係の変更もやってはいけません。
    • good
    • 0
この回答へのお礼

 回答ありがとうございます。

 なるほど。クラスの意義が以前よりつかめてきた気がします。これからも多々、突拍子もない、はた迷惑な質問をさせていただくこともあるかと思いますが、そのときは気が向いたらお願いします。

お礼日時:2012/03/25 17:48

クラスEのコンストラクタ処理を、E,D,C,B,Aのすべてのコンストラクタ処理が順番に呼び出されるよりも、1関数に展開して書いたほうが速いというのはそれは当然だと思います。

やってることは、昔からある、関数のインライン展開です。

そこまで速さを求めているのであれば、クラスEにとって不要なコードは実行している箇所が1ステップでもあれば削除しましょう。

継承はやめて、クラスEだけで完結させて、必要最低限の初期化にすべきかと。
    • good
    • 0
この回答へのお礼

 回答ありがとうございます。

 インライン展開と言うのですか。勉強になりました。今後の役に立たせていただきます。

お礼日時:2012/03/25 17:45

> 確かにまだまだプログラミング全体に関しては勉強不足ですが、継承に関しては多少の心得はあるつもりです。


>継承することによって、継承先は継承元の全てのフィールド、プロパティ、メソッドを引継、
>さらに継承先は独自の機能などを追加、または既存の機能を再定義できる、
>ポリモーフィズム、継承先のインスタンスを継承元の型の変数に代入できるなどですよね?
>(間違っていたら指摘してください)
残念ながら、継承の意味を半分しか理解していません。

上に書いてあるのは継承の結果得られる恩恵だけです。継承の結果獲得するのはそれだけではなく、親クラスの存在するところをその子クラスに入れ替えても何の問題が発生しないようにしなくてはならないという義務も負うことになります。単にコード重複の除去や機能の再定義を実現させたいという理由だけで継承を使ってはならないのです。
http://ja.wikipedia.org/wiki/%E3%83%AA%E3%82%B9% …
を読んで熟考してください。

「そんなことはもう知ってるよ」という前に、No.3さんへの補足を読み返してください。継承を行うごとに追加されているものを表すコメントが"//変数・「機能」2"(鍵括弧を付加しました)となってますよね? これはあなたが継承をたんに機能拡張のための便利な仕組みと捉えている証拠です。
もう一度クラス構造を見直すべきです。機能の追加という観点が入り込んでいないかどうかを確認し、問題があったら再設計してください。

> ライブラリ内において本来、継承関係に在った方がしっくりくるようなものを
>「パフォーマンス向上」という名目で若干のメモリ消費と保守性、その他を犠牲にして
>インターフェイスを利用する方式でで高速化を図る手法と言うのは果たして
>許される事なのでしょうか?(どちらにせよ、目的は間違いなく達成できることが前提です)
許されない。というか、そんな目的でインターフェースを利用してもパフォーマンスは向上しない。
    • good
    • 0
この回答へのお礼

 回答・ご指摘ありがとうございます。

> 残念ながら、継承の意味を半分しか理解していません。
 ええ、もちろん多少の心得しかありませんから。

 リスコフの置換原則は初めて読ませていただきました。
> S が T の派生型であれば、プログラム内で T 型のオブジェクトが使われている箇所は全て S 型のオブジェクトで置換可能であり、それによってプログラムの動作は全く変化しない

 これは多分OKだと思います。が、「プログラムの動作は”全く”変化しない」というのは「アルゴリズム単位で変化しない」のではなく、「外観の挙動が変化しない」という意味で間違いないですよね?(間違っていたらご指摘ください)

> 事前条件を派生型で強めることはできない。つまり、上位の型よりも強い事前条件を持つ派生型を作ることはできない。
> 事後条件を派生型で弱めることはできない。つまり、上位の型よりも弱い事後条件を持つ派生型を作ることはできない。

 上記に関しては無知だったので意識してませんでしたが、その他全クラスで達成されています。事前/事後条件なんて言葉は初めて見ました。勉強になります。ありがとうございます。

> 継承をたんに機能拡張のための便利な仕組みと捉えている証拠です。
> 機能の追加という観点が入り込んでいないかどうかを確認し

 確かに継承は機能拡張のために存在するわけではないのですが、1つ質問です。この文章の意図することは、「継承を機能拡張目的で使用してはならない」なのか、「継承を行う場合は機能を拡張してはならない」なのかあいまいです。おそらく以下の例を考えると前者になるのですが…。

 「鳥」というクラスに属するもののほとんどは泳げませんが、「鳥」から派生した「水鳥」は普通泳ぐことが出来ます。これは機能拡張の一種とみなす事ができるのでは。(この推論が間違っていればご指摘ください)

 それに関してもう一つ、「鳥 ならば 飛べる」としてしまうと、「ダチョウ」や「孔雀」は飛べないので例外となり、ややこしくなるために、このような、例えわずかな例外に関しても対応できるように「鳥 ならば 飛べる」というような式は作ってはならないということで間違いないでしょうか? それとも、何かと例外の多い「自然界」と人の手によって調節されている「プログラム」を対比すること自体がよろしくありませんか?

> 許されない。

 まあ、普通はそうですよね。単なる思い付きなんで確認したかっただけです。

> そんな目的でインターフェースを利用してもパフォーマンスは向上しない。

 もちろん、計算量は大して変わらないというか、計算量オーダーにすると結局O(1)です、が、計測してみると

 「ClassA」「ClassE」の初期化にかかる時間はそれぞれ「10,000,000回」のループでおよそ「500ms」、「1100ms」で、
 全く継承関係を持たない場合(前者の案)およそ「500ms」、「550ms」でした。実に2倍高速です(初期化に限れば)。

 「ClassB」以降を「ClassA」から直接派生させるという形式(後者の案)を取ったとしても「650ms」と1.7倍近く高速になっています(やはり初期化に限り ですが)。

 二重のループ中で初期化されることも多く、あるループの中では総処理時間の実に20%を占めていました。すなわち単純計算では8~10%高速になるわけです(とはいっても体感速度は変わらないのですが)。

 単純な数値だけを見ればパフォーマンスは向上しているということになるのですが、

> パフォーマンスは向上しない。

 というのは「微視的な速度向上があったとしても巨視的な速度にはつながらず、”全体として”のパフォーマンスには影響が出ない」ため、不毛だということでしょうか?

 それともう一つ、

> 許されない。

 ですが、何となく、前者の案のみに回答されている感じがするので再度質問です。これは、後者の案、「継承関係は持つが、ClassB以降を全てClassAから直接派生させる」ような場合においても許されないということでしょうか?(ちなみにNo3の補足には後者もインターフェイスを実装していますが、インターフェイスなしの方が良いのならそうできます)

お礼日時:2012/03/25 10:43

>> Aの初期化を省くということは,Aの部分が使えなくなることであり,継承する意味がありません。


>  少し誤解があるようです。
誤解?
継承についての勉強不足,それにつきると思いますが。

>  今回の継承関係はタイピングの手間を省くためにあるような状態です。それならいっそのこと、継承関係をなくすか、またはクラスB~EがクラスAから直接派生するという形にすることでコンストラクタの何重もの呼び出しを避けようと考えたのです。
タイピングの手間を省くための継承関係というのは意味が不明です。
継承はそういう目的に使うものではないですから。
元々継承を使ったのが間違いなのだから,継承を使わなければよいかと。

この回答への補足

 ご指摘ありがとうございます。

> 不要だったり意味がなかったりする継承関係があるのであれば,まずはそれを削ることを考えてみてはどうでしょう。

 一つ前のご指摘ですが、これに関しては”本来”削れる継承関係は在りません「ClassE」は「ClassA」~「ClassD」すべての性質を持っています。

>誤解?
>継承についての勉強不足,それにつきると思いますが。

 確かにまだまだプログラミング全体に関しては勉強不足ですが、継承に関しては多少の心得はあるつもりです。継承することによって、継承先は継承元の全てのフィールド、プロパティ、メソッドを引継、さらに
継承先は独自の機能などを追加、または既存の機能を再定義できる、ポリモーフィズム、継承先のインスタンスを継承元の型の変数に代入できるなどですよね?(間違っていたら指摘してください)

 ですから、

public class ClassA
{
  //変数・機能1
}

public class ClassB : ClassA
{
  //変数・機能2
}

public class ClassC : ClassB
{
  //変数・機能3
}

とする代わりに、

public class ClassA : IClassA
{
  //変数・機能1
}

public class ClassB : IClassA, IClassB
{
  //変数・機能1
  //変数・機能2
}

public class ClassC : IClassA, IClassB, IClassC
{
  //変数・機能1
  //変数・機能2
  //変数・機能3
}

または

public class ClassA
{
  //変数・機能1
}

public class ClassB : ClassA, IClassB
{
  //変数・機能2
}

public class ClassC : ClassA, IClassB, IClassC
{
  //変数・機能2
  //変数・機能3
}

 とするのは如何なのでしょうかと質問させていただきました。

 流石に初心者でも継承関係が無ければ継承元の機能が使えない、継承しなくても同じ名前のプロパティ、メソッドがあればインターフェイスが利用できるかも知れない、インターフェイスを使えば簡易的にポリモーフィズムは実現可能くらいは知っていると思いますよ(超初心者の自分が言うのだから間違いない…ハズ)。

> タイピングの手間を省くための継承関係というのは意味が不明です。

 コードの重複がなくなり、保守性の向上(場合によっては可読性も向上)が見込まれる。端的に言えばタイピングの量が減ったこと”にも”なりますよね?

 確かに迂闊な記述だったことは間違いないです。しかし、この辺りは「高速化と、許されるかどうか」という本題から逸れているので、そこを拾う意味があったのかは疑問ですが。

 というわけなのですが、副題の質問の回答が1つと、その他細かなご指摘がある以外は本題の質問への回答が全く見当たらないの質問をもう少し噛み砕いて再度質問させていただきます。

 ライブラリ内において本来、継承関係に在った方がしっくりくるようなものを「パフォーマンス向上」という名目で若干のメモリ消費と保守性、その他を犠牲にしてインターフェイスを利用する方式でで高速化を図る手法と言うのは果たして許される事なのでしょうか?(どちらにせよ、目的は間違いなく達成できることが前提です)

補足日時:2012/03/25 01:18
    • good
    • 0

コンストラクタの呼び出しの何が問題なのかが理解できません。


コンストラクタの呼び出しは,そのインスタンスの初期化に必要なコストのはずです。
BがAを継承しているということは,BのインスタンスのAの部分の初期化が必要なはずで,それを担うのがAのコンストラクタになります。
Aの初期化を省くということは,Aの部分が使えなくなることであり,継承する意味がありません。

不要だったり意味がなかったりする継承関係があるのであれば,まずはそれを削ることを考えてみてはどうでしょう。
速度の前に設計思想を間違えていると,単に使えないライブラリができあがってしまいますよ。

この回答への補足

> Aの初期化を省くということは,Aの部分が使えなくなることであり,継承する意味がありません。

 少し誤解があるようです。

 さて、以下のようなコードが続くとしたら、Method5()の処理はどうなっているでしょうか?

void Method1()
{
  //処理1
}

void Method2()
{
  Method1();
  //処理2
}

void Method3()
{
  Method2();
  //処理3
}

……

 「Method4を呼び出す」→「Method3」を呼び出す→「Method2」を呼び出す→「Method1を呼び出す」→「処理1の実行」→「Method2に戻る」→「処理2の実行」→「Method3に戻る」→「処理3の実行」→「Method4に戻る」→「処理4の実行」→「Method5に戻る」→「処理5の実行」→「完了」
となるはずです。

 しかし、

void AllInOneMethod()
{
  //処理1~5
}

 のようにすればどうなるか…簡単に想像が付くと思います。

 確かに、コードの重複は保守性の面からも、メモリ消費の面からも良くないかもしれませんが、元々コンストラクタには0~2行程度の処理しかありません。そこまで大きく損なわれるとは思っていません。

 今回の継承関係はタイピングの手間を省くためにあるような状態です。それならいっそのこと、継承関係をなくすか、またはクラスB~EがクラスAから直接派生するという形にすることでコンストラクタの何重もの呼び出しを避けようと考えたのです。

補足日時:2012/03/24 23:41
    • good
    • 0

クラスEでは、クラスA, Bのコンストラクタの処理内容は不要であるが、


継承の関係で走ってしまって遅くなるという内容でしょうか ?

この回答への補足

 回答ありがとうございます。

 大体はそういう意味です。

 さて、以下のようなコードが続くとしたら、Method5()の処理はどうなっているでしょうか?

void Method1()
{
  //処理1
}

void Method2()
{
  Method1();
  //処理2
}

void Method3()
{
  Method2();
  //処理3
}

……

 「Method4を呼び出す」→「Method3」を呼び出す→「Method2」を呼び出す→「Method1を呼び出す」→「処理1の実行」→「Method2に戻る」→「処理2の実行」→「Method3に戻る」→「処理3の実行」→「Method4に戻る」→「処理4の実行」→「Method5に戻る」→「処理5の実行」→「完了」
となるはずです。

 しかし、

void AllInOneMethod()
{
  //処理1~5
}

 のようにすればどうなるか…簡単に想像が付くと思います。

 確かに、コードの重複は保守性の面からも、メモリ消費の面からも良くないかもしれませんが、元々コンストラクタには0~2行程度の処理しかありません。そこまで大きく損なわれるとは思っていません。

 今回の継承関係はタイピングの手間を省くためにあるような状態です。それならいっそのこと、継承関係をなくすか、またはクラスB~EがクラスAから直接派生するという形にすることでコンストラクタの何重もの呼び出しを避けようと考えたのです。

補足日時:2012/03/24 23:40
    • good
    • 0

お探しのQ&Aが見つからない時は、教えて!gooで質問しましょう!