web-dev-qa-db-ja.com

現代のOO言語はC ++の配列ストアのパフォーマンスと競合できますか?

私が気づいたのは、私が少なくともある程度知っているすべての最新のOOプログラミング言語(基本的にはJava、C#、およびDのみ))が共変配列を許可することです。つまり、文字列配列はオブジェクトですアレイ:

Object[] arr = new String[2];   // Java, C# and D allow this

共変配列は静的型システムの穴です。コンパイル時に検出できないタイプエラーが発生する可能性があるため、配列へのすべての書き込みは実行時にチェックする必要があります。

arr[0] = "hello";        // ok
arr[1] = new Object();   // ArrayStoreException

配列ストアをたくさん行うと、これはひどいパフォーマンスヒットのようです。

C++には共変配列がないため、このようなランタイムチェックを実行する必要はありません。つまり、パフォーマンスが低下することはありません。

必要なランタイムチェックの数を減らすために行われる分析はありますか?たとえば、私が言う場合:

arr[1] = arr[0];

店が失敗する可能性はないと主張することができます。他にも考えられなかった最適化が他にもたくさんあると思います。

最近のコンパイラは実際にこのような種類の最適化を行うのですか、または、たとえば、Quicksortは常にO(n log n)の不要なランタイムチェックを行うという事実に耐えなければなりませんか?

現代のOO言語は、共変配列をサポートすることによって生じるオーバーヘッドを回避できますか?

40
fredoverflow

Dには共変配列はありません。最新リリース( dmd 2.057 )より前のバージョンでは許可されていましたが、 bug は修正されています。

Dの配列は、ポインタと長さを持つ構造体です。

struct A(T)
{
    T* ptr;
    size_t length;
}

境界のチェックは配列にインデックスを付けるときに通常行われますが、-releaseでコンパイルすると削除されます。したがって、リリースモードでは、C/C++の配列とDの配列に実際のパフォーマンスの違いはありません。

33

はい、重要な最適化の1つは次のとおりです。

sealed class Foo
{
}

C#では、このクラスをどのタイプのスーパータイプにすることもできないため、Fooタイプの配列のチェックを回避できます。

そして2番目の質問では、F#の共変配列は許可されていません(ただし、実行時の最適化で不要であることが判明しない限り、チェックはCLRに残ると思います)

let a = [| "st" |]
let b : System.Object[] = a // Fails

https://stackoverflow.com/questions/7339013/array-covariance-in-f

やや関連する問題は、配列の境界チェックです。これは、CLRで行われた最適化についての興味深い(しかし古い)読み取りかもしれません(共分散も1箇所で言及されています): http://blogs.msdn.com/b/clrcodegeneration/archive/2009/08/13 /array-bounds-check-elimination-in-the-clr.aspx

21
Lasse Espeholt

Javaの答え:

実際にコードをベンチマークしていないと思いますか?一般に、Javaのすべての動的キャストの90%は無料です。JITがそれらを排除できるため(クイックソートがこの良い例になるはずです)、残りは1つですld/cmp/brsequenceは完全に予測可能です(そうでない場合は、コードが動的キャスト例外をすべてスローしているのはなぜですか?)。

実際の比較よりもはるかに早くロードを実行します。ブランチはすべてのケースの99.9999%(統計を構成しました!)で正しく予測されるため、パイプラインを停止させません(メモリがロードでヒットしない場合)よくわかりませんが、とにかく負荷が必要です)。したがって、JITがチェックをまったく回避できない場合、コストは1クロックサイクルです。

オーバーヘッドはありますか?確かに、私はあなたがそれを気づくことはないだろうと思う。


私の答えを支援するために、これを参照してください Dr。Cliff Click blogpost 議論Java対Cのパフォーマンス。

13
Voo

Dは共変配列を許可しません。

void main()
{
    class Foo {}
    Object[] a = new Foo[10];
}  

/* Error: cannot implicitly convert expression (new Foo[](10LU)) of type Foo[]
to Object[] */

あなたが言うように、型システムにこれを可能にする穴があります。

このバグ は、12月13日にリリースされた最新のDMDでのみ修正されたため、この間違いは許されます。

Dでの配列アクセスは、CまたはC++と同じくらい高速です。

10
Peter Alexander

私が安価なラップトップで行ったテストから、int[]Integer[]の使用の違いは約1.0 nsです。違いは、型の追加チェックによる可能性があります。

通常、オブジェクトは、すべてのnsカウントではない場合に、より高いレベルのロジックにのみ使用されます。すべてのnsを保存する必要がある場合は、オブジェクトなどのより高レベルの構成を使用しないようにします。実際のプログラムでは、割り当てだけで非常に小さな要素になる可能性があります。例えば同じマシンで新しいオブジェクトを作成するのは5 nsです。

文字列などの複雑なオブジェクトを使用している場合は特に、compareToの呼び出しははるかに高価になる可能性があります。

5
Peter Lawrey

あなたは他の現代のOO=言語について質問しましたか?まあ、Delphiはstringをオブジェクトではなくプリミティブにすることでこの問題を完全に回避します。したがって、文字列の配列は正確に文字列のみであり、それらに対する操作はネイティブコードと同じくらい高速で、型チェックのオーバーヘッドもありません。

ただし、文字列配列はあまり使用されません。 DelphiプログラマはTStringListクラスを支持する傾向があります。最小限のオーバーヘッドで、クラスがスイスアーミーナイフと比較されるほど多くの状況で役立つ一連の文字列グループメソッドを提供します。したがって、文字列配列の代わりに文字列リストオブジェクトを使用するのは慣用的です。

Delphiでは、C++のように、ここで説明する種類のシステムホールの種類を防ぐために配列が共変ではないため、他のオブジェクト全般については問題は発生しません。

2
Mason Wheeler

または、たとえば、Quicksortが常にO(n log n)の不要なランタイムチェックを行うという事実に耐えなければなりませんか?

CPUのパフォーマンスは単調ではありません。つまり、長いプログラムは短いプログラムよりも高速になる可能性があります(これはCPUに依存し、一般的なx86およびAMD64アーキテクチャに当てはまります)。したがって、配列の境界チェックを行うプログラムは、これらの境界チェックを削除することにより、以前のプログラムから推定されたプログラムよりも実際に高速である可能性があります。

この動作の理由は、バウンドチェックがメモリ内のコードの配置を変更し、キャッシュヒットの頻度などを変更するためです。

だから、はい、Quicksortは常にO(n log n)の偽のチェックを行い、プロファイリング後に最適化するという事実を受け入れます。

1
user40989

Scalaは、OO共変ではなく不変の配列を持つ言語です。JVMをターゲットにしているため、パフォーマンスは向上しませんが、両方に共通する機能の誤りを回避しますJavaおよびC#は、下位互換性の理由で型の安全性を危うくします。

1
Pillsy