最新のオブジェクト指向言語はC ++の配列ストアのパフォーマンスと競合できますか?


40

少なくともある程度慣れ親しんでいる最新のオブジェクト指向プログラミング言語(基本的には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];

ストアが失敗することはないと主張することができます。私が考えていない他の多くの可能な最適化があると確信しています。

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

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


7
C ++がサポートしていない機能で、C ++が他の言語よりも高速であることを提案している理由がわかりません。

17
@eco:共変配列をサポートしていないため、配列アクセスではC ++の方が高速です。フレッドは、「最新のOO言語」が共変配列のオーバーヘッドを排除してC ++の速度に近づけることが可能かどうかを知りたいと考えています。
Mooingダック

13
「コンパイルするが、実行時に例外をスローするコードは悪いニュースです」

4
@Jesseそして何百万人もの人々が、動的言語で信頼性が高くスケーラブルなコードを書きます。Imo:コードのテストケースを作成しない場合、コンパイラエラーがあるかどうかは気にしませんが、それを信用するつもりはありません。
Voo

7
@Jesseそして、実行時以外に例外を期待するのはいつですか?問題は、実行時に例外をスローするコードではありません-それが理にかなっている多くの場合があります-問題は、コンパイラによって静的にキャッチされず、代わりに例外が発生する間違ったことが保証されているコードですランタイム。
ジョナサンMデイビス

回答:


33

Dに共変配列がありません。最新のリリース(dmd 2.057)の前にそれらを許可しましたが、そのバグ修正されました。

Dの配列は、事実上、ポインターと長さを持つ単なる構造体です。

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

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


表示されない-d-programming-language.org/arrays.htmlは、「静的配列T[dim]は暗黙的に次のいずれかに変換できる:... U[]...動的配列T[]は暗黙的に次のいずれかに変換できる:」と述べていますU[]。 ..はのU基本クラスですT。」
ベンフォークト

2
@BenVoigtその後、オンラインドキュメントを更新する必要があります。残念ながら、それらは常に100%最新ではありません。変換はで機能しますconst U[]。なぜなら、間違った型を配列の要素に割り当てることはできないからです。しかし、可変である限り、T[]間違いなく変換しませ。以前のように共変配列を許可することは深刻な設計上の欠陥でしたが、現在修正されています。U[]U[]
ジョナサンMデイビス

@JonathanMDavis:共変配列は厄介ですが、同じ配列から読み取られた配列要素にのみ書き込むコードの特定のシナリオではうまく機能します。Dは、どのようにして任意の型の配列をソートできるメソッドを書くことを許可しますか?
supercat

@supercat任意の型をソートする関数を作成する場合は、テンプレート化します。例えばdlang.org/phobos/std_algorithm.html#sort
ジョナサン・M・デービス

21

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

sealed class Foo
{
}

C#では、このクラスをどの型のスーパータイプにすることもできないため、typeの配列のチェックを回避できます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


2
また、Scalaはこの構造を防ぎます:val a = Array("st"); val b: Array[Any] = a違法です。(ただし、Scalaの配列は...特別な魔法です...使用されている基礎JVMが原因です。)
pst

13

Javaの答え:

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

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

いくつかのオーバーヘッド?はい


私の答えを支援するために、JavaとCのパフォーマンスについて説明しているこのCliff Click博士のブログ投稿を参照してください。


10

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 ++と同じくらい高速です。


d-programming-language.org/arrays.htmlによると、「静的配列T [dim]は、次のいずれかに暗黙的に変換できます。... U [] ...動的配列T []は、暗黙的に変換できます。 U [] ... UはTの基本クラスです。」
ベンフォークト

1
@BenVoigt:古いドキュメント。
BCS

1
@BenVoigt:ドキュメントを更新するためのプルリクエストを作成しました。うまくいけば、これはすぐに解決されるでしょう。それを指摘してくれてありがとう。
ピーターアレクサンダー

5

テストから私は、安価なノートパソコンで使用する場合の違いを行っているint[]し、Integer[]1.0ナノ秒程度です。違いは、タイプの追加チェックによる可能性があります。

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

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


2

他の現代的なオブジェクト指向言語について尋ねましたか?Delphiはstring、オブジェクトではなくプリミティブであることで、この問題を完全に回避しています。したがって、文字列の配列は文字列の配列であり、他の何もありません。また、文字列に対する操作は、ネイティブコードが可能な限り高速で、型チェックのオーバーヘッドがありません。

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

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


1

または、たとえば、クイックソートは常にO(n log n)の不要なランタイムチェックを行うという事実に対応する必要がありますか?

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

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

はい、そうです、Quicksortは常にO(n log n)のスプリアスチェックを行い、プロファイリング後に最適化するという事実と共に生きます。


1

Scalaは、共変ではなく不変の配列を持つオブジェクト指向言語です。JVMを対象としているため、パフォーマンスは向上しませんが、下位互換性のために型安全性を損なうJavaとC#の両方に共通する誤機能を回避します。

弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.