メモリ管理に関するエントリーレベルのエンジニアの質問


9

入門レベルのソフトウェア開発者としてのポジションを始めてから数ヶ月になります。いくつかの学習曲線(言語、専門用語、VBやC#の構文など)を過ぎた今、より優れたソフトウェアを書くために、より難解なトピックに焦点を合わせ始めています。

同僚に簡単な質問をしたところ、「間違ったことに集中している」と答えました。私はこの同僚を尊重していますが、これが「間違ったこと」に焦点を当てていることには同意しません。

ここにコード(VB)があり、その後に質問が続きました。

注:関数GenerateAlert()は整数を返します。

Dim alertID as Integer = GenerateAlert()
_errorDictionary.Add(argErrorID, NewErrorInfo(Now(), alertID))    

対...

 _errorDictionary.Add(argErrorID, New ErrorInfo(Now(), GenerateAlert()))

私は後者を最初に作成し、「Dim alertID」を使用して書き直したので、他の誰かが読みやすくなっています。しかし、ここに私の懸念と質問がありました:

Dim AlertIDでこれを書き込むと、実際にはより多くのメモリを消費します。有限ではありますが、このメソッドを何度も呼び出す必要がありますか?.NETはこのオブジェクトAlertIDをどのように処理しますか。.NETの外では、使用後にオブジェクトを手動で破棄する必要があります(サブの末尾近く)。

ガベージコレクションだけに頼らない知識豊富なプログラマーになりたいです。私はこれを考えすぎていますか?私は間違ったことに集中していますか?


1
最初のバージョンの方が読みやすいので、彼が100%であると簡単に言えます。私はコンパイラがあなたが気にかけていることさえも面倒を見るかもしれないと賭けます。そうでなかったとしても、あなたは時期尚早に最適化しています。
リグ

6
名前付き整数よりも匿名整数の方が実際に多くのメモリを使用するかどうか、私にはまったくわかりません。いずれにせよ、これは実際には時期尚早の最適化です。このレベルで効率を心配する必要がある場合(私はそうしないとほぼ確信しています)、C#ではなくC ++が必要になる場合があります。パフォーマンスの問題と内部で何が起こるかを理解するのは良いことですが、これは大きな森の中の小さな木です。
psr 2012

5
名前付きvs匿名の整数は、特に匿名整数は、YOUが名前を付けなかった名前付き整数であるため、より多くのメモリを使用しません(コンパイラーは名前を付ける必要があります)。多くても、名前付き整数のスコープは異なるため、存続時間が長くなる可能性があります。匿名整数は、メソッドがそれを必要とする間だけ存続し、名前付き整数は、親がそれを必要とする間存続します。
Joel Etherton、2012

見てみましょう... Integerがクラスの場合、ヒープに割り当てられます。ローカル変数(スタック上の可能性が最も高い)は、その変数への参照を保持します。参照はオブジェクトerrorDictionaryに渡されます。ランタイムが参照カウントなどを行っている場合、参照がなくなると、それ(オブジェクト)はヒープから割り当て解除されます。スタックにあるものは、メソッドが終了すると自動的に「割り当て解除」されます。それがプリミティブである場合、それは(おそらく)スタックに行き着きます。
ポール

あなたの同僚は正しかった:あなたの質問によって提起された問題は、最適化についてではなく、読みやすさについてであったはずです。
ヘイレム2012

回答:


25

「時期尚早な最適化は、プログラミングにおけるすべての悪(または少なくともその大部分)の原因です。」-ドナルド・クヌース

最初のパスになると、コードが正しく、クリーンになるように記述します。後でコードがパフォーマンスクリティカルであると判断された場合(これをプロファイラーと呼ぶツールを決定するツールがあります)、コードを書き直すことができます。コードがパフォーマンス重視であると判断されない場合は、読みやすさがはるかに重要です。

これらのパフォーマンスと最適化のトピックを掘り下げる価値はありますか?絶対に必要ですが、会社のドルではありません。


1
他の誰のドルにすべきですか?あなたの雇用者はあなたよりもあなたのスキルの向上から利益を得ます。
Marcin、2012

現在のタスクに直接貢献していない主題は?あなたは自分の時間にこれらのことを追求するべきです。私が座って、その日の好奇心を刺激したすべてのCompSciアイテムを調査した場合、私は何もしません。それが私の夜の目的です。
クリストファー・バーマン2012

2
変だ。私たちの一部には私生活があり、私が言うように、雇用主は主に研究から利益を得ています。重要なのは、実際に一日中費やさないことです。
Marcin、2012

6
よかったね。ただし、実際にはそれが普遍的なルールになるわけではありません。さらに、労働者が職場で学ぶことを思いとどまらせた場合、あなたがしたことは、労働者が学習することを思いとどまらせ、実際にスタッフの育成にお金を払っている別の雇用者を見つけるように彼らを励ますことです。
Marcin、2012

2
上記のコメントに記載されている意見を理解しました。お昼休みにお願いしたことをお願いします。:)。繰り返しますが、こことStack Exchangeサイト全体で、ご意見をお寄せいただきありがとうございます。それは非常に貴重です!
Sean Hobbs、2012

5

平均的な.NETプログラムの場合、はい、それは考えすぎです。.NETの内部で何が起こっているのかを正確に把握したい場合がありますが、これは比較的まれです。

私が持っていた難しい移行の1つは、CとMASMの使用から90年代のクラシックVBでのプログラミングに切り替えることでした。私はすべてのサイズと速度を最適化することに慣れていました。私は効果的にするために、大部分はこの考え方を手放し、VBにそれをさせる必要がありました。


5

私の同僚はいつも言っていたので:

  1. 機能させる
  2. すべてのバグを修正して、問題なく動作するようにします
  3. しっかりさせる
  4. パフォーマンスが遅い場合は最適化を適用します

つまり、KISSを常に念頭に置いてください(単純な愚かさを保つ)。過剰設計のため、一部のコードロジックを過剰に考えることは、次回ロジックを変更する際の問題となる可能性があります。ただし、コードをクリーンでシンプル保つことは、常に良い習慣です。

ただし、時間と経験によって、どのコードの臭いがするかがわかり、すぐに最適化が必要になります。


3

Dim AlertIDを使用してこれを書き込む必要があります

読みやすさが重要です。しかし、あなたの例では、あなたが本当にもっと読みやすいもの作っているのかわかりません。GenerateAlert()には適切な名前があり、ノイズが多くなることはありません。おそらくあなたの時間のより良い使い方があるでしょう。

実際にはより多くのメモリを消費します。

そうではないようです。これは、コンパイラーが行う比較的単純な最適化です。

このメソッドを何度も呼び出す必要がありますか?

中間としてローカル変数を使用しても、ガベージコレクタには影響しません。GenerateAlert()が新しいメモリを使用している場合は、問題になります。しかし、それはローカル変数かどうかに関係なく重要になります。

.NETはこのオブジェクトAlertIDをどのように処理しますか。

AlertIDはオブジェクトではありません。GenerateAlert()の結果はオブジェクトです。AlertIDは変数であり、ローカル変数の場合は、追跡するためのメソッドに関連付けられた単なるスペースです。

.NETの外では、使用後にオブジェクトを手動で破棄する必要があります

これは、関連するコンテキストと、GenerateAlert()によって提供されるインスタンスの所有権セマンティクスに依存する、より難しい質問です。一般に、インスタンスを作成したものはすべて削除する必要があります。プログラムが手動のメモリ管理を念頭に置いて設計されている場合、プログラムは大きく異なる可能性があります。

ガベージコレクションだけで中継するのではなく、知識の豊富なプログラマーになるようにしたいと思っています。私はこれを考えすぎていますか?私は間違ったことに集中していますか?

優れたプログラマーは、ガベージコレクターなど、使用可能なツールを使用します。忘却して生きるよりも、物事を考えすぎた方がいいです。あなたは間違ったことに集中しているかもしれませんが、私たちがここにいるので、あなたはそれについて学ぶこともできます。


2

機能させる、クリーンにする、しっかりさせる、そして必要なだけの速度で機能させる

それが通常の順序です。あなたの最優先事項は、要件から外れる受け入れテストに合格するものを作ることです。これはクライアントの最優先事項なので、これが最優先事項です。開発期限内に機能要件を満たします。次の優先事項は、わかりやすく、読みやすいコードを書くことです。したがって、必要になったときにWTFがなくても後世に維持できます(「if」の質問はほとんどありません。あなたまたはあなたの後の誰かが行かなければなりません)。戻って何かを変更/修正してください)。3番目の優先事項は、コードをSOLID手法(または、必要に応じてGRASP)に準拠させることです。これにより、コードはモジュール化された再利用可能な交換可能なチャンクに入れられ、メンテナンスを再度支援します(ユーザーが何をしたか、またその理由を理解できるだけでなく、しかし、コードの断片を外科的に削除して置き換えることができるきれいな行があります)。最後の優先事項はパフォーマンスです。コードがパフォーマンス仕様に準拠しなければならないほど重要である場合、まず間違いなく、正しく、クリーンで、SOLIDにするために十分に重要です。

クリストファー(およびドナルドクヌース)を反響させて、「時期尚早な最適化はすべての悪の根源です」。さらに、検討している最適化の種類は、マイナー(ソースコードで名前を付けるかどうかに関係なく、新しいオブジェクトへの参照がスタックに作成されます)と、コンパイル時に違いを引き起こさないタイプの両方です。 IL。変数名はILに持ち越されないので、最初の(おそらく唯一の)使用の直前に変数を宣言しているので、ILが2つの例で同じであるとは信じられません。したがって、同僚は100%正しいです。何かを最適化するための名前付き変数とインラインのインスタンス化を表示している場合、間違った場所を表示しています。

.NETでのマイクロ最適化は、それだけの価値はほとんどありません(私はケースの99.99%について話しています)。C / C ++では、多分、あなたが何をしているのか知っているなら。.NET環境で作業しているときは、ハードウェアの金属から十分離れているため、コード実行にかなりのオーバーヘッドがあります。だから、あなたはすでに猛烈な速度をあきらめていることを示す環境にいて、代わりに「正しい」コードを書こうとしているとすると、.NET環境で何かが本当に十分に速く動作していない場合、その複雑さも高すぎるか、並列化を検討する必要があります。以下は、最適化のために従うべきいくつかの基本的な指針です。最適化の生産性(費やした時間で得られる速度)が急上昇することを保証します。

  • 関数の形状を変更することは、係数を変更することよりも重要です -WRT Big-Ohの複雑さ、N 2アルゴリズムで実行する必要があるステップの数を半分に減らすことができ、実行する場合でも2次複雑度アルゴリズムを使用できます。以前の半分の時間。それがこのタイプの問題の複雑さの下限である場合はそうですが、同じ問題に対してNlogN、線形、または対数の解決策がある場合は、アルゴリズムを切り替えることで、手元にあるものを最適化するよりも、複雑さを減らす方が得られます。
  • 複雑さを確認できないからといって、コストがかからないというわけではありません -単語の中で最もエレガントな1行の多くはひどく実行されます(たとえば、正規表現の素数チェッカーは、効率的でありながら指数関数的複雑度関数です)平方根よりも小さいすべての素数で数値を除算することを含む素数評価は、O(Nlog(sqrt(N)))のオーダーです。Linqは、コードを簡略化する優れたライブラリですが、SQLエンジンとは異なり、.Netコンパイラーは、クエリを実行する最も効率的な方法を見つけようとしません。メソッドを使用するとどうなるか、したがって、生成中にチェーンの前(または後)に配置するとメソッドが高速になる理由を知っている必要があります。同じ結果。
  • OTOH、ほとんどの場合、ソースの複雑さとランタイムの複雑さの間にはトレードオフがあります。SelectionSortの実装は非常に簡単です。あなたはおそらく10LOC以下でそれを行うことができます。MergeSortは少し複雑で、Quicksortはさらに複雑で、RadixSortはさらに複雑です。ただし、アルゴリズムのコーディングが複雑になると(したがって「先行」開発時間も)、実行時の複雑さが減少します。MergeSortとQuickSortはNlogNであり、RadixSortは一般に線形と見なされます(技術的にはNlogMで、MはNの最大数です)。
  • すぐに中断する -安価で実行できるチェックがあり、それが真実である可能性が高く、次に進むことができる場合は、最初にそのチェックを実行します。たとえば、アルゴリズムが1、2、または3で終わる数値のみを考慮する場合、最も可能性の高いケース(完全にランダムなデータが与えられた場合)が他の数字で終わる数値であるため、数値が終了しないことをテストします1、2、または3の前に、数値が1、2、または3で終わるかどうかを確認します。ロジックの一部にA&Bが必要で、P(A)= 0.9でP(B)= 0.1が必要な場合は、確認します。 !Aなら!B(if(myObject != null && myObject.someProperty == 1))のように、またはBの評価にAの9倍以上かかる()場合を除き、Bが最初if(myObject != null && some10SecondMethodReturningBool())です。
  • すでに答えがわかっている質問をしないでください -一連の「フォールスルー」条件があり、これらの条件の1つ以上が、チェックする必要があるより単純な条件に依存している場合は、両方のチェックを行わないでください。これらは独立しています。たとえば、Aを必要とするチェックとA && Bを必要とするチェックがある場合は、Aをチェックし、trueの場合はBをチェックする必要があります。!Aの場合は!A && Bなので、気にしないでください。
  • 何かをする回数が増えるほど、それがどのように行われるかに注意を払う必要があります -これは、多くのレベルで、開発における共通のテーマです。一般的な開発の意味では、「一般的なタスクに時間がかかる、または面倒な場合は、イライラし、知識が十分にあり、より良い方法を思いつくまで、それを続けます」。コードの面では、非効率的なアルゴリズムが実行される回数が増えるほど、最適化によって全体的なパフォーマンスが向上します。バイナリアセンブリとそのデバッグシンボルを取得し、いくつかのユースケースを実行した後、最も実行されたコード行を示すことができるプロファイリングツールがあります。これらのライン、およびそれらのラインを実行するラインは、達成する効率の向上が倍増するため、最も注意を払うべきものです。
  • より複雑なアルゴリズムは、十分なハードウェアを投入すると、それほど複雑ではないアルゴリズムのように見えます。アルゴリズムが、実行しているシステム(またはその一部)の技術的な限界に近づいていることを認識しなければならない場合があります。その時点から、より高速にする必要がある場合は、より優れたハードウェアで実行するだけで、より多くを得ることができます。これは並列化にも当てはまります。N 2複雑度アルゴリズムは、Nコアで実行すると線形に見えます。したがって、記述しているアルゴリズムのタイプの複雑さの下限に達していることが確実な場合は、「分割統治」する方法を探してください。
  • 十分に高速な場合は高速です-特定のチップを対象とするアセンブリを手作業で梱包しない限り、常に何かを得る必要があります。ただし、ハンドパッキングアセンブリにしたくない場合は、クライアントが「十分」と呼ぶものを常に心に留めておく必要があります。繰り返しますが、「時期尚早な最適化はすべての悪の根源です」。あなたのクライアントがそれを十分に速く呼ぶとき、あなたは彼がそれがもう十分な速度であると思わなくなるまであなたは終わりです。

0

最適化について早い段階で心配する唯一の時間は、巨大なもの、または膨大な回数実行されることがわかっているものに対処していることがわかったときです。

「巨大」の定義は明らかに、ターゲットシステムがどのようなものかによって異なります。


0

デバッガーでステップ実行する方が簡単だからといって、2行バージョンの方がいいと思います。複数の埋め込み呼び出しを含む行は、より困難になります。

弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.