100-go-mistakes - 📖 100 Go Mistakes のソースコードとコミュニティスペース

(Source code and community space of 📖 100 Go Mistakes)

Created at: 2020-12-12 19:52:59
Language: Go
License: NOASSERTION

📖囲碁の 100 の間違いとその回避方法

2022 年 10 月にマニングから出版された100 の Go の間違いとその回避方法のソース コードとコミュニティ スペース(本について)。

ℹ️本を購入するのに苦労している場合は、@teivahに DM を送ってください。

よくある囲碁の間違い

🌎このセクションには、本の 100 の間違いが含まれています。ただし、コミュニティに開かれたセクションでもあります。間違いを追加する必要があると思われる場合は、コミュニティの間違いの問題を作成してください。ここでコミュニティの提案を確認し、👍または👎問題自体に対する反応。問題がコンセンサスに達すると、以下の対応するセクションに追加されます。

コードとプロジェクトの組織

意図しない変数のシャドーイング (#1)

シャドウ変数を避けることで、間違った変数を参照したり、読者を混乱させたりするなどのミスを防ぐことができます。

不要なネストされたコード (#2)

ネストされたレベルを避け、ハッピー パスを左側に配置することで、メンタル コード モデルの構築が容易になります。

init 関数の誤用 (#3)

変数を初期化するときは、init 関数ではエラー処理が制限されており、状態の処理とテストがより複雑になることに注意してください。ほとんどの場合、初期化は特定の関数として処理する必要があります。

ゲッターとセッターの使いすぎ (#4)

ゲッターとセッターの使用を強制することは、Go では慣用的ではありません。実用的であり、効率性と盲目的に特定のイディオムに従うこととの間の適切なバランスを見つけることが、進むべき道です。

インターフェース汚染 (#5)

抽象化は、作成するのではなく、発見する必要があります。不必要な複雑さを避けるために、インターフェイスが必要になると予想されるときではなく、必要なときにインターフェイスを作成するか、少なくとも抽象化が有効であることを証明できる場合にインターフェイスを作成します。

プロデューサー側のインターフェース (#6)

インターフェイスをクライアント側に保持することで、不要な抽象化を回避できます。

インターフェースを返す (#7)

柔軟性が制限されるのを防ぐために、ほとんどの場合、関数はインターフェイスではなく具体的​​な実装を返す必要があります。逆に、関数は可能な限りインターフェイスを受け入れる必要があります。

any
何も言わない (#8)

any
などの可能なタイプを受け入れるか返す必要がある場合にのみ使用してください
json.Marshal
。そう
any
しないと、意味のある情報が提供されず、呼び出し元が任意のデータ型でメソッドを呼び出すことができるため、コンパイル時の問題が発生する可能性があります。

ジェネリックをいつ使用するかについて混乱している(#9)

ジェネリックと型パラメーターに依存すると、定型コードを記述して要素や動作を除外することができなくなる可能性があります。ただし、型パラメーターを時期尚早に使用しないでください。具体的な必要性がある場合にのみ使用してください。そうしないと、不必要な抽象化と複雑さが生じます。

型の埋め込みで起こりうる問題を認識していない (#10)

型の埋め込みを使用すると、ボイラープレート コードを回避することもできます。ただし、そうすることで、一部のフィールドが非表示のままである必要がある可視性の問題が発生しないようにしてください。

機能オプション パターンを使用しない (#11)

オプションを便利かつ API フレンドリーな方法で処理するには、機能オプション パターンを使用します。

プロジェクトの編成ミス (プロジェクト構造とパッケージ編成) (#12)

project-layout などのレイアウトに従うことは、特に新しいプロジェクトを標準化するための既存の規則を探している場合に、Go プロジェクトの構造化を開始するための良い方法です。

ユーティリティ パッケージの作成 (#13)

ネーミングは、アプリケーション設計の重要な部分です。

common
、
util
、およびなどのパッケージを作成
shared
しても、読者にはあまり価値がありません。そのようなパッケージを意味のある具体的なパッケージ名にリファクタリングします。

パッケージ名の衝突を無視する (#14)

変数とパッケージの間で名前が競合し、混乱やバグの原因となるのを避けるために、それぞれに一意の名前を使用してください。これが不可能な場合は、インポート エイリアスを使用して修飾子を変更し、パッケージ名と変数名を区別するか、より適切な名前を考えてください。

コードのドキュメントの欠落 (#15)

クライアントとメンテナーがコードの目的を理解できるように、エクスポートされた要素を文書化します。

リンターを使用しない (#16)

コードの品質と一貫性を向上させるには、リンターとフォーマッターを使用します。

データ型

8 進リテラルで混乱を招く (#17)

既存のコードを読むときは、0 で始まる整数リテラルは 8 進数であることに注意してください。また、読みやすさを向上させるために、8 進整数の前に を付けて明示的にし

0o
ます。

整数オーバーフローの無視 (#18)

整数のオーバーフローとアンダーフローは Go でサイレントに処理されるため、独自の関数を実装してそれらをキャッチできます。

浮動小数点を理解していない (#19)

特定のデルタ内で浮動小数点比較を行うと、コードの移植性が保証されます。

加算または減算を実行するときは、精度を優先するために同じような大きさの順序で演算をグループ化します。また、足し算・引き算の前に掛け算・割り算を行います。

スライスの長さと容量を理解していない (#20)

スライスの長さと容量の違いを理解することは、Go 開発者のコ​​ア知識の一部である必要があります。スライスの長さはスライスで使用可能な要素の数であり、スライスの容量は補助配列の要素の数です。

非効率なスライスの初期化 (#21)

スライスを作成するとき、その長さがすでにわかっている場合は、指定された長さまたは容量で初期化します。これにより、割り当ての数が減り、パフォーマンスが向上します。同じロジックがマップにも当てはまり、サイズを初期化する必要があります。

nil と空のスライスについて混乱している (#22)

encoding/json
またはパッケージを使用する場合などの一般的な混乱を避けるために
reflect
、nil スライスと空のスライスの違いを理解する必要があります。どちらも長さゼロ、容量ゼロのスライスですが、nil スライスだけは割り当てを必要としません。

スライスが空かどうかを適切にチェックしない (#23)

スライスに要素が含まれていないかどうかを確認するには、その長さを確認します。このチェックは、スライスが

nil
空かどうかに関係なく機能します。同じことが地図にも言えます。

明確な API を設計するには、nil スライスと空のスライスを区別しないでください。

スライスのコピーが正しく作成されない (#24)

copy
組み込み関数を使用して 1 つのスライスを別のスライスにコピーするには、コピーされる要素の数が 2 つのスライスの長さの最小値に対応することに注意してください。

スライス追加を使用した予期しない副作用 (#25)

コピーまたは完全なスライス式を

append
使用すると、2 つの異なる関数が同じ配列に基づくスライスを使用する場合に競合が発生するのを防ぐことができます。ただし、大きなスライスを縮小する場合は、スライスのコピーのみがメモリ リークを防ぎます。

スライスとメモリリーク (#26)

ポインター フィールドを持つ構造体またはポインターのスライスを操作する場合、スライス操作によって除外された要素を nil としてマークすることで、メモリ リークを回避できます。

非効率的なマップの初期化 (#27)

#21を参照してください。

マップとメモリリーク (#28)

マップは常にメモリ内で拡大できますが、縮小することはありません。したがって、メモリの問題が発生する場合は、Go に強制的にマップを再作成させるか、ポインターを使用するなど、さまざまなオプションを試すことができます。

値を誤って比較する (#29)

Go で型を比較す​​るには、2 つの型が比較可能であれば == 演算子と != 演算子を使用できます: ブール値、数値、文字列、ポインター、チャネル、および構造体が完全に比較可能な型で構成されています。それ以外の場合は、

reflect.DeepEqual
リフレクションの代償を払って使用するか、カスタムの実装とライブラリを使用できます。

制御構造

要素が
range
ループでコピーされることを無視する (#30)

ループ内の値要素は

range
コピーです。したがって、たとえば、構造体を変更するには、そのインデックスまたは従来の
for
ループを介してアクセスします (変更する要素またはフィールドがポインターでない場合)。

引数が
range
ループ (チャネルと配列) で評価される方法を無視する (#31)

オペレーターに渡される式が

range
ループの開始前に 1 回だけ評価されることを理解すると、チャネルまたはスライスの反復における非効率的な割り当てなどのよくある間違いを回避するのに役立ちます。

ループでポインター要素を使用することの影響を無視
range
する (#32)

ローカル変数を使用したり、インデックスを使用して要素にアクセスしたりすることで、ループ内でポインターをコピーする際のミスを防ぐことができます。

マップの反復中に誤った仮定を行う (反復中の順序付けとマップの挿入) (#33)

マップを使用するときに予測可能な出力を確保するには、マップのデータ構造が次のことを覚えておいてください。

  • データをキーで並べ替えない
  • 広告掲載順は保持されません
  • 確定的な反復順序がない
  • 反復中に追加された要素が、この反復中に生成されることを保証しません

break
ステートメントの仕組みを無視する(#34)

break
orをラベルとともに使用する
continue
と、特定のステートメントが強制的に破棄されます。これは、ループ内の
switch
orステートメントで役立ちます。
select

ループ内での使用
defer
(#35)

関数内でループ ロジックを抽出する

defer
と、各反復の最後にステートメントが実行されます。

ストリングス

ルーンの概念を理解していない (#36)

ルーンが Unicode コード ポイントの概念に対応し、複数のバイトで構成できることを理解することは、文字列を正確に操作するための Go 開発者のコ​​ア知識の一部である必要があります。

不正確な文字列反復 (#37)

演算子を使用して文字列を

range
反復すると、ルーンのバイト シーケンスの開始インデックスに対応するインデックスを使用してルーンを反復処理します。特定のルーン インデックス (3 番目のルーンなど) にアクセスするには、文字列を
[]rune
.

トリム機能の誤用 (#38)

strings.TrimRight
/
strings.TrimLeft
は、指定されたセットに含まれる末尾/先頭のルーン文字をすべて削除しますが、 / は指定されたサフィックス
strings.TrimSuffix
/
strings.TrimPrefix
プレフィックスのない文字列を返します。

最適化されていない文字列連結 (#39)

strings.Builder
各反復中に新しい文字列が割り当てられないように、文字列のリストを連結する必要があります。

役に立たない文字列変換 (#40)

bytes
パッケージがパッケージと同じ操作を提供することを覚えておくと、
strings
余分なバイト/文字列の変換を避けることができます。

部分文字列とメモリ リーク (#41)

部分文字列の代わりにコピーを使用すると、部分文字列操作によって返される文字列が同じバイト配列によってサポートされるため、メモリ リークを防ぐことができます。

関数とメソッド

どのタイプの受信機を使用すればよいかわからない (#42)

値またはポインター レシーバーを使用するかどうかは、型、変更する必要があるかどうか、コピーできないフィールドが含まれているかどうか、オブジェクトの大きさなどの要因に基づいて決定する必要があります。疑わしい場合は、ポインタ レシーバを使用してください。

名前付き結果パラメータを使用しない (#43)

名前付きの結果パラメーターを使用すると、特に複数の結果パラメーターが同じ型である場合に、関数/メソッドの可読性を向上させる効率的な方法になります。場合によっては、名前付き結果パラメーターがゼロ値に初期化されるため、この方法が便利な場合もあります。ただし、潜在的な副作用には注意してください。

名前付き結果パラメータによる意図しない副作用 (#44)

#43を参照してください。

nil レシーバーを返す (#45)

インターフェイスを返すときは、nil ポインタではなく明示的な nil 値を返すことに注意してください。そうしないと、呼び出し元が nil 以外の値を受け取るため、意図しない結果が生じる可能性があります。

ファイル名を関数入力として使用する (#46)

ファイル名の代わりに型を受け取るように関数を設計する

io.Reader
と、関数の再利用性が向上し、テストが容易になります。

引数とレシーバーがどのように評価されるかを無視する
defer
(引数評価、ポインター、および値レシーバー) (#47)

関数へのポインターを渡すこと

defer
と、呼び出しをクロージャー内にラップすることは、引数とレシーバーの即時評価を克服するための 2 つの可能な解決策です。

エラー管理

パニック (#48)

Using

panic
は、Go でエラーを処理するためのオプションです。ただし、回復不能な状況でのみ控えめに使用する必要があります。たとえば、プログラマーのエラーを通知する場合や、必須の依存関係の読み込みに失敗した場合などです。

エラーをラップするタイミングを無視する (#49)

エラーをラップすると、エラーをマークしたり、追加のコンテキストを提供したりできます。ただし、エラー ラッピングは、呼び出し元がソース エラーを利用できるようにするため、結合の可能性を生み出します。それを防ぎたい場合は、エラーラッピングを使用しないでください。

エラータイプの不正確な比較 (#50)

Go 1.13 エラー ラッピングを

%w
ディレクティブ and
fmt.Errorf
で使用する場合、型または値に対するエラーの比較は、それぞれ
errors.As
またはを使用して行う必要
errors.Is
があります。それ以外の場合、チェックしたい返されたエラーがラップされていると、チェックに失敗します。

エラー値の不正確な比較 (#51)

#50を参照してください。

予想されるエラーを伝えるには、エラー センティネル (エラー値) を使用します。予期しないエラーは、特定のエラー タイプである必要があります。

エラーを 2 回処理する (#52)

ほとんどの場合、エラーは 1 回だけ処理する必要があります。エラーをログに記録することは、エラーを処理することです。したがって、ログに記録するか、エラーを返すかを選択する必要があります。多くの場合、エラー ラッピングは、エラーに追加のコンテキストを提供し、ソース エラーを返すことができるため、ソリューションです。

エラーを処理しない (#53)

関数呼び出し中か関数内かに関係なく、エラーを無視するには

defer
、空白の識別子を使用して明示的に行う必要があります。そうしないと、将来の読者は、それが意図的なものなのかミスなのかについて混乱する可能性があります.

エラーを処理し
defer
ない (#54)

多くの場合、

defer
関数によって返されるエラーを無視すべきではありません。コンテキストに応じて、直接処理するか、呼び出し元に伝達します。無視する場合は、空白の識別子を使用してください。

並行性: 基盤

並行性と並列性を混同する (#55)

並行処理と並列処理の基本的な違いを理解することは、Go 開発者の知識の基礎です。並行性は構造に関するものであり、並列性は実行に関するものです。

並行性を考えると常に高速になります (#56)

熟練した開発者になるには、同時実行が常に高速であるとは限らないことを認識する必要があります。最小限のワークロードの並列化を含むソリューションは、順次実装よりも必ずしも高速であるとは限りません。シーケンシャル ソリューションとコンカレント ソリューションのベンチマークを行うことで、仮定を検証することができます。

チャネルまたはミューテックスをいつ使用するかについて困惑している (#57)

ゴルーチンの相互作用を認識することは、チャネルとミューテックスのどちらを使用するかを決定する際にも役立ちます。一般に、並列ゴルーチンには同期が必要であり、したがってミューテックスが必要です。逆に、並行ゴルーチンは通常、調整とオーケストレーション、したがってチャネルを必要とします。

競合の問題を理解していない (データ競合と競合状態および Go メモリ モデル) (#58)

同時実行に習熟するということは、データ競合と競合状態が異なる概念であることを理解することも意味します。データ競合は、複数のゴルーチンが同時に同じメモリ位置にアクセスし、そのうちの少なくとも 1 つが書き込みを行っている場合に発生します。一方、データ競合がないということは、必ずしも決定論的な実行を意味するわけではありません。動作が、制御できないイベントのシーケンスまたはタイミングに依存している場合、これは競合状態です。

Go メモリ モデルと、順序付けと同期に関する基本的な保証を理解することは、データ競合や競合状態を防ぐために不可欠です。

ワークロード タイプの同時実行の影響を理解していない (#59)

特定の数のゴルーチンを作成するときは、ワークロードのタイプを考慮してください。CPU バウンドのゴルーチンを作成するということは、この数を

GOMAXPROCS
変数の近くに制限することを意味します (デフォルトでは、ホストの CPU コアの数に基づきます)。I/O バウンドのゴルーチンの作成は、外部システムなどの他の要因に依存します。

Go コンテキストの誤解 (#60)

Go コンテキストは、Go の同時実行の基礎の 1 つでもあります。コンテキストを使用すると、期限、キャンセル シグナル、および/またはキーと値のリストを運ぶことができます。

並行性: 練習

不適切なコンテキストの伝播 (#61)

コンテキストをキャンセルできる条件を理解することは、コンテキストを伝播するときに重要です。たとえば、応答が送信されたときに HTTP ハンドラーがコンテキストをキャンセルする場合などです。

停止するタイミングを知らずにゴルーチンを開始する (#62)

リークを回避するということは、ゴルーチンが開始されるたびに、最終的にそれを停止する計画を立てる必要があることに注意することを意味します。

ゴルーチンとループ変数に注意しない (#63)

ゴルーチンとループ変数のバグを回避するには、ローカル変数を作成するか、クロージャの代わりに関数を呼び出します。

選択とチャネルを使用して決定論的な動作を期待する (#64)

select
複数のチャネルがあり、複数のオプションが可能な場合にケースをランダムに選択することを理解すると、微妙な並行性のバグにつながる可能性のある誤った仮定を防ぐことができます。

通知チャネルを使用しない (#65)

chan struct{}
タイプを使用して通知を送信します。

nil チャネルを使用しない (#66)

たとえば、nil チャネルを使用すると、ステートメントからケースを削除できるため、同時実行ツールセットの一部にする必要があります。

select

チャンネルサイズに戸惑う (#67)

問題が発生した場合は、使用する適切なチャネル タイプを慎重に決定してください。バッファリングされていないチャネルのみが、強力な同期保証を提供します。

バッファリングされたチャネル以外のチャネル サイズを指定する十分な理由があるはずです。

文字列フォーマットで起こりうる副作用 (etcd データ競合の例とデッドロック) を忘れる (#68)

文字列の書式設定が既存の関数の呼び出しにつながる可能性があることに注意することは、デッドロックやその他のデータ競合の可能性に注意することを意味します。

追加によるデータ競合の作成 (#69)

呼び出し

append
に常にデータ競合がないわけではありません。したがって、共有スライスで同時に使用しないでください。

スライスとマップでミューテックスを不正確に使用する (#70)

スライスとマップはポインターであることを覚えておくと、一般的なデータ競合を防ぐことができます。

誤用
sync.WaitGroup
(#71)

To accurately use

sync.WaitGroup
, call the
Add
method before spinning up goroutines.

Forgetting about
sync.Cond
(#72)

You can send repeated notifications to multiple goroutines with

sync.Cond
.

Not using
errgroup
(#73)

You can synchronize a group of goroutines and handle errors and contexts with the

errgroup
package.

Copying a
sync
type (#74)

sync
types shouldn’t be copied.

Standard Library

Providing a wrong time duration (#75)

Remain cautious with functions accepting a

time.Duration
. Even though passing an integer is allowed, strive to use the time API to prevent any possible confusion.

time.After
and memory leaks (#76)

Avoiding calls to

time.After
in repeated functions (such as loops or HTTP handlers) can avoid peak memory consumption. The resources created by
time.After
are released only when the timer expires.

JSON handling common mistakes (#77)

  • Unexpected behavior because of type embedding

    Be careful about using embedded fields in Go structs. Doing so may lead to sneaky bugs like an embedded time.Time field implementing the

    json.Marshaler
    interface, hence overriding the default marshaling behavior.

  • JSON and the monotonic clock

    When comparing two

    time.Time
    structs, recall that
    time.Time
    contains both a wall clock and a monotonic clock, and the comparison using the == operator is done on both clocks.

  • Map of

    any

    To avoid wrong assumptions when you provide a map while unmarshaling JSON data, remember that numerics are converted to

    float64
    by default.

Common SQL mistakes (#78)

  • Forgetting that

    sql.Open
    doesn't necessarily establish connections to a database

    Call the

    Ping
    or
    PingContext
    method if you need to test your configuration and make sure a database is reachable.

  • Forgetting about connections pooling

    Configure the database connection parameters for production-grade applications.

  • Not using prepared statements

    Using SQL prepared statements makes queries more efficient and more secure.

  • Mishandling null values

    Deal with nullable columns in tables using pointers or

    sql.NullXXX
    types.

  • Not handling rows iteration errors

    次の行の準備中にエラーを見逃さないように、行の反復後に

    Err
    メソッドを呼び出します。
    sql.Rows

sql.Rows
一時的なリソース (HTTP 本文、 、および)を閉じない
os.File
(#79)

最終的には、実装しているすべての構造体を閉じて、

io.Closer
リークの可能性を回避します。

HTTP リクエストへの返信後に return ステートメントを忘れる (#80)

return
HTTP ハンドラーの実装で予期しない動作を回避するには、 の後にハンドラーを停止する場合はステートメントを見逃さないようにしてください
http.Error
。

デフォルトの HTTP クライアントとサーバーの使用 (#81)

運用レベルのアプリケーションでは、デフォルトの HTTP クライアントとサーバーの実装を使用しないでください。これらの実装には、本番環境で必須となるタイムアウトと動作がありません。

テスト

テストを分類しない (ビルドタグ、環境変数、ショートモード) (#82)

ビルド フラグ、環境変数、またはショート モードを使用してテストを分類すると、テスト プロセスがより効率的になります。ビルド フラグまたは環境変数 (ユニット テストと統合テストなど) を使用してテスト カテゴリを作成し、短時間実行テストと長時間実行テストを区別して、実行するテストの種類を決定できます。

レースフラグを有効にしない (#83)

-race
並行アプリケーションを作成する場合は、フラグを有効にすることを強くお勧めします。そうすることで、ソフトウェアのバグにつながる可能性のある潜在的なデータ競合をキャッチできます。

テスト実行モード (並列およびシャッフル) を使用しない (#84)

フラグを使用する

-parallel
と、テスト、特に実行時間の長いテストを高速化する効率的な方法になります。

フラグを使用し

-shuffle
て、テスト スイートがバグを隠す可能性のある誤った仮定に依存しないようにします。

テーブル駆動テストを使用しない (#85)

テーブル駆動テストは、コードの重複を防ぎ、将来の更新を処理しやすくするために、一連の類似したテストをグループ化する効率的な方法です。

単体テストでのスリープ (#86)

同期を使用してスリープを回避し、テストの不安定さを軽減し、より堅牢にします。同期が不可能な場合は、再試行の方法を検討してください。

時間 API を効率的に処理しない (#87)

time API を使用して関数を処理する方法を理解することは、テストの不安定さを軽減するもう 1 つの方法です。非表示の依存関係の一部として時間を処理したり、クライアントに時間を提供するように依頼したりするなど、標準的な手法を使用できます。

テスト ユーティリティ パッケージ (
httptest
および
iotest
) を使用しない (#88)

この

httptest
パッケージは、HTTP アプリケーションを扱うのに役立ちます。クライアントとサーバーの両方をテストするための一連のユーティリティを提供します。

この

iotest
パッケージは、io.Reader を記述し、アプリケーションがエラーに耐えられるかどうかをテストするのに役立ちます。

不正確なベンチマークの記述 (#89)

  • タイマーをリセットまたは一時停止しない

    時間メソッドを使用して、ベンチマークの精度を維持します。

  • マイクロベンチマークについて間違った仮定をする

    のようなツールを増やし

    benchtime
    たり使用したりする
    benchstat
    と、マイクロベンチマークを扱うときに役立ちます。

    最終的にアプリケーションを実行するシステムがマイクロベンチマークを実行するシステムと異なる場合は、マイクロベンチマークの結果に注意してください。

  • コンパイラの最適化に注意を払わない

    Make sure the function under test leads to a side effect, to prevent compiler optimizations from fooling you about the benchmark results.

  • Being fooled by the observer effect

    To prevent the observer effect, force a benchmark to re-create the data used by a CPU-bound function.

Not exploring all the Go testing features (#90)

  • Code coverage

    Use code coverage with the

    -coverprofile
    flag to quickly see which part of the code needs more attention.

  • Testing from a different package

    Place unit tests in a different package to enforce writing tests that focus on an exposed behavior, not internals.

  • Utility functions

    Handling errors using the

    *testing.T
    variable instead of the classic
    if err != nil
    makes code shorter and easier to read.

  • Setup and teardown

    You can use setup and teardown functions to configure a complex environment, such as in the case of integration tests.

Optimizations

Not understanding CPU caches (#91)

  • CPU architecture

    Understanding how to use CPU caches is important for optimizing CPU-bound applications because the L1 cache is about 50 to 100 times faster than the main memory.

  • Cache line

    Being conscious of the cache line concept is critical to understanding how to organize data in data-intensive applications. A CPU doesn’t fetch memory word by word; instead, it usually copies a memory block to a 64-byte cache line. To get the most out of each individual cache line, enforce spatial locality.

  • Slice of structs vs. struct of slices

  • Predictability

    CPU に対してコードを予測可能にすることは、特定の機能を最適化する効率的な方法にもなります。たとえば、単位ストライドまたは定数ストライドは CPU で予測できますが、非単位ストライド (リンク リストなど) は予測できません。

  • キャッシュ配置ポリシー

    重大なストライドを回避して、キャッシュのごく一部のみを使用するには、キャッシュが分割されていることに注意してください。

偽共有につながる並行コードの作成 (#92)

下位レベルの CPU キャッシュがすべてのコアで共有されていないことを知っておくと、同時実行コードの記述中に誤った共有などのパフォーマンス低下パターンを回避するのに役立ちます。記憶の共有は幻想です。

命令レベルの並列性を考慮していない (#93)

命令レベルの並列処理 (ILP) を使用してコードの特定の部分を最適化し、CPU ができるだけ多くの並列命令を実行できるようにします。データハザードを特定することは、主要なステップの 1 つです。

データアライメントを意識していない (#94)

Go では、基本型が独自のサイズに合わせて配置されていることを覚えておくことで、よくある間違いを避けることができます。たとえば、構造体のフィールドをサイズ別に降順に再編成すると、構造体がよりコンパクトになる可能性があることに注意してください (メモリ割り当てが少なくなり、空間的局所性が向上する可能性があります)。

スタックとヒープを理解していない (#95)

ヒープとスタックの基本的な違いを理解することも、Go アプリケーションを最適化する際の重要な知識の一部である必要があります。スタック割り当てはほとんど無料ですが、ヒープ割り当ては遅く、GC に依存してメモリをクリーンアップします。

割り当てを減らす方法がわからない (API の変更、コンパイラの最適化、および
sync.Pool
) (#96)

割り当てを減らすことも、Go アプリケーションを最適化する上で不可欠な側面です。これは、共有を避けるために API を慎重に設計する、一般的な Go コンパイラの最適化を理解する、

sync.Pool
.

インライン化に依存しない (#97)

高速パスのインライン化手法を使用して、関数を呼び出すための償却時間を効率的に削減します。

Go 診断ツール (プロファイリング [pprof、CPU、ヒープ、ゴルーチン、ブロック、およびミューテックスのプロファイリングを有効にする] および実行トレーサー) を使用しない (#98)

プロファイリングと実行トレーサーを利用して、アプリケーションのパフォーマンスと最適化する部分を理解してください。

GC の仕組みを理解していない (#99)

GC の調整方法を理解すると、急激な負荷の増加をより効率的に処理できるなど、複数の利点が得られます。

Docker と Kubernetes で Go を実行することの影響を理解していない (#100)

Docker および Kubernetes にデプロイされたときに CPU スロットリングを回避するために、Go は CFS に対応していないことに注意してください。