読みやすいコードと読みにくい高速コード。いつ線を越えるか?


67

コードを書くときは、コードをできる限りクリーンで読みやすいものにするように常に心がけています。

ときどき、ラインを越えて、すっきりしたきれいなコードから少しugいコードに移行して、より速くする必要があるときがあります。

その線を横切るのはいつですか?


69
あなたはあなた自身の質問に答えた、あなたはラインを越える必要があるとき
ニブラー

6
また、ハードウェア上で「汚れたコード」が「クリーンコード」と同じくらい早く動作するようになるのは、6か月後です。ただし、Windowsのように船外に出ないでください。:)
Mateen Ulhaq

21
理解しにくいアルゴリズムと、理解しにくいコードには大きな違いがあります。実装する必要のあるアルゴリズムは複雑な場合があり、複雑なアイデアを表現しているという理由だけで、コードは必然的に混乱を招きます。ただし、コード自体が難しい点である場合は、コードを修正する必要があります。
タイラー

8
多くの場合、スマートコンパイラ/インタプリタは、クリーンで読み取り可能なコードを最適化できるため、「ugい」コードと同じパフォーマンスを実現できます。そのため、プロファイリングで特に断りのない限り、ほとんど言い訳はありません。
ダン・ディプロ

1
最近のコンパイラに関して言えば、見苦しいコードはクリーンなコードと同じである可能性が高いでしょう(本当に奇妙なことをしないと仮定すると)。特に.NETでは、変数の定義方法がパフォーマンスに影響を与えるC ++ / MFC時代とは異なります。保守可能なコードを作成します。一部のコードは単に複雑になりますが、thatいというわけではありません。
ダスティンデイビス

回答:


118

あなたがラインを横切るとき

  • あなたのコードは意図した使用に遅すぎると測定しました。
  • コードを改造する必要のない代替の改善を試みました。

実例は次のとおりです。私が実行している実験システムは、データの生成が非常に遅く、実行ごとに9時間以上かかり、CPUの40%しか使用していませんでした。コードをめちゃくちゃにするのではなく、すべての一時ファイルをメモリ内ファイルシステムに移動しました。8行の見苦しいコードを追加し、CPU使用率は98%を超えました。問題が解決しました; さは必要ありません。


2
また、リファレンス実装として、またハードウェアが変更されて高速でハッカーのあるコードが機能しなくなったときにフォールバックするために、元の低速でクリーンなコードを保持するようにします。
ポールR

4
@PaulRそのコードをどのように保持しますか?コメントの形で?それは間違っています、imo-コメントは古くなり、誰も読んでいないので、個人的にはコメントアウトされたコードを見たら私は通常それを削除します-これがソース管理の目的です。方法について説明するメソッドのコメントの方が良いです、imo。
エフゲニー

5
@Eugene:私は通常、ルーチンの元のバージョンに名前を付けfooて名前を変更します。foo_ref通常fooは、ソースファイルのすぐ上にあります。私のテストハーネスではfoofoo_ref検証と相対的なパフォーマンス測定のために呼び出します。
ポールR

5
@Paulを使用している場合、最適化されたバージョンがref関数よりも遅い場合、テストに失敗することをお勧めします。これは、高速化するために行った仮定が正しくない場合に発生する可能性があります。
user1852503 14年

58

それは間違った二分法です。あなたはコードを高速に行うことができますし、維持しやすいです。

あなたがそれをする方法は、特にできるだけ単純なデータ構造で、きれいに書くことです。

次に、時間の流れがどこにあるかを見つけて(それを実行することによって、前にではなく、書いた後で)、それらを1つずつ修正します。 (例を示します。)

追加:時間とメモリのトレードオフ、速度と保守性のトレードオフなど、トレードオフについて常に耳にしますか?そのような曲線が存在する可能性は十分にありますが、特定のプログラムが曲線上にある、またはその近くにあるとさえ仮定すべきではありません。

曲線上にあるプログラムは(特定の種類のプログラマーに提供することで)簡単に、非常に遅く、保守性を低くすることができます。そうすると、曲線に近づきません。そのようなプログラムには、より速く、より保守しやすいようにする十分な余地があります。

私の経験では、そこから多くのプログラムが始まります。


同意する。実際、クリーンではない高速なコードは、適切に変更できないため、最終的には遅くなります。
edA-qa mort-ora-y

22
私はそれが誤った二分法であることに同意しませ。IMOには、特にライブラリコードアプリケーションコードではなく)に、分割が非常に現実的なシナリオがあります。詳細については私の回答をご覧ください。
マークグラヴェル

1
マーク、「リンク」URLでコメントの回答にリンクできます。Programmers.stackexchange.com/questions/89620/…–

コードを高速化するために必要なすべての時間を見つけました。しかし、プロファイラーで実験して最適なソリューションを見つけた後(コードがい場合)、これはコードがtheいままである必要があるという意味ではありません。最初は明白に見えないかもしれないが、一度見つかったら通常はきれいにコーディングできる最良の解決策を見つけることです。したがって、私はそれが誤った二分法であり、おもちゃで楽しんだ後にあなたの部屋を片付けないだけの言い訳であると信じています。私はそれを吸い上げてあなたの部屋を片付けると言います。
マーティンヨーク

私はそれが誤った二分法であることに同意しません。私は多くのグラフィックス作業を行っているため、最も明らかな例はタイトなグラフィックスループです。これがどのくらいの頻度で行われるかはわかりませんが、Cで記述されたゲームエンジンがコアレンダリングにアセンブリを使用するのは一般的でした速度の最後の一滴ごとに絞り出すためにループします。また、PythonでプログラミングしているのにC ++で書かれたモジュールを使用している状況を考えさせられます。「読みにくい」は常に相対的です。速度を上げるために低レベルの言語にドロップすると、そのコードは他のコードよりも読みにくくなります。
11

31

私のOSSの存在では、パフォーマンスを目的とした多くのライブラリ作業を行います。これは、呼び出し側のデータ構造(つまり、ライブラリの外部)に深く結び付けられており、着信タイプに対する(設計上の)義務はありません。ここで、このパフォーマンスを実現する最良の方法はメタプログラミングです。これは(私は.NETの国にいるので)IL-emitを意味します。これはsomeい、いコードですが、非常に高速です。

このように、ライブラリコードはアプリケーションコードよりも「ugい」かもしれませんが、入力の制御が少ない(またはまったくない)ため、異なるメカニズムでいくつかのタスクを達成する必要があります。または先日私がそれを表明したように:

「狂気の崖をコーディングするので、あなたはする必要はありません

現在、アプリケーションコードはわずかに異なります。これは、「通常の」(健全な)開発者が通常、共同作業/専門家の時間の多くを投資する場所であるためです。それぞれの目標と期待は(IMO)わずかに異なります。

IMOは、高速保守が容易であることを示唆する上記の回答は、開発者がデータ構造をより細かく制御し、メタプログラミングなどのツールを使用していないアプリケーションコードを参照しています。とはいえ、メタプログラミングにはさまざまな方法があり、さまざまなレベルの狂気とさまざまなレベルのオーバーヘッドがあります。その分野でも、適切な抽象化レベルを選択する必要があります。しかし、積極的に、積極的に、本当に最速の方法で予期しないデータを処理することを本当に望んでいるとき。いかもしれません。それに対処する; p


4
コードがいからといって、それがメンテナンス不能である必要があるという意味ではありません。コメントとインデントは無料で、見苦しいコードは通常、管理可能なエンティティ(言語に応じてクラス、モジュール、パッケージ、機能)にカプセル化できます。コードは同じようにいかもしれませんが、少なくとも人々はこれから加えようとしている変更の影響を判断することができます。
-tdammers

6
実際、@ tdammersであり、可能な限りそうしようとしています。豚に口紅をつけるようなものです。
マークグラヴェル

1
、い構文といアルゴリズムを区別する必要があるかもしれません-いアルゴリズムが必要になることもありますが、い構文は一般的に許されないIMOです。
tdammers

4
@IMOのugい構文は、通常の言語レベルの下にあるいくつかの抽象化レベルの性質である場合、やむを得ないことです。
マークグラヴェル

1
@marc ...おもしろい。メタ/アブストラクトがtoいという私の最初の反応は、特定の言語/プレートフォームがメタコーディングに対して非伝導的であるという疑いであり、2つの基本的な法則ではありません。それは数学の進歩的なメタレベルの例であり、代数や具体的な数学よりも表現がほとんどない集合論で終わると信じていました。しかし、その後、セット表記はおそらくまったく異なる言語であり、その下のすべての抽象レベルには独自の言語があります。...-
explorest

26

コードのプロファイルを作成し、実際に大幅な速度低下を引き起こしていることを確認したとき。


3
そして「重要」とは何ですか?
ルーク

2
@ hotpaw2:それは賢明な答えです-それは開発者が少なくともいくらか競争力があることを前提としています。そうでない場合は、バブルソートよりも高速なものを使用するのが(通常)良い考えです。しかし、多くの場合、誰かが(ソートを維持するために)クイックソートをヒープソートに1%の差で交換しますが、同じ理由で6か月後に他の誰かがそれをスワップバックするのを見るだけです。

1
クリーンでないコードを作成する理由はありません。効率的なコードをクリーンで保守しにくい場合は、何か間違ったことをしていることになります。
edA-qa mort-ora-y

2
@SF。- より速くできる場合、顧客は常にそれが遅すぎると感じるでしょう。彼はコードの「クリーンレス」を気にしません。
ルーク

1
@Rook:顧客は(些細な)インターフェイスコードが遅すぎると感じるかもしれません。いくつかの非常に単純な心理的トリックは、実際にコードを加速することなくユーザーエクスペリエンスを向上させます-アクションをオンザフライで実行する代わりにバックグラウンドルーチンにボタンイベントを延期し、プログレスバーなどを表示し、アクティビティがバックグラウンドで実行されている間に些細な質問をします...これらが十分でない場合は、実際の最適化を検討できます。
SF。

13

クリーンコードは、高速実行コードと必ずしも排他的ではありません。通常、読みにくいコードは、実行速度が速いためではなく、記述が速いために作成されました。

あなたの変更が実際に何かを改善することを確実に知らないので、それをより速くしようとする試みで「汚い」コードを書くことは間違いなく賢明ではありません。Knuthが最高の結果を出しました:

「小さな効率を忘れて、時間の約97%を言うべきです。時期尚早な最適化はすべての悪の根源です。しかし、その重要な3%で機会を逃してはなりません。優れたプログラマーは、論理的に、彼は重要なコードを注意深く見るのが賢明でしょう。しかし、そのコードが特定された後にだけです。」

つまり、最初にコードをきれいに記述します。次に、結果のプログラムのプロファイルを作成し、そのセグメントが実際にパフォーマンスのボトルネックになっているかどうかを確認します。その場合、必要に応じてセクションを最適化し、最適化について説明するために、ドキュメントのコメント(おそらく元のコードを含む)を十分に含めるようにしてください。次に、結果をプロファイルして、実際に改善を行ったことを確認します。


10

質問は「コードを読むのが難しい」と言っているので、単純な答えは決してありません。読みにくいコードを書く言い訳はありません。どうして?2つの理由。

  1. 今夜帰宅途中にバスにぶつかったらどうなりますか?または(より楽観的に、より一般的に)このプロジェクトを中止し、他の何かに再割り当てしましたか?あなたがあなたのもつれたコードの混乱であなたが想像した小さな利益は、他の誰もそれを理解できないという事実によって完全に上回ってます。これがソフトウェアプロジェクトにもたらすリスクは、誇張するのが難しいです。かつて主要なPBXで働いていたメーカー(オフィスで働いている場合は、おそらく自分の机に電話があります)。彼らのプロジェクトマネージャーはある日、コア製品(標準のLinuxボックスを完全な機能を備えた電話交換機に変えたプロプライエタリなソフトウェア)が社内で「ブロブ」として知られていると話しました。誰もそれを理解していません。新しい機能を実装するたびに。彼らはコンパイルをヒットし、後ろに立ち、目を閉じて20に数え、指で覗いてそれが機能するかどうかを確認しました。もはや管理していないコア製品を必要とするビジネスはありませんが、それは恐ろしいほど一般的なシナリオです。
  2. しかし、最適化する必要があります!OK、それでこの質問に対する他の回答のすべての優れたアドバイスに従いました:あなたのコードはパフォーマンステストケースに失敗しています、あなたはそれを注意深くプロファイリングし、ボトルネックを特定し、解決策を考え出します...そしてそれはいくつかのビットいじりを伴います。罰金:今すぐ先に進み、最適化します。しかし、ここに秘密があります(そして、あなたはこのために座りたくなるかもしれません):ソースコードサイズの最適化と縮小は同じものではありません。コメント、空白、角括弧、意味のある変数名はすべて、読みやすくするための大きな助けになります。コンパイラはそれらを破棄するため、コストはまったくかかりません。(または、JavaScriptのような非コンパイル言語を記述している場合-そして、はい、JavaScriptを最適化する非常に正当な理由があります-それらはコンプレッサーで処理できます。)掲載ここに)最適化とは何の関係もありません:それは、彼らができるだけ少ない文字数に限り多くのコードを充填することによりどのように巧妙な示そうとプログラマです。それは賢くありません、それは愚かです。本当に賢いプログラマーとは、自分の考えを他の人に明確に伝えることができるプログラマーです。

2
答えが「決して」ではないことに同意できません。一部のアルゴリズムは、効率的に理解および/または実装することが本質的に非常に困難です。コメントの数に関係なく、コードを読むことは非常に難しいプロセスです。
レックスカー

4

使い捨てコードの場合。私はその文字通り意味:あなたは一回限りの計算やタスクを実行する、そして、そのような確信を持って知っているスクリプトを書くとき、あなたは「RMソース・ファイル」躊躇せずに、できることを再度、そのアクションを行う必要がありませんそして、あなたが選ぶことができますいルート。

さもなければ、それは間違った二分法です-より速くそれをugくする必要があると思うなら、あなたはそれを間違っています。(または、優れたコードについての原則を改訂する必要があります。gotoを使用することは、問題の適切な解決策である場合、実際には非常にエレガントです。ただし、めったにありません。)


5
使い捨てコードのようなものはありません。「動作するコード、書き換える時間がない」ために「スローコード」が本番環境に入るたびにペニーがあれば、私は億万長者になります。あなたが書くコードのすべての行は、あなたが今夜雷に打たれた後、別の有能なプログラマーが明日それを拾うことができるように書かれるべきです。それ以外の場合は書きません。
マーク・ウィテカー

私はそれが誤った二分法であることに同意しません。IMOには、特にライブラリコード(アプリケーションコードではなく)に、分割が非常に現実的なシナリオがあります。詳細については私の回答をご覧ください。
マークグラヴェル

@マーク、「もう一人の有能なプログラマー」が本当に有能であれば、スローアウェイコードも問題にならないはずです:)

@マーク-簡単。おそらく修正不可能な方法で、実稼働テストに失敗するように、スローアウェイコードを記述するだけです。
hotpaw2

@Mark、あなたの「スローアウェイコード」が本番に移行する場合、それはスローアウェイコードではありません。私が答えに時間を割いて、文字通り捨てられているコード、つまり、最初の使用後に削除するコードについて話していることを明確にしていることに注意してください。それ以外の場合、私はあなたの感情に同意し、私の答えで同じように言った。
maaku

3

市場でのパフォーマンス低下の推定コストが、問題のコードモジュールのコードメンテナンスの推定コストよりも大きい場合。

人々はいまだにツイストされた手書きのSSE / NEONなどを行っています。今年の人気のあるCPUチップで競合他社のソフトウェアを試してみようとするアセンブリ。


ビジネスの観点からすると、プログラマーは単に技術的な観点を超えて見る必要がある場合があります。
this.josh

3

適切なドキュメントとコメントを付けることで、読みにくいコードを理解しやすくすることを忘れないでください。

一般に、目的の機能を実行する読みやすいコードを作成した後にプロファイルを作成します。ボトルネックが発生すると、より複雑に見えるようにする必要がありますが、自分で説明することで修正できます。


0

私にはそれは安定性の割合です(コンクリートにセメントで固めた、粘土をオーブンで焼き、石に固め、永久インクで書いたような)。将来的にコードを変更する必要がある確率が高いほど、コードが不安定になるほど、生産性を維持するために、湿った粘土のように柔軟性が必要になります。また、読みやすさではなく柔軟性を強調しています。私にとって、コードの変更のしやすさは、コードの読みやすさよりも重要です。コードは読みやすく、悪夢は変更することができます。また、変更が悪夢である場合、実装の詳細を読み取って簡単に理解できるのはどのような用途ですか?単なるアカデミックな演習でない限り、通常、実動コードベースのコードを簡単に理解できるのは、必要に応じてより簡単にコードを変更できるようにすることです。変更が難しい場合は、その後、読みやすさの多くの利点が見えなくなります。可読性は一般に柔軟性のコンテキストでのみ有用であり、柔軟性は不安定性のコンテキストでのみ有用です。

読むのがどれだけ簡単か難しいかに関係なく、想像できるコードを維持するのが最も難しい場合でも、それを変更する理由がなく、それを使用するだけであれば、問題は生じません。そして、特にパフォーマンスが最も重要になる傾向がある低レベルのシステムコードでは、このような品質を実現することが可能です。私は今でも定期的に使用しているCコードを持っていますが、80年代後半から変わっていません。それ以来、変更する必要はありません。コードは、いじくり回す頃に書かれたfuいもので、私にはほとんど理解できません。しかし、それは今日でも適用可能であり、十分に活用するためにその実装を理解する必要はありません。

テストを徹底的に記述することは、安定性を向上させる1つの方法です。もう1つはデカップリングです。コードが他のものに依存していない場合、コードを変更する唯一の理由は、コード自体を変更する必要がある場合です。場合によっては、少量のコードの複製がデカップリングメカニズムとして機能して、安定性を劇的に向上させることができます。これにより、他のコードから完全に独立したコードが得られた場合、価値のあるトレードオフになります。これで、コードは外の世界への変更に対して無敵です。一方、10の異なる外部ライブラリに依存するコードには、将来変更される理由が10倍あります。

実際には、ライブラリをコードベースの不安定な部分から分離することもできます。サードパーティのライブラリの場合と同様に、ライブラリを個別にビルドすることもできます(同様に、少なくとも、チーム)。そのような組織は、人々が改ざんするのを防ぐことができます。

もう1つはミニマリズムです。コードが実行しようとする回数が少ないほど、コードが適切に実行できる可能性が高くなります。モノリシックデザインはほとんど永久に不安定です。機能が追加されると、完成度が低下するためです。

死に至るまで微調整された並列化されたSIMDコードのように、必然的に変更が困難になるコードを作成しようとする場合は常に、安定性が主な目標になります。コードを変更する必要がなく、将来それを維持する必要がない可能性を最大化することにより、コードを維持する難しさを打ち消します。これにより、コードの保守がどれほど困難であっても、保守コストがゼロになります。

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