web-dev-qa-db-ja.com

なぜそれほど多くの開発者がパフォーマンス、可読性、保守性が共存できないと信じているのですか?

この質問 に対応する際、多くの開発者が優れた設計ではパフォーマンスを考慮に入れるべきではないと考えたのはなぜでしょうか。

優れた設計では、作成時にパフォーマンスも考慮されると思います。優れた設計の優れた開発者は、読みやすさや保守性に悪影響を与えることなく効率的なプログラムを書くことができると思います。

極端な場合があることは承知していますが、なぜ多くの開発者が効率的なプログラム/設計は可読性や保守性の低下につながり、その結果、パフォーマンスは設計上の考慮事項にならないと主張するのですか?

34
justin

私はそう思いますそのような見解は通常、時期尚早な(マイクロ)最適化の試みに対する反応です、これはまだ一般的であり、通常は良いよりもはるかに害が大きいです。そのような見方に対抗しようとすると、他の極端に陥る、または少なくともそのように見えるのは簡単です。

それにもかかわらず、ここ数十年のハードウェアリソースの莫大な開発により、今日作成されたほとんどのプログラムでは、パフォーマンスが主要な制限要因でなくなったというのは事実です。もちろん、設計段階で予想される達成可能なパフォーマンスを考慮に入れる必要がありますパフォーマンスが主要な問題となる可能性があるケースを特定する。そして、パフォーマンスを最初から設計することは確かに重要です。ただし、全体的なシンプルさ、読みやすさ、保守性はさらに重要です。他の人が指摘したように、パフォーマンスが最適化されたコードは、最も単純な実用的なソリューションよりも複雑で、読み取りや保守が難しく、バグが発生しやすくなります。したがって、最適化に費やされたすべての努力は、証明されている必要があります-信じられているだけではありません-プログラムの長期保守性をできるだけ低下させずに、真の利益をもたらすため。つまり、優れた設計は、複雑でパフォーマンスを重視する部分をコードの残りの部分から分離するであり、これは可能な限り単純かつクリーンに保たれます。

38
Péter Török

高性能コードに取り組む開発者の側からあなたの質問に来ると、設計において考慮すべきいくつかの事柄があります。

  • 時期尚早に悲観しないでください。複雑さが等しい2つの設計から選択する場合は、最高のパフォーマンス特性を持つ設計を選択してください。有名なC++の例の1つは、ループ内のカウンター(またはイテレーター)のポストインクリメントの普及です。これは完全に不要な時期尚早の悲観化であり、コストはかかりませんが、実行しないでください。
  • 多くの場合、マイクロ最適化の近くに行くビジネスはまだありません。アルゴリズムによる最適化は、手間がかからず、ほとんどの場合、実際の低レベルの最適化よりもはるかに簡単に理解できます。
  • パフォーマンスが絶対に重要である場合にのみ、あなたは落ちて汚くなります。実際には、最初にできる限りコードを分離し、それからあなたは落ちて汚い。キャッシュスキーム、遅延評価、キャッシュ用のメモリレイアウトの最適化、インライン組み込み関数またはアセンブリのブロック、テンプレートのレイヤーごとのレイヤーなどを使用して、非常に汚れています。このコードでメンテナンスを行う必要がある場合は害を及ぼしますが、パフォーマンスは絶対に重要であるため、これを行う必要があります。編集:ところで、私はこのコードが美しくあり得ないと言っているわけではありません。可能な限り美しくする必要がありますが、最適化されていないコードと比較して、非常に複雑で、しばしば複雑になります。

正しく取得し、美しく、すばやく取得します。その順番で。

38

@greengitのナイスダイアグラムを "借りる"ことができると想定して、少し追加します。

|
P
E
R
F
O  *               X <- a program as first written
R   * 
M    *
A      *
N        *
C          *  *   *  *  *
E
|
O -- R E A D A B I L I T Y --

私たちは皆、トレードオフ曲線があることを「教えられました」。また、私達は私達が私達が書くどんな与えられたプログラムも非常にタイトである曲線上で非常に最適なプログラマーであると仮定しました。プログラムが曲線上にある場合、1つの次元での改善は必ず他の次元でのコストを招きます。

私の経験では、プログラムは調整、微調整、ハンマー処理、ワックス処理され、一般に「コードゴルフ」に変換されることによってのみ曲線に近づきます。ほとんどのプログラムには、あらゆる面で改善の余地がたくさんあります。 これが私の意味です

16
Mike Dunlavey

正確に言うと、パフォーマンスの高いソフトウェアコンポーネントは一般に、他のソフトウェアコンポーネントよりも桁違いに複雑です(他のすべてのものは同じです)。

それでもそれほど明確ではありませんが、パフォーマンスメトリックが非常に重要な要件である場合、そのような要件に対応するために設計が複雑であることが不可欠です。危険は、コンポーネントから数ミリ秒余分にスクイーズしようとする比較的単純な機能にスプリントを浪費する開発者です。

いずれにせよ、設計の複雑さは、開発者がそのような設計をすばやく習得して習熟する能力と直接的な相関関係があり、複雑なコンポーネントの機能をさらに変更すると、単体テストでは検出できないバグが発生する可能性があります。複雑な設計には、100%の単体テストカバレッジの目標をパイプの夢のようにすることを検討するための、より多くのファセットと可能なテストケースがあります。

とはいえ、元々の作者の無知に基づいて愚かに書かれ、不必要に複雑だったからといって、パフォーマンスの悪いソフトウェアコンポーネントはパフォーマンスが低い可能性があることに注意してください、完全に不必要なコードであり、結果として単一のコードパスになるなど...)これらのケースは、リファクタリングの結果として発生するコード品質とパフォーマンスの向上の問題であり、必ずしも意図した結果ではありません。

ただし、適切に設計されたコンポーネントを想定すると、パフォーマンスのために調整された同様に適切に設計されたコンポーネント(他のすべてが同じ)に比べて、複雑さは常に少なくなります。

5
maple_shaft

それらが共存できないほどではありません。問題は、最初の反復で全員のコードが遅く、読めず、保守できないことです。残りの時間は、最も重要なものを改善するために費やされます。それがパフォーマンスであれば、それを試してください。意地悪にひどいコードを書かないでください。ただX高速にする必要がある場合は、X高速にしてください。性能と清潔さは基本的に無相関だと思います。実行コードは醜いコードを引き起こしません。ただし、コードのすべてのビットを高速に調整することに時間を費やしている場合、時間を費やしていないことは何でしょうか?コードをクリーンでメンテナンス可能なものにする。

3
 | 
 P 
 E 
 R 
 F 
 O * 
 R * 
 M * 
 A * 
 N * 
 C * * * * * 
 E 
 | 
 O-読みやすさ-

ご覧のように...

  • 読みやすさを犠牲にするとパフォーマンスは向上しますが、それだけです。ある時点の後で、より良いアルゴリズムとハードウェアのような「実際の」手段に頼らなければなりません。
  • また、読みやすさを犠牲にしてパフォーマンスが低下することは、ある程度しか発生しません。その後、パフォーマンスに影響を与えることなく、プログラムを必要なだけ読みやすくすることができます。たとえば、より役立つコメントを追加しても、パフォーマンスは低下しません。

したがって、パフォーマンスと可読性はある程度関連していますが、ほとんどの場合、前者を後者よりも優先する大きなインセンティブはありません。そして私はここで高級言語について話している。

2
treecoder

3つすべてを達成するのは難しいと思います。 2つは実現可能だと思う。たとえば、場合によっては効率と可読性を実現することは可能だと思いますが、マイクロチューニングされたコードでは保守性が難しい場合があります。地球上で最も効率的なコードには、Intelが記述したSoAベクトル化されたマルチスレッドSIMDコードを理解できる人でない限り、一般におそらく両方保守性と可読性が欠けています。インライン化されたアセンブリ、または業界で最も使用されている最先端のアルゴリズム。40ページの数学的論文がわずか2か月前に公開され、1つの非常に複雑なデータ構造に対して12ライブラリに相当するコードが含まれています。

マイクロ効率

世論に反するかもしれないと私が提案することの1つは、最もスマートなアルゴリズムコードは、最も微調整された単純なアルゴリズムよりも維持が難しいことが多いということです。スケーラビリティの改善により、マイクロチューニングされたコード(例:キャッシュフレンドリーなアクセスパターン、マルチスレッド、SIMDなど)よりも効果が高まるという考えは、少なくとも非常に複雑な業界で働いていたため、私が挑戦したいことです。データ構造とアルゴリズム(ビジュアルFX業界)、特にメッシュ処理などの領域。なぜならば、ビッグバンは大きいかもしれないが、ブランドであるため、これまで誰も聞いたことのない新しいアルゴリズムとデータ構造を導入すると、そのコストは非常に高くなる新着。さらに、数学的ウィザードによって公開された最先端の産業用アルゴリズムではなく、「理論的に」はあまり拡張されなかった一般的なコンピューターサイエンスとコンピューターアーキテクチャの知識に依存するより単純なコードで、このようなアルゴリズムとデータ構造を打ち負かすことに成功しました(例:linearithmic vs. linear)が微調整されましたが、私のバージョンでは必要なコードの約1/100しか必要としませんでしたが、入力が大きいほど速くなり(入力が大きくなるほど速くなり、バージョンがさらにスケーラブルになりました)理論的にはそれほどスケーラブルではありませんでしたが).

したがって、アルゴリズムの最適化は常に、たとえばメモリアクセスパターンに関連する最適化よりも優先されるというこの考えは、常に私がまったく同意しなかったものです。もちろん、バブルソートを使用している場合、ミクロの最適化を行っても役に立ちません...しかし、当然のことながら、それが常に明確であるとは限りません。そして間違いなく、アルゴリズムによる最適化は、ミクロの最適化よりも維持が困難です。流体シミュレーションを最先端の方法で加速するためのDreamworkのOpenVDBコードよりも、古典的で簡単なBVHアルゴリズムを採用し、クラップをマイクロチューンするIntel's Embreeのメンテナンスがはるかに簡単だと思います。したがって、少なくとも私の業界では、数千、数千の新しいアルゴリズムやデータ構造を考え出すのではなく、Intelが現場に足を踏み入れたときのように、コンピューターアーキテクチャのマイクロ最適化に精通している人をもっと増やしたいと思います。効果的なマイクロ最適化により、新しいアルゴリズムを発明する理由がますます少なくなる可能性があります。

以前はレガシーコードベースで作業していましたが、ほとんどすべてのユーザー操作が独自のデータ構造とその背後にアルゴリズムを備えていました(最大で数百のエキゾチックデータ構造が追加されています)。そして、それらのほとんどは非常に偏ったパフォーマンス特性を持ち、非常に狭く適用できました。システムがより広く適用可能な数十のデータ構造を中心に展開できれば、はるかに簡単だったでしょうし、マイクロ最適化がはるかに優れていればそうだったと思います。マイクロ最適化は、キャッシュミスの残さを伴う厳密な読み取り専用の目的で安全に使用できない数百のマイクロ悲観化データ構造の違いを意味する場合、このような場合に潜在的に保守性を大幅に改善できるため、このケースについて言及します適切な場合と、全体で非常に効率的に機能する数十のマイクロ最適化データ構造との比較。

関数型言語

一方、私がこれまでに遭遇した中で最も保守しやすいコードのいくつかは、かなり効率的でしたが、関数型言語で書かれているため、非常に読みにくいものでした。一般に、可読性と超保守性は、私の意見では相反する考えです。

コードを読みやすく、保守しやすく、一度に効率的にすることは本当に困難です。通常、読みやすさを犠牲にして保守性を損なったり、保守性を犠牲にして効率を上げたりするなど、2つではなくてもこれら3つのうちの1つを少し妥協する必要があります。他の2つをたくさん探すときに悩むのは、通常は保守性です。

可読性と保守性

今言ったように、可読性と保守性はnot調和のとれた概念であると信じています。結局のところ、私たち人間のほとんどにとって最も読みやすいコードは、人間の思考パターンに非常に直感的にマッピングされており、人間の思考パターンは本質的にエラーが発生しやすいものです: "これが発生した場合、これを実行します。おっと、私は何かを忘れました!これらのシステムが相互に対話する場合、これはこのシステムがこれを行うことができるように起こるはずです...あら、このイベントがトリガーされたときにそのシステムはどうなりますか? "正確な引用ですが、ローマがソフトウェアのように構築されていれば、鳥が壁に着陸して倒れるだけだと誰かが言ったことがあります。これは、ほとんどのソフトウェアに当てはまります。私たちがよく考えているよりも壊れやすいものです。一見無害に見えるコードが数行あちこちにあり、デザイン全体を再考するまでにコードを停止させる可能性があります。可能な限り読みやすくすることを目的とする高級言語は、このような人間のデザインエラーの例外ではありません。

純粋な関数型言語は、実行可能な範囲でこれにほぼ無傷に近いものです(無傷に近いものではありませんが、ほとんどの場合よりもはるかに近いものです)。そしてそれは部分的には人間の思考に直感的に対応していないためです。彼らは読むことができません。彼らは私たちに思考パターンを強制します。それにより、可能な限り少ない知識を使用して、副作用を引き起こさずに、できるだけ少ない特別なケースで問題を解決する必要があります。それらは非常に直交しており、コードを頻繁に変更および変更することができますので、すべてを書き直すことなく、全体的なデザインについての考え方を変えるという点でさえ、図面ボードでデザインを再考する必要があります。それよりも保守しやすくなるとは思われません...しかし、コードは依然として非常に読みにくく、ほとんどの場合、非常に保守可能です(問題なく変更するのは簡単です)。

1
user204677

私の意見では、パフォーマンスはそれが 実際の問​​題 (または要件など)である場合の考慮事項です。そうしないと、マイクロ最適化につながる傾向があり、数マイクロ秒をあちこちに節約するだけでコードが難読化され、保守性と可読性が低下する可能性があります。代わりに、システムの実際のボトルネックに焦点を当て、必要に応じて、そこでパフォーマンスを強調する必要があります。

1

重要なのはnot読みやすさは常に効率よりも優れていることです。アルゴリズムが非常に効率的である必要があることを最初から知っている場合、それはそれを開発するために使用する要因の1つになります。

事はmostユースケースで、高速なコードを目隠しする必要がない場合です。多くの場合、IOまたはユーザーインタラクションにより、アルゴリズムの実行よりもはるかに多くの遅延が発生します。要点は、わからない場合は、効率を上げるために邪魔にならないようにすることです。ボトルネックです。

パフォーマンスのためにコードを最適化すると、一般的に最も直感的ではなく、賢い方法で物事を実行するため、コードが複雑になることがよくあります。より複雑なコードは、維持が難しく、他の開発者が拾うのが困難です(どちらも考慮する必要のあるコストです)。同時に、コンパイラーは一般的なケースの最適化に非常に優れています。一般的なケースを改善しようとすると、コンパイラーがパターンを認識できなくなり、コードの高速化に役立たなくなる可能性があります。これは、パフォーマンスを気にせずに、好きなように書くという意味ではないことに注意してください。明らかに非効率的なことをすべきではありません。

重要なのは、mightが物事をより良いものにする可能性のある小さなことについて心配しないことです。プロファイラーを使用して、1)現在の問題が問題であり、2)変更内容が改善されていることを確認します。

1
unholysampler

ほとんどのプログラマーは、ほとんどの場合、パフォーマンスコードがアプリケーション内の他のコードよりも多くの情報(コンテキスト、ハードウェアの知識、グローバルアーキテクチャに関する)に基づいたコードであるため、その直感を感じていると思います。ほとんどのコードは、特定の問題に対するいくつかの解決策を、いくつかの抽象化でモジュール化された方法で(関数のように)表現するだけです。つまり、コンテキストの知識を、そのカプセル化に入ったもの(関数のパラメーターのような)だけに制限します。

高いパフォーマンスを求めるために書くとき、アルゴリズムの最適化を修正した後、コンテキストについてのはるかに多くの知識を必要とする詳細に入ります。それは当然、タスクに十分に集中していると感じないプログラマーを圧倒するかもしれません。

1
Klaim

最適化されていないコードを実行するために必要な地球温暖化(数億のPCと大規模なデータセンター設備によってスケーリングされた余分なCPUサイクルから)と平凡なバッテリー寿命(ユーザーのモバイルデバイス上)のコストは、ほとんどの場合ほとんど表示されませんプログラマーのパフォーマンスまたはピアレビュー。

それは、無視された汚染の形に似た、経済的に負の外部性です。したがって、パフォーマンスについて考えることのコスト/メリット比は、実際には精神的に歪んでいます。

ハードウェア設計者は、最新のCPUに省電力機能とクロックスケーリング機能を追加するために懸命に取り組んでいます。利用可能なすべてのCPUクロックサイクルを消費するのではなく、ハードウェアがこれらの機能をより頻繁に利用できるようにするのはプログラマの責任です。

追加:昔、1台のコンピューターのコストは数百万でしたので、CPU時間の最適化は非常に重要でした。その後、コードの開発と保守のコストはコンピューターのコストよりも高くなり、最適化はプログラマーの生産性に比べてはるかに不利になりました。しかし、現在、別のコストがコンピュータのコストよりも高くなり、これらすべてのデータセンターに電力を供給して冷却するコストが、内部のすべてのプロセッサのコストよりも高くなっています。

1
hotpaw2

高度に最適化されたコードの一部は、高度に最適化されたコードが読みにくく、理解が難しい場合に対応できる、ほとんどの人々の頭脳を曲げる有名な部分もあります。

これが私が思う最も有名なものです。 Quake III Arenaから取得され、John Carmakによるものです。ただし、この関数にはいくつかの反復があり、元々は彼によって作成されたものではないと思います( Wikipediaは素晴らしいですか? )。

float Q_rsqrt( float number )
{
    long i;
    float x2, y;
    const float threehalfs = 1.5F;

    x2 = number * 0.5F;
    y  = number;
    i  = * ( long * ) &y;                       // evil floating point bit level hacking
    i  = 0x5f3759df - ( i >> 1 );               // what the fuck?
    y  = * ( float * ) &i;
    y  = y * ( threehalfs - ( x2 * y * y ) );   // 1st iteration
    //      y  = y * ( threehalfs - ( x2 * y * y ) );   // 2nd iteration, this can be removed

    return y;
}
0
fwgx

1つの問題は、開発者の時間が限られているということは、最適化しようとするものはすべて、他の問題に費やす時間を奪うということです。

Meyerのコードコンプリートで参照されているこれについては、かなり良い実験が行われています。さまざまな開発者グループに、速度、メモリ使用量、読みやすさ、堅牢性などを最適化するよう依頼しました。彼らのプロジェクトは、彼らが最適化するように求められたものは何でも高得点でしたが、他のすべての資質では低かったことがわかりました。

0
deworde

経験豊富なプログラマーがそれが本当であることを学んだからです。

無駄のない、平均的でパフォーマンスの問題がないコードを使用しました。

私たちは多くのコードに取り組んできましたが、パフォーマンスの問題に対処することは非常に複雑です。

すぐに頭に浮かぶ1つの例は、私の最後のプロジェクトに手動で分割された8,192のSQLテーブルが含まれていたことです。これは、パフォーマンスの問題のために必要でした。 1つのテーブルから選択する設定は、8,192のシャードから選択して維持するよりもはるかに簡単です。

0
Michael Durrant