ホームページ  >  記事  >  バックエンド開発  >  Go: 簡単な最適化メモ

Go: 簡単な最適化メモ

Go语言进阶学习
Go语言进阶学习転載
2023-07-21 13:04:42938ブラウズ
#クラウド コンピューティングの時代では、サーバーレス アプリケーション (開発者がサーバーを管理せずにアプリケーションを構築および実行できるクラウドネイティブ開発モデル) を作成することがよくあります。私たちのプロジェクトがこのモデルを採用すると、インフラストラクチャのメンテナンス予算がリストの最上位になります。サービスの負荷が低い場合は、実質無料です。しかし、何か問題が発生した場合、多額の費用を支払うことになります。お金のことになると、必ず何らかの反応をするはずです。

VPS で複数のサービス アプリケーションを実行しているが、そのうちの 1 つがすべてのリソースを占有し、ssh 経由でサーバーにアクセスできなくなる場合があります。 Kubernetes クラスターの使用に移行し、すべてのアプリケーションに制限を設定します。その後、OOM キラーがメモリの「リーク」問題を修正したため、いくつかのアプリケーションが再起動されたことがわかりました。

もちろん、OOM は常にリークの問題であるわけではなく、リソースのオーバーランになる可能性もあります。リークの問題はプログラムエラーによって引き起こされる可能性が高く、今日のテーマは、この状況を可能な限り回避する方法です。

リソースの過剰な消費はウォレットに悪影響を与える可能性があるため、すぐに行動を起こす必要があります。

最適化を時期尚早に行わないでください

次に、最適化について話しましょう。時期尚早に最適化をすべきではない理由を理解していただければ幸いです。

  • まず、最適化は無駄な作業かもしれません。なぜなら、最初にアプリケーション全体を調査する必要があり、コードがボトルネックになる可能性は低いからです。必要なのは迅速な結果、MVP(Minimum Viable Product、実行可能最小限の製品)であり、その上で問題点を考えます。
  • #第 2 に、最適化には基礎が必要です。つまり、あらゆる最適化はベンチマークに基づく必要があり、それがどれだけの利益をもたらすかを証明する必要があります。
  • #第三に、最適化により複雑さが生じる可能性があります。知っておく必要があるのは、ほとんどの最適化によりコードが読みにくくなるということです。このバランスを取る必要があります。

最適化の提案

Go の標準的なエンティティ分類に従って、いくつかの実用的な提案を提供します。

#1. 配列とスライス

事前にスライスにメモリを割り当てます

3 番目のパラメータを使用してみてください: make([]T, 0, len)<span style="font-size: 15px;"></span>

要素の正確な数がわからず、スライスの有効期間が短い場合は、より大きな要素を割り当てることができます。スライスが大きくならないようにサイズを変更します。

copy を使用することを忘れないでください

2 つ以上のスライスを結合する場合など、コピーするときは append を使用しないようにしてください。

正しく反復する

多くの要素または大きな要素を含むスライスの場合、for を使用して単一の要素を取得します。こうすることで、不必要な重複が回避されます。

複数のスライス

受信スライスに対して何らかの操作が実行され、変更された結果が返された場合、それを返すことができます。これにより、新たなメモリ割り当てが回避されます。

スライスの未使用部分を残さないでください

スライスから小さな部分を切り取ってそれのみを使用する必要がある場合、スライスの主要部分も保持されます。正しいアプローチは、この小さなスライスの新しいコピーを使用し、古いスライスを GC にスローすることです。

2. 文字列

正しいスプライシング

文字列のスプライシングが 1 つのステートメントで完了できる場合は、 <span style="font-size: 15px;"> # を使用します。 </span>## オペレーター。これをループ内で行う必要がある場合は、string.Builder<span style="font-size: 15px;"></span> を使用し、その Grow<span style="font-size: 15px;"># を使用します。 </span>## メソッドは、メモリ割り当ての数を減らすために、Builder<span style="font-size: 15px;"></span> のサイズを事前に指定します。 変換の最適化

string と []byte は基礎的な構造が非常に似ており、メモリ割り当てを回避するためにこれら 2 つの型の間で強力な変換を使用できる場合があります。

文字列常駐

文字列をプールできるため、コンパイラが同じ文字列を 1 回だけ保存できるようになります。

割り当ての回避

#​​
##複合キーの代わりにマップ (カスケード) を使用でき、バイト スライスを使用できます。

fmt<span style="font-size: 15px;"> パッケージはすべてのメソッドがリフレクションを使用するため、使用しないようにしてください。 </span>

3. 構造

大きな構造のコピーを避ける

私たちが理解している小さな構造は、フィールドが 4 つ以内、マシン ワード サイズが 1 つ以内です。

#いくつかの典型的なコピー シーン

  • インターフェイスへのプロジェクト
  • チャネルの受信と送信
  • #マップ内の要素を置換
  • #要素をスライスに追加します。
  • 反復 (範囲)
  • ポインターを介した構造体フィールドへのアクセスを回避します
逆参照はコストがかかるため、特にループ内ではできるだけ少なくする必要があります。また、高速レジスタを使用する機能も失われます。

小さな構造の処理

この作業はエディターによって最適化されているため、コストが安くなります。

配置を使用して構造体のサイズを削減する

構造体を配置する (フィールドのサイズに応じて正しい順序で配置する) ことで、構造体のサイズを削減できます。サイズそのもの。

4. 関数

インライン関数を使用するか、自分でインライン化します

コンパイラによってインライン化できる小さな関数を作成してみてください。自分で関数にコードを埋め込むよりもさらに高速です。これは特にホット パスに当てはまります。

インラインにならないものはどれか

  • #リカバリ
  • ブロックを選択
  • # #型宣言
  • defer
  • ##goroutine
  • for-range
  • 関数パラメーターを賢く選択してください
重複する可能性があるため、小さなパラメーターを使用するようにしてください。最適化されました。レプリケーションとスタックの増加の GC 負荷のバランスを保つようにしてください。多数のパラメータを避け、プログラムで高速レジスタを使用します (レジスタの数は制限されています)。

名前付き戻り値

これは、関数本体でこれらの変数を宣言するよりも効率的だと思われます。

中間結果の保存

コンパイラーによるコードの最適化を支援し、中間結果を保存すると、コードを最適化するためのオプションがさらに増えます。

defer は慎重に使用してください

defer は使用しないようにするか、少なくともループ内で使用しないでください。

ヘルプ ホット パス

ホット パス、特に存続期間の短いオブジェクトにメモリを割り当てることは避けてください。最も一般的な分岐を作成します (if、switch)

5. Map

メモリを事前に確保します

スライスと同様に、マップの初期化時に指定しますその大きさ 。

空の構造体を値として使用する

struct{} は何もない (メモリを消費しない) ため、たとえばシグナルを渡すときにこれを使用すると非常に有益です。

クリアマップ

マップは拡大のみ可能で、縮小はできません。マップをリセットする必要がある場合、その要素をすべて削除しても役に立ちません。

キーと値にポインターを使用しないようにしてください

マップにポインターが含まれていない場合、GC は貴重な時間を無駄にすることはありません。文字列でもポインターが使用されるため、文字列の代わりにバイト配列をキーとして使用する必要があります。

変更の数を減らす

同様に、ポインターは使用したくありませんが、マップとスライスを組み合わせて使用​​し、キーをマップとスライス内の値。このようにして、制限なく値を変更できます。

6. インターフェイス

メモリ割り当ての計算

インターフェイスに値を割り当てる場合は、まずその値をどこかにコピーする必要があることに注意してください。 , 次に、ポインターを貼り付けます。重要なのはコピーすることです。インターフェイスのボックス化とボックス化解除のコストは、構造体サイズの割り当てとほぼ同じであることがわかります。

最適なタイプの選択

場合によっては、インターフェイスのボックス化およびボックス化解除中に割り当てが行われないことがあります。たとえば、変数や定数の小さい値やブール値、単純なフィールドが 1 つある構造体、ポインター (マップ、チャネル、関数を含む)

メモリ割り当ての回避
#​​

##他の場所と同様に、不必要な割り当てを避けるようにしてください。たとえば、2 回ボックス化する代わりに、1 つのインターフェイスを別のインターフェイスに割り当てます。

必要な場合にのみ使用してください

頻繁に呼び出される関数パラメータおよび返される結果でインターフェイスを使用することは避けてください。追加の開梱作業は必要ありません。インライン化が防止されるため、インターフェイス メソッド呼び出しの使用頻度を減らします。

7. ポインタ、チャネル、境界チェック

不必要な逆参照を避ける

特にループ内では負荷が高すぎることが判明します。逆参照は、私たちが自費で行いたくないものです。

チャネルの使用は非効率です

チャネル同期は、他の基本的な同期方法よりも時間がかかります。さらに、選択するケースが増えるほど、プログラムは遅くなります。ただし、選択、大文字と小文字、デフォルトは最適化されています。

不必要な境界チェックを避ける

これもコストがかかるため、避ける必要があります。たとえば、最大スライス インデックスを複数回チェック (取得) するのではなく、1 回だけチェックします。今すぐ極端なオプションを試してみるのが最善です。

概要

この記事では、同じ最適化ルールがいくつか見られました。

コンパイラが正しい決定を下せるよう支援していただければ、コンパイラは感謝します。コンパイル時にメモリを割り当て、中間結果を使用し、コードを読みやすい状態に保つようにしてください。

#繰り返しますが、暗黙的な最適化にはベンチマークが必須です。コンパイラのバージョン間の変更が早すぎるため、昨日機能したものが明日は機能しなくなる場合や、その逆の場合も同様です。

Go の組み込みプロファイリングおよびトレース ツールを忘れずに使用してください。

以上がGo: 簡単な最適化メモの詳細内容です。詳細については、PHP 中国語 Web サイトの他の関連記事を参照してください。

声明:
この記事はGo语言进阶学习で複製されています。侵害がある場合は、admin@php.cn までご連絡ください。