私が気づいたのは、私が少なくともある程度知っているすべての最新の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言語は、共変配列をサポートすることによって生じるオーバーヘッドを回避できますか?
はい、重要な最適化の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
Javaの答え:
実際にコードをベンチマークしていないと思いますか?一般に、Javaのすべての動的キャストの90%は無料です。JITがそれらを排除できるため(クイックソートがこの良い例になるはずです)、残りは1つですld/cmp/br
sequenceは完全に予測可能です(そうでない場合は、コードが動的キャスト例外をすべてスローしているのはなぜですか?)。
実際の比較よりもはるかに早くロードを実行します。ブランチはすべてのケースの99.9999%(統計を構成しました!)で正しく予測されるため、パイプラインを停止させません(メモリがロードでヒットしない場合)よくわかりませんが、とにかく負荷が必要です)。したがって、JITがチェックをまったく回避できない場合、コストは1クロックサイクルです。
オーバーヘッドはありますか?確かに、私はあなたがそれを気づくことはないだろうと思う。
私の答えを支援するために、これを参照してください Dr。Cliff Click blogpost 議論Java対Cのパフォーマンス。
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++と同じくらい高速です。
私が安価なラップトップで行ったテストから、int[]
とInteger[]
の使用の違いは約1.0 nsです。違いは、型の追加チェックによる可能性があります。
通常、オブジェクトは、すべてのnsカウントではない場合に、より高いレベルのロジックにのみ使用されます。すべてのnsを保存する必要がある場合は、オブジェクトなどのより高レベルの構成を使用しないようにします。実際のプログラムでは、割り当てだけで非常に小さな要素になる可能性があります。例えば同じマシンで新しいオブジェクトを作成するのは5 nsです。
文字列などの複雑なオブジェクトを使用している場合は特に、compareToの呼び出しははるかに高価になる可能性があります。
あなたは他の現代のOO=言語について質問しましたか?まあ、Delphiはstring
をオブジェクトではなくプリミティブにすることでこの問題を完全に回避します。したがって、文字列の配列は正確に文字列のみであり、それらに対する操作はネイティブコードと同じくらい高速で、型チェックのオーバーヘッドもありません。
ただし、文字列配列はあまり使用されません。 DelphiプログラマはTStringList
クラスを支持する傾向があります。最小限のオーバーヘッドで、クラスがスイスアーミーナイフと比較されるほど多くの状況で役立つ一連の文字列グループメソッドを提供します。したがって、文字列配列の代わりに文字列リストオブジェクトを使用するのは慣用的です。
Delphiでは、C++のように、ここで説明する種類のシステムホールの種類を防ぐために配列が共変ではないため、他のオブジェクト全般については問題は発生しません。
または、たとえば、Quicksortが常にO(n log n)の不要なランタイムチェックを行うという事実に耐えなければなりませんか?
CPUのパフォーマンスは単調ではありません。つまり、長いプログラムは短いプログラムよりも高速になる可能性があります(これはCPUに依存し、一般的なx86およびAMD64アーキテクチャに当てはまります)。したがって、配列の境界チェックを行うプログラムは、これらの境界チェックを削除することにより、以前のプログラムから推定されたプログラムよりも実際に高速である可能性があります。
この動作の理由は、バウンドチェックがメモリ内のコードの配置を変更し、キャッシュヒットの頻度などを変更するためです。
だから、はい、Quicksortは常にO(n log n)の偽のチェックを行い、プロファイリング後に最適化するという事実を受け入れます。
Scalaは、OO共変ではなく不変の配列を持つ言語です。JVMをターゲットにしているため、パフォーマンスは向上しませんが、両方に共通する機能の誤りを回避しますJavaおよびC#は、下位互換性の理由で型の安全性を危うくします。