コーディングガイドラインは、通常のiterator
に比べて少し速いため、const_iterator
を優先します。 const_iterator
を使用すると、コンパイラがコードを最適化するようです。
これは本当に正しいですか?はいの場合、const_iterator
を高速化する内部で実際に何が起こりますか?.
編集:const_iterator
とiterator
をチェックするための小さなテストを作成し、さまざまな結果を見つけました。
10,000個のオブジェクトを反復する場合、const_terator
は数ミリ秒(約16ミリ秒)少なくなりました。しかし常にではない。両方が等しい反復がありました。
他に何もないとしても、const_iterator
readsより良い、コードを読んでいる人に「私はこのコンテナを繰り返し処理しているだけで、含まれているオブジェクトをいじっていない」と伝えるからです。
これは大きな勝利です。パフォーマンスの違いを気にしないでください。
私たちが使用するガイドラインは次のとおりです。
常に非constよりもconstを優先します
Constオブジェクトを使用する傾向がある場合は、取得したオブジェクトに対して一定の操作のみを使用することに慣れています。これは、可能な限りconst_iteratorを使用することと同じです。
Constnessにはviralプロパティがあります。使用できるようになると、すべてのコードに伝播されます。変化しないメソッドは一定になり、属性に対して一定の操作のみを使用し、一定の参照を渡す必要があり、それ自体が一定の操作のみを強制します...
私にとって、非定数イテレータ(あるとしても)よりも定数イテレータを使用することのパフォーマンス上の利点は、コード自体の改善ほど重要ではありません。操作は、変化しないことを意味します(設計されています)are定数。
それらは重要なコンテナ/イテレータ用です。あなたの習慣をまっすぐにしてください、そしてそれが重要であるときあなたはパフォーマンスを失うことはありません。
また、何があっても、const_iteratorを好む理由はいくつかあります。
begin()
を使用してデータにダーティ(OpenSGなど)のフラグを付け、同期時に他のスレッド/ネットワーク経由で送信するため、パフォーマンスに実際の影響があります。上記の最後のポイントの例として、Qtのqmap.hからの抜粋を次に示します。
_inline iterator begin() { detach(); return iterator(e->forward[0]); }
inline const_iterator begin() const { return const_iterator(e->forward[0]); }
_
Iteratorとconst_iteratorが実質的に同等であっても(const
を除く)、それを使用するオブジェクトが2つ以上ある場合、detach()
はデータの新しいコピーを作成します。 _const_iterator
_を使用して示すデータを読み取るだけの場合、これはまったく役に立ちません。
もちろん、他の方向にもデータポイントがあります。
なぜそうなるのかわかりません-constnessはコンパイル時のチェックです。しかし、明白な答えはテストを書くことです。
編集:これが私のテストです-それは私のマシンで同じタイミングを与えます:
#include <vector>
#include <iostream>
#include <ctime>
using namespace std;;
int main() {
vector <int> v;
const int BIG = 10000000;
for ( int i = 0; i < BIG; i++ ) {
v.Push_back( i );
}
cout << "begin\n";
int n = 0;
time_t now = time(0);
for ( int a = 0; a < 10; a++ ) {
for( vector <int>::iterator it = v.begin(); it != v.end(); ++it ) {
n += *it;
}
}
cout << time(0) - now << "\n";
now = time(0);
for ( int a = 0; a < 10; a++ ) {
for( vector <int>::const_iterator cit = v.begin(); cit != v.end(); ++cit ) {
n += *cit;
}
}
cout << time(0) - now << "\n";;
return n != 0;
}
使用するコンテナと実装によって異なります。
はい、const_iterator
mayのパフォーマンスが向上します。
一部のコンテナーでは、constイテレーターと可変イテレーターの実装が異なる場合があります。私が考えることができる最初の例は SGIのSTLロープコンテナ です。可変イテレータには、更新をサポートするために親ロープへの追加のポインタがあります。これは、参照カウントの更新と親ロープへのポインタのメモリに無駄な追加のリソースがあることを意味します。 ここに実装ノート を参照してください。
ただし、他の人が言ったように、コンパイラーはそれ自体でconst
を使用して最適化を行うことはできません。 const
は、参照されているオブジェクトが不変であると言うのではなく、読み取り専用アクセスを許可するだけです。 std::vector
のようなコンテナの場合、そのイテレータは通常単純なポインタとして実装されますが、違いはありません。
私たちのコーディングガイドラインはconst_iteratorを好むと言っています
これを見てください Scott Meyersによる記事はこちら 。彼は、const_iteratorよりもイテレータを好む理由を説明しています。
Constnessはコンパイル時のチェックであるため、これらは同一である必要があります。
癖がないことを証明するために、私はanonのコードを取得し、clock_gettime
を使用するように変更し、キャッシュバイアスを回避するために外部ループを追加し、何度も実行しました。結果は驚くほど一貫性がありませんでした-20%上下(アイドルボックスは利用できません)-しかし最小時間iterator
とconst_iterator
の両方で実質的に同一。
次に、コンパイラ(GCC 4.5.2 -O3)にアセンブリ出力を生成させ、2つのループを視覚的に比較しました:同一(ただし、2つのレジスタロードの順序は逆)
iterator
ループ
call clock_gettime
movl 56(%esp), %esi
movl $10, %ecx
movl 60(%esp), %edx
.p2align 4,,7
.p2align 3
.L35:
cmpl %esi, %edx
je .L33
movl %esi, %eax .p2align 4,,7
.p2align 3
.L34:
addl (%eax), %ebx
addl $4, %eax
cmpl %eax, %edx
jne .L34
.L33:
subl $1, %ecx
jne .L35
leal 68(%esp), %edx
movl %edx, 4(%esp)
leal 56(%esp), %esi
movl $1, (%esp)
const_iterator
ループ:
movl 60(%esp), %edx
movl $10, %ecx
movl 56(%esp), %esi
.p2align 4,,7
.p2align 3
.L38:
cmpl %esi, %edx
je .L36
movl %esi, %eax
.p2align 4,,7
.p2align 3
.L37:
addl (%eax), %ebx
addl $4, %eax
cmpl %eax, %edx
jne .L37
.L36:
subl $1, %ecx
jne .L38
leal 68(%esp), %edx
movl %edx, 4(%esp)
leal 56(%esp), %esi
movl $1, (%esp)
これらのいずれかをベンチマークするときは、適切な最適化レベルを使用するようにしてください。「-O0」と「-O」などを使用すると、タイミングが大きく異なります。
アクセス制限(パブリック、プロテクト、プライベート)のような「const-ness」は、最適化を支援するよりもプログラマーに利益をもたらします。
コンパイラーは、多くの理由(const_cast、可変データメンバー、ポインター/参照エイリアシングなど)のために、constが関係する多くの状況に対して実際に最適化することはできません。ただし、ここで最も重要な理由は、const_iteratorが参照するデータの変更を許可していないからといって、そのデータを他の方法で変更できないという意味ではないということです。また、コンパイラがデータが読み取り専用であると判断できない場合、非定数イテレータの場合よりも実際に最適化することはできません。
詳細と例は次の場所にあります: http://www.gotw.ca/gotw/081.htm
container<T>::const_iterator::operator*
はconst T&
の代わりにT&
を返すため、コンパイラーはconstオブジェクトに対して通常の最適化を行うことができます。
私の経験では、コンパイラは定数イテレータを使用するときに測定可能な最適化を行いません。 「それは可能である」という記述は真実であり、参照は標準のポインタとして定義されていませんが。
ただし、同じオブジェクトへの参照を2つ持つことができるため、1つはconst、もう1つはnon-constにすることができます。次に、 ポインタの制限に関するこのスレッド の答えを推測します。適用:コンパイラは、オブジェクトが別のスレッドによって変更されたか、割り込み処理コードによって変更されたかを知ることができません。