2012-06-26 3 views
8

JVMがchar []に基づいてStringの作成を実装する方法に関する質問に続いて、char []が新しい文字列の内部にコピーされるときに反復が行われないことを述べましたSystem.arraycopyは最終的に呼び出されるため、memcpyなどの関数を使用してネイティブ実装固有のレベル(the original question)で目的のメモリをコピーします。System.arraycopyのOpenJDKの実装

自分自身で確認したかったので、Openjdk 7のソースコードをダウンロードして閲覧し始めました。 Iはopenjdx/hotspot/src/share/vm/oops/objArrayKlass.cppに、OpenJDKのC++ソースコードにSystem.arraycopyの実装を発見した:

if (stype == bound || Klass::cast(stype)->is_subtype_of(bound)) { 
    // elements are guaranteed to be subtypes, so no check necessary 
    bs->write_ref_array_pre(dst, length); 
    Copy::conjoint_oops_atomic(src, dst, length); 
} else { 
    // slow case: need individual subtype checks 

要素がないタイプチェックを必要としない場合(すなわち、例えば、プリミティブデータ型アレイの場合だ)、コピー: :conjoin_oops_atomicが呼び出されます。

Copy::conjoint_oops_atomic機能は「copy.hpp」に格納されています。コピー操作は、OS /アーキテクチャに基づいて、異なる実装を持っているよう

// overloaded for UseCompressedOops 
static void conjoint_oops_atomic(narrowOop* from, narrowOop* to, size_t count) { 
    assert(sizeof(narrowOop) == sizeof(jint), "this cast is wrong"); 
    assert_params_ok(from, to, LogBytesPerInt); 
    pd_conjoint_jints_atomic((jint*)from, (jint*)to, count); 
} 

は、今、私たちは、プラットフォームに依存しています。私は例としてWindowsと一緒に行くつもりです。 openjdk\hotspot\src\os_cpu\windows_x86\vm\copy_windows_x86.inline.hpp:私の驚きに

static void pd_conjoint_oops_atomic(oop* from, oop* to, size_t count) { 
// Do better than this: inline memmove body NEEDS CLEANUP 
if (from > to) { 
    while (count-- > 0) { 
    // Copy forwards 
    *to++ = *from++; 
    } 
} else { 
    from += count - 1; 
    to += count - 1; 
    while (count-- > 0) { 
    // Copy backwards 
    *to-- = *from--; 
    } 
} 
} 

そして...、それは彼らに(一見)一つ一つをコピーし、要素(OOP値)を反復処理します。誰かが、配列内の要素を反復することによって、コピーが元のレベルであってもなぜ行われたのか説明できますか?

答えて

4

jintは、データバスの幅と基本的に同じサイズの古いハードウェアアーキテクチャWORDに最も密接に対応するintに最も密接に対応しているためです。

今日のメモリアーキテクチャおよびCPU処理は、キャッシュミスの場合でも処理を試みるように設計されており、メモリ位置はブロックをプリフェッチする傾向があります。あなたが思っているように、あなたが見ているコードはパフォーマンスにおいて「悪い」ものではありません。ハードウェアはよりスマートです。実際にプロファイルを作成しない場合、「スマートな」フェッチルーチンは実際に何も追加しない(または処理を遅くすることさえあります)。

ハードウェアアーキテクチャに導入された場合、簡単なものに導入する必要があります。現代のものはもっと多くのことをしているので、非効率的なコードは実際には非効率的であると考えることはできません。たとえば、メモリ参照がif文の条件を評価するために行われると、検索が実行されている間にif文の両方の分岐が実行され、評価のためにデータが利用可能になった後に処理の「偽」分岐が破棄されます条件。効率を上げたい場合は、プロファイリングを行い、プロファイリングされたデータを処理する必要があります。

JVMオペコードセクションのブランチを見てください。オペコードを処理したコードに3つの異なる方法でジャンプする(一度に)ことをサポートするのは、ifdefマクロの不思議さです。これは、Windows、Linux、およびSolarisのさまざまなアーキテクチャで、3つの異なる方法が実際に意味のあるパフォーマンスの違いをもたらしたためです。

MMXルーチンが含まれている可能性がありますが、現代のハードウェアでSUNがそれを心配するのに十分だとは思っていませんでした。

+0

うわー、ありがとう!はじめてOpenJDKの実装を見るのはちょっと混乱していたので、何か間違っていると思っていました。 :Pだから、この最適化はどう考えますか?私はいくつかのテストとシステムをやった。arraycopyは、通常のJavaの方法よりも10000のintをコピーするのが2倍高速です。 C++では、さまざまなコンパイラの最適化によって影響を受ける可能性がありますが、似たような処理が目立って高速です。 –

+0

C++コピーに別のスレッドで実行されているガベージコレクタがありません。たとえあなたがゴミを発生させないとしても、コレクターは何もする必要がないことを確認するために数サイクルを盗む必要があります。コンパイラーがarraycopyループをアンロールしているかどうか、またはハードウェアがアレイのブロック全体をキャッシュにプリフェッチしているかどうかはわかりません。実際、マイクロコードの最適化では、私の知識の深さを超えています。それがプロファイリングが重要な理由です。最適化が価値があることが証明されたテストです。 –

関連する問題