最適化について少し調べた後、ゲームを最適化するのが早すぎることは、広く認識されている罪のように思えます(文字通りどこでも)。
私はこれを本当に理解していませんが、パフォーマンスを念頭に置いて初めて開発するのではなく、ゲームのコア構造の一部を最後に変更することは非常に難しいことではありませんか?
ゲームが終了するまで待つことで最適化が必要かどうかがわかりますが、とにかくそれを行うべきではありません。プレーヤー。
最適化が早すぎるのはなぜ悪い考えなのか、誰かに説明してもらえますか?
最適化について少し調べた後、ゲームを最適化するのが早すぎることは、広く認識されている罪のように思えます(文字通りどこでも)。
私はこれを本当に理解していませんが、パフォーマンスを念頭に置いて初めて開発するのではなく、ゲームのコア構造の一部を最後に変更することは非常に難しいことではありませんか?
ゲームが終了するまで待つことで最適化が必要かどうかがわかりますが、とにかくそれを行うべきではありません。プレーヤー。
最適化が早すぎるのはなぜ悪い考えなのか、誰かに説明してもらえますか?
回答:
前文:
コメントではいくつかの異議が提起されており、それらは主に「時期尚早な最適化」と言ったときの意味の誤解に起因していると思うので、それについて少し説明を加えたかった。
「時期尚早に最適化しない」とは、「Knuthが最後までクリーンアップすることは許可されていないため、コードの記述が悪いことを意味する」という意味ではありません。
これは、「プログラムのどの部分が実際に高速化する必要があるかがわかるまで、最適化のために時間と読みやすさを犠牲にしないでください」という意味です。典型的なプログラムはほとんどの時間をいくつかのボトルネックに費やしているため、「すべて」の最適化に投資しても、ボトルネックのコードだけに同じ投資を集中させるのと同じ速度にならない場合があります。
これは、疑わしい場合、次のことを行う必要があることを意味します。
書きやすく、わかりやすく、初心者にとっても簡単に変更できるコードを好む
さらに最適化が必要かどうかを確認します(通常は実行中のプログラムをプロファイリングしますが、以下のコメントでは数学的な分析を行っていることに注意してください。
時期尚早な最適化ではありません:
適切なモジュール/責任/インターフェース/通信システムを考慮した方法で選択することで、ニーズに合わせてコードを構成するためのアーキテクチャ上の決定。
余分な時間をかけたり、コードを読みにくくしたりしないシンプルな効率。厳密な型指定の使用は、効率的であり、意図をより明確にすることができます。繰り返し検索する代わりに参照をキャッシュすることも、別の例です(複雑なキャッシュ無効化ロジックを必要としない限り、単純な方法を最初にプロファイルするまで、それを書くのを保留することができます)。
ジョブに適切なアルゴリズムを使用します。A *は、経路探索グラフを徹底的に検索するよりも最適で複雑です。また、業界標準です。テーマを繰り返し、このような実証済みのメソッドに固執することで、単純なことをするが既知のベストプラクティスに反する場合よりも実際にコードを理解しやすくすることができます。以前のプロジェクトでゲーム機能Xを一方向に実装するボトルネックに遭遇した経験がある場合は、このプロジェクトで同じボトルネックにぶつかる必要はありません。それが本当かどうかを知る必要があります。ゲーム。
これらのタイプの最適化はすべて十分に正当化されており、一般的に「時期尚早」というラベルは付けられません(8x8チェスボードマップの最先端の経路探索を実装するウサギの穴を下っていない限り...)
そこで、ゲームでこのポリシーが特に役立つと思われる理由について説明します。
特にgamedevでは、反復速度が最も貴重なものです。最終的なゲームに最終的に付属するよりもはるかに多くのアイデアを実装して再実装し、「楽しみを見つけよう」とすることがよくあります。
メカニックを簡単かつ多分少し素朴な方法でプロトタイプ化し、翌日にプレイテストできる場合、最初に最適なバージョンを作成するのに1週間を費やした場合よりもはるかに優れた位置にいます。特にそれが吸うことが判明し、その機能を捨てることになる場合。早期にテストできるように簡単な方法で行うと、保持していないコードを最適化する無駄な作業を大幅に節約できます。
また、最適化されていないコードは、1つの正確なことを最適に実行するために微調整されたコードよりも、一般的に変更およびさまざまなバリアントの変更が簡単です。これは、壊れやすく、バグを導入したり、速度を低下させずに変更するのが難しく、困難になる傾向があります。そのため、コードをシンプルで変更しやすい状態に保つことは、ほとんどの開発全体で実行時の非効率性の価値があります(通常、ターゲット仕様以上のマシンで開発しているため、オーバーヘッドを吸収し、最初にターゲットエクスペリエンスを得ることに集中できます)。 '機能から必要なものをロックダウンし、低速であることがわかっている部分を最適化できます。
はい、開発の後半でプロジェクトの一部をリファクタリングしてスロースポットを最適化するのは難しい場合があります。しかし、先月行った最適化はそれ以降のゲームの方向性と互換性がないため、または機能とコンテンツが増えたら実際のボトルネックではないことが判明したため、開発全体でリファクタリングを繰り返していますに。
ゲームは奇妙で実験的です。ゲームプロジェクトとその技術ニーズがどのように進化し、パフォーマンスが最も厳しくなるのかを予測するのは困難です。実際には、しばしば間違ったものを心配することになります。ここでパフォーマンスの質問を検索すると、開発者が紙の資料にまったく気を取られないという共通のテーマが現れることがわかります。
劇的な例を挙げると、ゲームがGPUバウンド(珍しいことではない)である場合、CPU作業のハイパー最適化とスレッド化に費やしたすべての時間は、具体的なメリットをまったくもたらさない可能性があります。これらの開発時間はすべて、プレイヤーエクスペリエンスを向上させるために、代わりにゲームプレイ機能の実装と洗練に費やしていた可能性があります。
全体として、ゲームでの作業に費やす時間のほとんどは、ボトルネックになるコードに費やされることはありません。特に、既存のエンジンで作業している場合、レンダリングシステムと物理システムの非常に高価な内部ループは、ほとんど手に負えません。その時点で、ゲームプレイスクリプトでのあなたの仕事は、基本的にエンジンの邪魔にならないようにすることです-そこにレンチを投げない限り、おそらく最初のビルドでかなり大丈夫です。
そのため、少しのコードの衛生と予算(たとえば、簡単に再利用できる場合は繰り返し検索/構築しない、経路探索/物理クエリまたはGPUリードバックを控えめにするなど)を除いて、終わらない習慣を作ります。 -本当の問題がどこにあるかを知る前に最適化することは生産性に良いことがわかります-間違ったものを最適化する時間を無駄にせず、コードをよりシンプルで全体的に微調整しやすくします。
注:この回答は、DMGregoryの回答に対するコメントとして始まったため、彼が述べた非常に良い点を再現していません。
「パフォーマンスを念頭に置いて初めて開発するのではなく、最後にゲームのコア構造の一部を変更することは非常に難しいことではないでしょうか?」
これは、私にとって、質問の核心です。
オリジナルのデザインを作成するときは、効率を高めるためにトップレベルで設計する必要があります。これは最適化ではなく、構造に関するものです。
例:
川を渡るシステムを作成する必要があります。明らかなデザインは橋またはフェリーです。どちらを選びますか?
もちろん、答えは交差点のサイズと交通量に依存します。これは最適化ではなく、問題に適した設計から始めることです。
設計の選択肢が提示されたら、何をしたいかに最適なものを選択します。
したがって、トラフィック量がかなり少ないと言えば、2つのターミナルを構築し、フェリーを購入してトラフィックを処理することにします。すてきなシンプルな実装
残念ながら、一度実行すると、予想よりも多くのトラフィックが発生していることがわかります。フェリーを最適化する必要があります!(それが機能し、現在橋を建設するのは良い計画ではないからです)
オプション:
これは、元の設計を可能な限りモジュール化することを試みるべき場所です。
上記のすべてが可能な最適化であり、3つすべてを行うこともできます。
しかし、大きな構造変更をせずにこれらの変更をどのように行うのでしょうか?
明確に定義されたインターフェースを備えたモジュール設計がある場合、これらの変更を簡単に実装できるはずです。
コードが密結合されていない場合、モジュールの変更は周囲の構造に影響しません。
フェリーの追加を見てみましょう。
「悪い」プログラムは、単一のフェリーのアイデアを中心に構築され、ドック州とフェリー州を持ち、すべてをまとめて状態を共有します。これは、システムに追加のフェリーを追加できるように変更するのが難しいでしょう。
より良い設計は、ドックとフェリーを別々のエンティティとして持つことです。それらの間には密接な結びつきはありませんが、フェリーが到着し、乗客を降ろし、新しい乗客に乗り、出発できるインターフェースがあります。ドックとフェリーはこのインターフェースのみを共有します。これにより、この場合は2番目のフェリーを追加することにより、システムを簡単に変更できます。ドックは、実際にフェリーが何であるかを気にしません。心配しているのは、何か(何でも)がそのインターフェースを使用していることだけです。
tl; dr:
その後、最適化する必要があるときにコードベース全体を再構築することなく、各モジュール内のメカニズムを変更できます。
IRiverCrossing
トラフィック量がフェリーのセットアップには大きすぎることが明らかになったら、ブリッジを実装してIRiverCrossing
ドロップインします。もちろん、トリックはフェリーの外部APIを減らすことです。共通の機能へのブリッジで、同じインターフェースで表すことができます。
「早期に最適化しない」とは、「可能な限り最悪の方法を選択する」という意味ではありません。まだプロトタイプを作成している場合を除き、パフォーマンスへの影響を考慮する必要があります。重要なのは、開発のその時点で、柔軟性、信頼性など、他のより重要なものを損なうことではありません。シンプルで安全な最適化を選びます-制限するものと自由にするものを選択します。コストを追跡します。強い型付けを使用する必要がありますか?ほとんどのゲームは正常に動作しました。gamemplayの柔軟性の興味深い用途を見つけた場合、それを削除するにはどれくらいの費用がかかりますか?
最適化されたコード、特に「スマート」コードを変更することははるかに困難です。常に良いものと悪いものを選択する選択です(たとえば、CPU時間をメモリ使用量と交換する場合があります)。その選択をするとき、あなたはすべての影響に注意する必要があります-それらは悲惨かもしれませんが、彼らはまた役立つことができます。
たとえば、司令官キーン、ウルフェンシュタイン、ドゥームはそれぞれ最適化されたレンダリングエンジンの上に構築されました。それぞれにゲームをそもそも存在させる「トリック」がありました(時間の経過とともにさらに最適化が行われていましたが、ここでは重要ではありません)。大丈夫です。ゲームの核となる部分を大幅に最適化してもかまいません。それがゲームを可能にします。特に、この特定の最適化された機能を使用すると、あまり探索されていないゲームデザインを検討できる新しい領域を探索する場合に役立ちます。最適化によってもたらされる制限により、興味深いゲームプレイも提供される場合があります(たとえば、RTSゲームのユニット数の制限は、パフォーマンスを改善する方法として開始された可能性がありますが、ゲームプレイ効果もあります)。
ただし、これらの各例では、最適化がなければゲームは存在し得ないことに注意してください。彼らは「完全に最適化された」エンジンから始めませんでした-彼らは必要性から始めて、彼らの方法を上げました。彼らは新しい技術を開発し、それらを使って楽しいゲームを作りました。また、エンジンのトリックはコードベースの可能な限り小さな部分に限定されていました-より重い最適化は、ゲームプレイがほとんど終了したとき、または興味深い新機能が出現するときにのみ導入されました。
次に、作成するゲームを検討します。そのゲームを成功させる、または破壊する技術的な奇跡は本当にありますか?無限の世界で開かれた世界のゲームを想像しているかもしれません。それは本当にゲームの中心部分ですか?それなしではゲームは機能しませんか?たぶん、現実的な地質学などで地形を制限なく変形できるゲームを考えているのかもしれません。より小さなスコープで動作させることができますか?3Dではなく2Dで動作しますか?できるだけ早く楽しいものを手に入れましょう-最適化のために既存のコードの膨大な部分を修正する必要がある場合でも、それは価値があるかもしれません。物事を大きくしてもゲームが本当に良くなるわけではないことに気付くかもしれません。
多くの最適化が行われた最近のゲームの例として、私はFactorioを指摘します。ゲームの重要な部分の1つはベルトです。何千ものベルトがあり、工場のあちこちにたくさんの個別の材料があります。ゲームは非常に最適化されたベルトエンジンで始まりましたか?番号!実際、元のベルトのデザインは最適化するのがほとんど不可能でした-ベルト上のアイテムの物理的なシミュレーションを行い、面白いことができました(これが「新しい」ゲームプレイを得る方法です-驚きのゲームプレイデザイナー)、しかし、ベルト上のすべてのアイテムをシミュレートする必要がありました。数千のベルトを使用すると、数万の物理的にシミュレートされたアイテムを取得できます。それを削除してベルトに機能させるだけでも、関連するCPU時間を95〜99%削減できます。メモリの局所性などを考慮しなくても。ただし、実際にこれらの制限に達した場合にのみ有効です。
ベルトを最適化するために、ベルトに関係するほとんどすべてを作り直す必要がありました。また、ベルトは最適化する必要がありました。大規模な工場には多くのベルトが必要であり、大きな工場はゲームの魅力の1つです。結局のところ、もしあなたが大きな工場を持つことができないなら、なぜ無限の世界があるのでしょうか?おもしろいことを尋ねてください-初期のバージョンはそうではありませんでした :) Javaがうまくいかないことに気付いたとき、ゲームは何度も何度も作り直され、現在の場所を取得しました。このようなゲームで、C ++に切り替えました。そして、それはFactorioにとってはうまく機能しました(まだ良いことではありましたが、始めから最適化されていませんでした-特に、これは趣味のプロジェクトであったためです。
しかし、事は、そこにあります限定された範囲の工場でできることはたくさんあります。多くのゲームがそれを示しています。制限は、自由よりも楽しみにさらに力を与えることができます。「マップ」が無限であれば、Spacechemはもっと楽しくなるでしょうか?高度に最適化された「ベルト」から始めた場合、ほとんどの場合、そのようにする必要があります。また、他のデザインの方向性を探ることができませんでした(物理学でシミュレートされたコンベヤベルトで何が面白いかを見るなど)。潜在的なデザインスペースを制限しています。まだ完成していないゲームはあまり見ないので、そのように思えないかもしれませんが、難しいのは楽しみを正しくすることです-あなたが見るすべての楽しいゲームには、おそらくそこに到達できずに捨てられた(またはさらに悪いことに、恐ろしい混乱としてリリースされました。最適化がそれを行うのに役立つ場合-先に進みます。そうでない場合... 時期尚早です。一部のゲームプレイメカニックがうまく機能すると思うが、本当に輝くために最適化が必要な場合-先に進みます。面白いメカニズムがない場合は、それらを最適化しないでください。最初に楽しみを見つけてください-ほとんどの最適化はそれを助けず、しばしば致命的です。
最後に、素晴らしい、楽しいゲームがあります。今すぐ最適化する意味がありますか?ハ!それはあなたが考えるかもしれないほど明確ではありません。楽しいものはありますか代わりにできますか?あなたの時間がまだ限られていることを忘れないでください。すべてが努力を要し、あなたはそれが最も重要な場所にその努力を集中させたいと思う。はい、「無料ゲーム」や「オープンソース」ゲームを作成している場合でも可能です。ゲームのプレイ方法をご覧ください。パフォーマンスがボトルネックになる場所に注意してください。それらの場所を最適化すると、もっと楽しくなります(これまで以上に大きく、かつてもつれた工場を建設するような)。それにより、より多くのプレイヤーを引き付けることができますか(例えば、より弱いコンピューターや異なるプラットフォームで)。あなたは常に優先順位を付ける必要があります-比率を生み出す努力を探してください。ゲームをプレイし、他の人がゲームをプレイするのを見るだけで、ぶら下がっている果物がたくさん見つかるでしょう。しかし、重要な部分に注意してください-そこに到達するには、ゲームが必要です。それに注目してください。
桜のように、最適化が終わることはないと考えてください。完了して他のタスクに進むのは、小さなチェックマークの付いたタスクではありません。常に「もう1つの最適化」を行うことができ、開発の大部分は優先順位を理解することです。最適化のために最適化を行うのではなく、特定の目標を達成するために最適化を行います(たとえば、「333 MHz Pentiumで一度に画面に200ユニット」は大きな目標です)。ターミナルゴールの前提条件ではないかもしれない中間ゴールに集中しすぎているからといって、ターミナルゴールを見失うことはありません。
答えの多くは「最適化」のパフォーマンスの側面に多く焦点を当てているようですが、私自身は最適化と、より抽象的なレベルで早すぎる最適化の試練を見てみたいです。
ポリオミノの助けを借りて自分の視点を詳しく説明しようとするので、私をユーモアにしてください。
作業しているフレームワークまたはエンジンによって設定されたいくつかの固定境界があるとします。
次に、ゲームの最初のレイヤー/モジュールの作成に進みます。
次に、2番目のレイヤー/モジュールを構築します。
この時点で、2つのモジュール間に使用可能なスペースがあることに気付く場合があり、割り当てられた境界を最大限に活用するためにそれを最適化しようとするかもしれません。
完璧です。現在、アプリケーションは利用可能なリソースを完全に利用していますが、アプリケーションの方が優れていますか?
アプリケーションの3番目のレイヤー/モジュールの構築に進み、突然、3番目のレイヤー/モジュールが機能していないことを認識します(おそらく、最初の計画では予測できなかったものでも)。
代替を検索し、それを見つけます。最後に、新しく選択した3番目のモジュールと互換性がないため、アプリケーションの2番目のモジュールを変更する必要があります。(ありがたいことに、最初のモジュールと多少互換性があるため、すべてを最初から書き直す必要はありません。)
それで私たちはすべてをまとめました...
うーん、何が起こったのかわかりますか?
最適化の時期を早めることで、最適化の対象が最終的なものではないため、実際には効率的に事態を悪化させています。
そして、後でいくつかの追加モジュールや余分な情報を追加したい場合、この時点でそれらを実行する能力がなくなる可能性があります。
そして、私たちのシステムの最低レベルを調整することは、他のすべてのレイヤーの下に埋められているため、もはや実行不可能です。
ただし、すぐに最適化するという衝動で待つことを選択した場合、次のような結果になります。
この時点で最適化を実行すると、満足のいくものが得られます。
少なくともあなたの何人かは、私がこれを作るのを楽しんでいたのと同じくらいこれを読んで楽しんでいたことを願っています:)そしてあなたが今あなたが主題をよりよく把握しているように感じたら-さらに良いことです。
お金。
それに帰着します。お金。時は金なり*なので、より多くのお金を生み出すことが保証されていないアクティビティに費やす時間が長いほど(つまり、これらのアクティビティを投資と見なすことはできません)、無駄になるリスクが大きくなり、お金が減りますゲーム。
最適化が早すぎる場合の潜在的な副作用と、避けるべき理由を次に示します。
*その他の回答は、「時間」の部分を十分に強調しています
サイドノートとして、 一般的に、経験は何が早期最適化であり何が早期最適化であり、何がビジネスにとって価値があり、何がそうでないかを決定するのに役立ちます。
たとえば、ゲームAで作業し、プロジェクトの終わりまでに機能XYZがゲームループで特に重いことに気付いた場合、最終的にまったく同じ機能を持つゲームBで作業を開始し、機能を書き直し、最初から最適化することは実際には時期尚早な最適化ではありません。何もしないとボトルネックになることがわかっています。
最適化とは、定義上、ソリューションの効率を、その有効性が失われるまで高めるプロセスです。このプロセスは、ソリューションのスペースの削減を意味します。
ソフトウェア開発の初期段階では、まだ「隠れた要件」が存在する可能性があります。ソリューションのスペースを減らしすぎると、ポップアップしたときに「隠れた要件」を満たすことができない状況になる可能性があります開発の後の段階で、この方法で不安定性と予想外の動作の可能性を追加して、アーキテクチャを変更する必要があります。
そのアイデアは、ソリューション全体を機能させることであり、その場合にのみ、すべての要件が修正および実装されたときにコードを強化します。コーディング中に一度に簡単に実装できた最適化の多くは、後の要件とのやり取りのために実行不可能になります。
ソリューションの実際の空間は、非常に複雑なシステムの完全な知識を持つことができないため、最初に予想されるものよりも常に大きくなります。
最初に機能させます。次に、ロープを締めます。
要するに、後で物事を変更したい場合、初期の最適化は非常に頻繁に無駄な労力となり、その後、簡単に変更可能なコード構造をはるかに低いレベルに最適化してしまい、それを高い値に戻す必要がありますレベルのアプローチ、および結果の最適化をもう一度行います。
これは、最適化のような「有用な作業を行った」ことから喜びを得ることに集中したい初心者開発者にとってよくある間違いであり、それが適切なタイミングであるかどうかを考えません。ゲームのような大きなプロジェクトのプログラミングの経験を積むと、既存のコードベースの最適化がいつ必要になるのか、またすぐに必要になるのかがわかります。この間違いをすることを恐れないでください、あなたはそれから学ぶことによってのみ利益を得るでしょう。
一般的に、最適化するのは、現時点で開発ビルドを実際に使用できない場合のみです。1秒間に何百万ものオブジェクトを60回作成および削除する場合のように。
したがって、学習経験の観点から、数回早期に最適化するのが良いと思います:p
最適化は、コンピューターをコード上で最適に動作させることに焦点を当てていますが、開発では、プログラマーをコード上で最適に動作させる必要があります。
最適化は洞察を引き出しません。これにより、コンピューターの作業が少なくなり、プログラマーの作業が増えます。
もちろん、車の設計など、さまざまなカテゴリがあります。最初のシャーシを構築する前にエンジンを最適化しても何も問題はありませんが、エンジンの形状がわからないうちにシャーシを最適化すると、時間の無駄になります。モジュール性と仕様は、製品が全体として組み立てられる前であっても、最適化作業に実行可能な多くの場所を取得できます。
コードを初期段階で最適化すべきではないというこれらすべてのステートメントには強く反対します。それはあなたがどの段階にあるかによります。これがMVP-プロトタイプであり、1〜2週間かかるゲームを見ようとしている場合は、すでに書いたすべてを書き直す準備ができていることを確認します。最適化は重要ではありません。ただし、リリースが予定されているゲームで既に作業している場合は、コードを最適化する必要があります。新しい機能や追加できるものについて人々が言う話は、誤解です。最適化されたコードではなく、設計が不十分なコードアーキテクチャです。優れたコードデザインがあれば、新しい要素を追加するために何かを書き換える必要はありません。
たとえば、A *パスファインディングアルゴリズムがある場合、できる限り最適な方法で記述した方が良いと思いませんか?後でコードの半分を変更するのではなく、別のメソッド呼び出しとコールバックが必要になったため、アルゴリズムを変更する必要があったためですか?また、最初から最適化されたコードを既にお持ちの場合、多くの時間を節約できます。膨大な数の新しいスクリプトを作成してすべての接続をすぐに作成する代わりに、オブジェクトとその相互作用の関係を自分で描くことができるため、不明瞭なスパゲッティコードにつながります。
最後に追加したいのは、ゲームをもう作成したくない場合でも、次のゲームに使用できる最適化されたA *アルゴリズムがあることです。再利用性。たとえば、多くのゲームには、インベントリ、経路探索、手順生成、NPC間の相互作用、戦闘システム、AI、インターフェイス相互作用、入力管理があります。ここで、「これらの要素をゼロから何回書き直したか」を自問する必要があります。ゲームの70〜80%です。本当にそれらを常に書き換える必要がありますか?そして、それらが最適化されていない場合はどうなりますか?