5

マイクロ最適化を開始することは私の目標ではないので、もしそれが最終的な結果になるのであれば、喜んで質問を取り下げます。しかし、私はいくつかの設計上の決定を始めようとしており、より多くの情報を得たいと思っています.

明確に定義された形式で文書化された多数のデータ構造を含むファイル形式を読み取って処理しています。それらを構造体としてコードで表現しました。

ここで、構造体を 1 バイト アラインメントで 1 つパックすると#pragma pack(1)、IO ストリームから構造体ポインターに直接構造体を読み取ることができます。これは便利です。構造体をパックしない場合はfread、フィールドを 1 つずつ、または一fread度にブロックしreinterpret_cast、構造体フィールドを 1 つずつパックすることができます。これはおそらくすぐに古くなります。

参考までに、構造体は (潜在的に) 数千単位で読み取られ、いくつかの処理が実行される可能性があります。これらは、ほとんどが符号なし 16 ビット整数 (約 60%)、符号なし 32 ビット整数 (約 30%)、および一部の 64 ビット整数で構成されています。

そこで当面の問題は、私は...

  • 何万もの小さな通話を行いfreadますか?
  • チャンクを読み取り、関連するバイトをコピーしますか?
  • 構造体をパックして、それらを直接読み取りますか?
4

3 に答える 3

5

最終的に、ソリューション A とソリューション B のパフォーマンスの違いは、ベンチマークによってのみ決定できます。インターネットで尋ねると、あなたのケースの現実を反映している場合と反映していない場合があるさまざまな結果が得られます.

データを「ミスアライン」すると、プロセッサは 1 つのデータに対して複数の読み取りを行う必要があります [書き込みにも同じことが当てはまります]。正確にどれだけの余分な時間がかかるかはプロセッサによって異なります - 一部のプロセッサは自動的にそれを行わないため、ランタイムシステムは「不正な読み取り」をトラップし、エミュレーションレイヤーで読み取りを実行します[または、一部のプロセッサーでは、単に「アラインされていないメモリアクセス」のプロセス]。明らかに、トラップを取得していくつかの読み取り操作を実行してから呼び出し元のコードに戻ることは、パフォーマンスにかなり大きな影響を与えます。アライメントされた読み取り操作よりも数百サイクル長くかかる可能性があります。

x86 の場合、「期待どおりに動作」しますが、通常は 1 クロック サイクル余分にかかるペナルティがあります [データが既に L1 キャッシュにある場合]。1 クロック サイクルは最新のプロセッサではあまり多くありませんが、ループの長さが 10000000000000 反復であり、アライメントされていないデータを n 回読み取る場合、n * 10000000000000 クロック サイクルが実行時間に追加されたことになります。これは重要な場合があります。

他の選択肢もパフォーマンスに影響を与えます。小さな読み取りを何度も行うと、大きな読み取りを 1 回行うよりも大幅に遅くなる可能性があります。パフォーマンスの観点からは、変換関数の方が優れている可能性があります。

繰り返しますが、これを「与えられた」ものと見なさないでください。実際には、さまざまなソリューションを比較する必要があります (または、1 つを選択し、パフォーマンスが悪くなく、コードの見た目が悪くない場合は、そのままにしておいてください)。 . あなたが「最良」であると提案する 3 つの解決策のすべてのケースを見つけることができると確信しています。

また、 #pragma packはコンパイラ固有のものであり、たとえば、「Microsoft」ソリューションと「gcc」ソリューションのどちらかを選択できるマクロを実現するのは簡単ではないことに注意してください。編集: 最近の gcc バージョンはこのオプションをサポートしているように見えますが、すべてのコンパイラがサポートしているわけではありません。

于 2013-04-19T16:21:53.147 に答える
2

別の回答へのコメントによると、コードはプラットフォームにとらわれず、ファイル形式のエンディアンが明確に指定されています。この場合、パックを直接読み取るstructと、読み取り後のエンディアンのクリーンアップ手順が必要になるか、ファイル形式とはエンディアンが異なるアーキテクチャで誤ったデータになるため、明確さが失われます。

常にバイト数を知っていると仮定すると (おそらくファイル内の構造体型インジケーターから)、作成されたオブジェクトのコンストラクターが属性ごとにメモリ バッファー属性からバイトを引き出す方法を知っているファクトリ パターンを使用することをお勧めします (ファイルがコンストラクターを介して loop/factory-create/deserialize-into-object-into-object-into を実行するよりも、全体をバッファーに読み込むことができるほど小さいこの方法では、エンディアンを制御し、コンパイラーの目的の構造体の配置を許可できます。

于 2013-04-19T16:21:45.400 に答える
1

パッキングを使用して構造体に直接読み込むと、コードが最も明確になります。それはまた、読むのが最も速い可能性があります。残念ながら、特に構造のレイアウトが将来変更された場合は、バグの原因になる可能性もあります。

多くの要因によって、要素の位置合わせが問題になる場合とそうでない場合があります。要素が最初に最大のものからサイズ順に並べ替えられている場合、配置は問題になる可能性はほとんどありません。ソースが構造全体を直接書き込むことによってバイト ストリームを生成した場合、それはそのシステムに対して適切に配置されている可能性が高く、ユーザー側で完全に機能する可能性があります。x86 アーキテクチャは、ミスアライメントをうまく処理できますが、最悪の場合でも速度がわずかに低下するだけです。それでも、キャッシュ ライン全体が一度に読み込まれるキャッシュ構造によって最小限に抑えられ、ほとんどのバイトが既にキャッシュにあることが保証されます。他のアーキテクチャではミスアライメントがまったく処理されない場合がありますが、それが発生した場合はすぐにわかります。

ソースとは異なるエンディアンが必要な場合は、構造体の各要素に対して関数を呼び出して、それらを個別に修正できます。その時点で、直接読み取りの単純さと明快さが失われ、他の方法を使用した方がよい場合があります。

于 2013-04-19T16:13:12.937 に答える