多くの開発者がパフォーマンス、可読性、保守性が共存できないと考えるのはなぜですか?


34

この質問に答えながら、なぜ多くの開発者が優れた設計がパフォーマンスを考慮すべきではないと考えるのか疑問に思い始めました。なぜなら、そうすると読みやすさや保守性に影響するからです。

優れたデザインでは、執筆時にパフォーマンスも考慮され、優れたデザインの優秀な開発者は、可読性や保守性に悪影響を与えることなく効率的なプログラムを作成できると考えています。

極端な場合があることは承知していますが、なぜ多くの開発者が効率的なプログラム/設計が読みやすさや保守性の低下をもたらすと主張するのでしょうか?


9
大規模に推論することはほぼ不可能ですが、小さなコードの場合は非常に明白です。たとえば、クイックソートの読みやすいバージョンと効率的なバージョンを比較してください。
SKロジック

7
む。多くの開発者は、効率が維持不能につながると主張しているという声明を支持することから始めるべきです。
ピーターテイラー

2
SK-logic:私の意見では、これはすべてのスタック交換サイトの最良の部分の1つです。あなたにとって明白なことは、他の誰かにとって明白でないかもしれませんし、逆もまた同様です。:)共有は気にしています。
アンドレアスヨハンソン

2
@ジャスティン、いや。そのスレッドは、効率的なコードと保守可能なコードのどちらかを強制的に選択する状況を前提にしているように思えます。質問者は、その状況で自分がどれだけ頻繁に自分自身を見つけるかを言わず、回答者はその状況に頻繁にいると主張していないようです。
ピーターテイラー

2
質問の場合は-1。私がそれを読んだとき、私はこれが唯一の本当の答えを追い払うためのストローマンだと思った:「彼らはPythonを使わないから」。
インゴ

回答:


38

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

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


8
「そのような見解に対抗しようとすると、他の極端に陥るのは簡単です-少なくとも見た目は同じです-ただ、プロとのバランスを取るだけで反対の見解を抱いていると思う人にはいつも問題があります短所。プログラミングだけでなく、あらゆることに。
11

1
私は..私は怒っていること、これについて議論皆のように病気だと両極端を取る
トーマスBonini

いくつかの良い反応がありましたが、私はあなたの考え方がこの考え方の起源を詳述するのに最善の試みをしたと思います。関係者全員に感謝します!
ジャスティン

私の答えは...ほとんどの開発者は自分の仕事で悪いです
TheCatWhisperer

38

高性能コードを扱う開発者の側からの質問に答えるには、設計で考慮すべきことがいくつかあります。

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

正しく、美しく、速くしてください。その順序で。


私は経験則が好きです:「それを美しくして、速くしてください。その順序で」。私はそれを使い始めます。
マーティンヨーク

まさに。そして、可能な限り3番目のポイントでコードを分離します。異なるハードウェアに移動すると、異なるキャッシュサイズのプロセッサと同じくらい小さなものであっても、これらが変わる可能性があるためです。
キースB

@KeithB-あなたは良い点を指摘します、私はそれを私の答えに加えます。
ジョリスティマーマンズ

+1:「正しく取得し、美しく取得し、高速に取得します。この順序で。」90%に同意する非常に素晴らしい要約。時々、私はそれを美しく(そしてより理解しやすい)にしたら特定のバグを修正する(正しくする)ことしかできない。
ジョルジオ

「時期尚早に悲観しないでください」の+1。時期尚早な最適化を回避するためのアドバイスは、骨抜きのアルゴリズムのみを使用する許可ではありません。Javaを作成していcontainsて、多くのコレクションを呼び出す場合はHashSet、ではなくを使用しArrayListます。パフォーマンスは重要ではないかもしれませんが、そうしない理由はありません。優れたデザインとパフォーマンスの間の一致を活用します-何らかのコレクションを処理する場合は、すべてを1回のパスで実行しようとします。
トムアンダーソン

16

@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つの次元の改善は必ず他の次元のコストを招きます。

私の経験では、プログラムはチューニング、微調整、ハンマー、ワックス、一般的には「コードゴルフ」に変えられることによってのみ曲線に近づきます。ほとんどのプログラムには、すべての側面で改善の余地が十分にあります。 これが私が言っていることです。


個人的には、カーブにもう1つの端があると思います(右側に移動する限り(おそらくアルゴリズムを再考することを意味します))。
マーティンヨーク

2
「ほとんどのプログラムには、すべての側面で改善の余地が十分にあります」の+1。
スティーブン

5

正確な理由は、パフォーマンスの高いソフトウェアコンポーネントは一般に、他のソフトウェアコンポーネントよりもはるかに複雑だからです(他のすべてのものは同じです)。

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

とにかく、設計の複雑さは、開発者がそのような設計を迅速に学習して慣れる能力と直接的な相関関係があり、複雑なコンポーネントの機能をさらに変更すると、単体テストで捕捉できないバグが発生する可能性があります。複雑な設計には、より多くのファセットとテストケースがあり、100%のユニットテストカバレッジの目標をさらに夢のようなものにすることを検討します。

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

ただし、適切に設計されたコンポーネントを想定すると、パフォーマンスのために調整された同様に適切に設計されたコンポーネントよりも複雑ではありません(他のすべての条件は同じです)。


3

それらが共存できないほどではありません。問題は、最初の反復では誰のコードも遅く、読みにくく、維持できないことです。残りの時間は、最も重要なものの改善に費やされます。それがパフォーマンスである場合、それのために行きます。ひどくひどいコードを書くな パフォーマンスと清潔さは基本的に無相関だと思います。パフォーマンスコードはcodeいコードを引き起こしません。ただし、コードのすべてのビットを高速にチューニングすることに時間を費やしている場合、時間を費やしていないことを推測してください。コードをクリーンで保守可能にします。


2
    |
    P
    E
    R
    F
    O *
    R * 
    M *
    A *
    N *
    C * * * * *
    E
    |
    O-可読性-

ご覧のように...

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

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


1

私の意見では、パフォーマンスは実際の問題(または要件など)である場合に考慮すべきです。そうしないと、マイクロオプティマイゼーションが発生する傾向があり、あちこちで数マイクロ秒を節約するだけでコードがより難読化される可能性があります。代わりに、必要に応じてシステムの実際のボトルネックに焦点を合わせ、そこでパフォーマンスに重点を置く必要があります。


1

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

大事なのは、ほとんどのユースケースで、目を見張るような高速コードが必要ないことです。多くの場合、IOまたはユーザーの操作により、アルゴリズムの実行が引き起こすよりもはるかに多くの遅延が生じます。ポイントは、ボトルネックであることがわからない場合は、何かを効率的にするために邪魔にならないことです。

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

ポイントは、物事を良くするかもしれないささいなことを心配しないことです。プロファイラーを使用して、1)現在の問題が問題であり、2)変更したものが改善されていることを確認します。


1

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

アルゴリズムの最適化を修正した後、パフォーマンスを向上させるために記述する場合、コンテキストに関するはるかに多くの知識を必要とする詳細に到達します。それは、タスクに十分に集中していると感じないプログラマを自然に圧倒するかもしれません。


1

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

無視された汚染の形に似た、経済的な負の外部性です。そのため、パフォーマンスについて考えることの費用対効果の比率は、現実から精神的に歪んでいます。

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

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


PCが地球温暖化の一因であるかどうかは別として、たとえそれが現実であっても:エネルギー効率が高いほどエネルギー需要が少なくなるというのは誤りです。PCが市場に登場した初日からわかるように、ほぼ正反対です。それ以前は、数百またはthousendsメインフレーム(それぞれに仮想的に独自の発電所が装備されていました)は、今日よりもはるかに少ないエネルギーを使用しました。しかし、コンピューティングの総エネルギー需要は以前よりも高くなっています。
インゴ

1

3つすべてを達成するのは難しいと思います。2つは実現可能だと思います。たとえば、場合によっては効率と読みやすさを実現することは可能だと思いますが、マイクロチューニングされたコードでは保守性が難しい場合があります。地球上で最も効率的なコードは、ほとんどの人にとって明らかであるように、一般的に保守性と可読性の両方を欠いいます。業界で使用されているエッジアルゴリズム。わずか2か月前に公開された40ページの数学論文と、1つの非常に複雑なデータ構造用の12のライブラリに相当するコード。

ミクロ効率

人気のある意見に反するかもしれないと思うことの1つは、最もスマートなアルゴリズムコードは、最も微調整された単純なアルゴリズムよりも維持が難しいことが多いということです。スケーラビリティの改善により、マイクロチューニングされたコード(例:キャッシュフレンドリーアクセスパターン、マルチスレッド、SIMDなど)よりも大きな成果が得られるというこの考えは、少なくとも非常に複雑な業界で働いていて、私が挑戦したいことですデータ構造とアルゴリズム(ビジュアルFX業界)、特にメッシュ処理などの領域では、ビッグになる可能性がありますが、新しいアルゴリズムとデータ構造を導入するとき、彼らはブランドとして以来誰も聞いたことがないため、費用は非常に高価です新しい。さらに、私は

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

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

関数型言語

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

コードを一度に読みやすく、保守しやすく、効率的にするのは本当に難しいです。通常、保守性のために読みやすさを損なうか、効率のために保守性を損なうなど、2つではないにしても3つのうちの1つで少し妥協する必要があります。通常、他の2つの多くを探し求めると、保守性が低下します。

読みやすさと保守性

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

純粋に関数型の言語は、これにほぼ無敵に近い(無敵に近くさえないが、ほとんどよりも比較的近い)。そして、それは、彼らが人間の思考に直観的にマッピングしないからです。彼らは読めません。彼らは思考パターンを私たちに押し付け、可能な限り最小限の知識を使用して、副作用を引き起こすことなく、できるだけ少ない特別なケースで問題を解決する必要があります。それらは非常に直交しているため、驚くことなくコードを頻繁に変更したり変更したりすることができるので、すべてを書き直すことなく、設計全体について考えを変更するまで、図面ボードで設計を再考する必要があります。それよりも保守が容易になるとは思われません...しかし、コードはまだ非常に読みにくく、


1
「マイクロ・効率性」「O(1)メモリアクセスのようなものはありません」というようなの一種である
Caleth

0

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

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


明らかにもっと時間を割くことができますが、最終的には開発者がプロ​​グラミングemacsから時間をかけて子供たちへの愛を表現する理由を疑問視し始めます。その時点で、あなたは基本的にビッグバン理論のシェルドンです
deworde

0

経験豊富なプログラマーがそれが真実だと学んだからです。

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

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

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


0

また、高度に最適化されたコードを読んで理解するのが難しい場合をサポートする、ほとんどの人々の脳を曲げる有名な高度に最適化されたコードの一部があります。

ここが最も有名だと思います。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;
}
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.