18

関数/メソッドに多くの入力パラメーターが含まれている場合、異なる順序で渡す場合に違いはありますか? もしそうなら、どの面(読みやすさ、効率など)で?自分の関数/メソッドに対してどのようにすればよいですか?

それは私には思われる:

  1. 参照/ポインターによって渡されるパラメーターは、多くの場合、値によって渡されるパラメーターの前に来ます。例えば:

    void* memset( void* dest, int ch, std::size_t count ); 
    
  2. 多くの場合、宛先パラメータはソース パラメータの前に来ます。例えば:

    void* memcpy( void* dest, const void* src, std::size_t count );
    
  3. いくつかの厳しい制約を除いて、つまり、デフォルト値を持つパラメーターは最後に来る必要があります。例えば:

    size_type find( const basic_string& str, size_type pos = 0 ) const;
    
  4. それらは、どの順序で渡されても機能的に同等です (同じ目標を達成します)。

4

4 に答える 4

12

それが問題になる理由はいくつかあります - 以下にリストします。C++ 標準自体は、この分野での特定の動作を義務付けていないため、パフォーマンスへの影響を推論する移植可能な方法はなく、たとえ 1 つの実行可能ファイルで何かが明らかに (わずかに) 高速であったとしても、プログラム内のどこかで、またはコンパイラーに変更が加えられたとしてもです。オプションまたはバージョンによって、以前の利点が削除されたり、逆になったりする可能性があります。実際には、パフォーマンス チューニングにおいてパラメータの順序付けが重要であるという話を聞くことは非常にまれです。どうしても気になる場合は、独自のコンパイラの出力および/または結果のコードのベンチマークを調べることをお勧めします。

例外

関数パラメーターに渡される式の評価の順序は指定されておらず、ソース コードに表示される順序の変更によって影響を受ける可能性が非常に高く、一部の組み合わせは CPU 実行パイプラインでより適切に機能したり、例外を早期に発生させたりします。他のパラメーターの準備をショートさせます。一部のパラメーターが一時オブジェクト (式の結果など) であり、割り当て/構築および破棄/割り当て解除にコストがかかる場合、これは重要なパフォーマンス要因になる可能性があります。繰り返しますが、プログラムに変更を加えると、以前に観察された利点またはペナルティが取り除かれたり、逆になったりする可能性があるため、これを気にする場合は、関数呼び出しを行う前に、最初に評価するパラメーターの名前付き一時を作成する必要があります。

レジスタとキャッシュ (スタック メモリ)

一部のパラメーターはレジスターで渡されますが、他のパラメーターはスタックにプッシュされます。これは事実上、少なくとも最速の CPU キャッシュに入ることを意味し、それらの処理が遅くなる可能性があることを意味します。

とにかく関数がすべてのパラメーターにアクセスすることになり、パラメーター X をレジスターに入れ、Y をスタックに入れるか、またはその逆にするかの選択である場合、それらがどのように渡されるかはあまり重要ではありませんが、関数が条件を持っている可能性があることを考えるとどの変数が実際に使用されるか (if ステートメント、スイッチ、入力されるかどうかわからないループ、早期リターンまたはブレークなど) に影響を与えるため、実際には必要でない変数がスタックにあり、必要な変数がスタックにある場合は、より高速になる可能性があります。レジスタ。

呼び出し規約の背景と情報については、http://en.wikipedia.org/wiki/X86_calling_conventionsを参照してください。

配置とパディング

パフォーマンスは、理論的には、パラメーターを渡す規則の細目によって影響を受ける可能性があります。パラメーターは、スタック上のアクセス (またはおそらくフルスピード) に対して特定のアライメントを必要とする場合があり、コンパイラーは、プッシュする値を並べ替えるのではなく、パディングすることを選択する場合があります。パラメーターのデータがキャッシュ ページ サイズのスケールでない限り、それが重要であることを想像するのは困難です。

非業績要因

あなたが言及した他の要因のいくつかは非常に重要になる可能性があります.それらを渡します。ただし、特に支配的な慣習はありません。

于 2014-01-02T10:57:54.403 に答える
3

呼び出し規約について言及している回答がいくつかあります。それらはあなたの質問とは何の関係もありません。使用する呼び出し規約に関係なく、パラメーターを宣言する順序は重要ではありません。同じ数のパラメーターがレジスターによって渡され、同じ量のパラメーターがスタックによって渡される限り、どのパラメーターがレジスターによって渡され、どのパラメーターがスタックによって渡されるかは問題ではありません。ネイティブ アーキテクチャのサイズ (32 ビットの場合は 4 バイト、64 ビットの場合は 8 バイト) よりもサイズが大きいパラメーターは、アドレスで渡されるため、小さいサイズのデータ​​と同じ速度で渡されることに注意してください。 .

例を見てみましょう:

6 つのパラメーターを持つ関数があります。そして、1 つのパラメーターをレジスターで渡し、残り (この場合は 5) をスタックで渡す呼び出し規約を CA と呼び、2 つ目の呼び出し規約を CB と呼び、4 つのパラメーターをレジスターで渡し、残りを CB と呼びます。 (この場合は 2) スタックごと。

もちろん、その CA は CB よりも高速ですが、パラメーターが宣言されている順序とは関係ありません。CA の場合、最初に (レジスターによって) 宣言するパラメーターと、2 番目、3 番目、..6 番目 (スタック) で宣言するパラメーターに関係なく高速になり、CB の場合、レジスターに対して宣言する 4 つの引数に関係なく高速になります。そして、最後の2つのパラメーターとして宣言します。


さて、あなたの質問に関して:

必須の唯一の規則は、オプションのパラメーターは最後に宣言する必要があるということです。オプションのパラメータの後に、オプションではないパラメータを続けることはできません。

それ以外は、好きな順序で使用できます。私から言える唯一の強力なアドバイスは、一貫性を保つことです。モデルを選択し、それに固執します。

考慮できるいくつかのガイドライン:

  • 宛先はソースの前に来ます。これは に近いはずdestination = sourceです。
  • バッファのサイズはバッファの後に来ます:f(char * s, unsigned size)
  • 最初に入力パラメーター、最後に出力パラメーター (これは、最初に提供したものと競合します)

しかし、パラメーターの順序について「間違った」または「正しい」というものはなく、普遍的に受け入れられているガイドラインさえありません。何かを選択し、一貫性を保ちます。

編集

パラメータを順序付ける「間違った」方法を考えました:アルファベット順:)。

編集 2

たとえば、CA の場合、vector(100) と int を渡す場合、vector(100) が最初に来る方が良いでしょう。つまり、レジスタを使用してより大きなデータ型をロードします。右?

いいえ。前述のとおり、データ サイズは関係ありません。32 ビット アーキテクチャについて話しましょう (同じ議論が 16 ビット、64 ビットなどのどのアーキテクチャにも当てはまります)。アーキテクチャのネイティブ サイズに関連するパラメータのサイズに関して考えられる 3 つのケースを分析してみましょう。

  • 同じサイズ: 4 バイトのパラメーター。ここで話すことは何もありません。
  • より小さいサイズ: 4 バイトのレジスタが使用されるか、4 バイトがスタックに割り当てられます。したがって、ここでも興味深いことは何もありません。
  • より大きなサイズ: (例: 多くのフィールドを持つ構造体、または静的配列)。この引数を渡すためにどの方法が選択されても、このデータはメモリに常駐し、渡されるのはそのデータへのポインタ (サイズ 4 バイト) です。ここでも、スタック上に 4 バイトのレジスタまたは 4 バイトがあります。

パラメータのサイズは関係ありません。

編集 3

@TonyDがどのように説明したか、すべてのパラメーターにアクセスしない場合、順序は重要です。彼の答えを見てください。

于 2014-01-02T11:33:15.683 に答える
3

厳密に言えば、それは問題ではありません。パラメーターはスタックにプッシュされ、関数は何らかの方法でスタックからそれらを取得することによってそれらにアクセスします。

ただし、ほとんどの C/C++ コンパイラでは、別の呼び出し規約を指定できます。たとえば、Visual C++ は、最初の 2 つのパラメーターを ECX および EDX レジスターに格納する__fastcall規則をサポートしています。これにより、(理論的には) 適切な状況でパフォーマンスが向上するはずです。

ポインターを ECX レジスターに格納する__thiscallもあります。thisC++ を使用している場合は、これが役立つ場合があります。

于 2014-01-02T10:48:03.973 に答える
1

何とか関連ページを見つけました。

https://softwareengineering.stackexchange.com/questions/101346/what-is-best-practice-on-ordering-parameters-in-a-function

https://google.github.io/styleguide/cppguide.html#Function_Parameter_Ordering

したがって、まず Google の C++ スタイルは、入力パラメーターまたは出力パラメーター内の実際の順序に答えることができないため、実際には質問に答えません。

別のページでは、基本的に、ある意味で理解しやすく、使いやすい順序パラメーターを提案しています。

読みやすくするために、個人的にはアルファベット順でパラメータを並べることを好みます。ただし、パラメーターを適切に順序付けて名前を付け、理解しやすく、使用しやすいようにするための戦略を立てることもできます。

于 2016-07-12T17:26:29.623 に答える