12

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

それを自分の目で確認したかったので、Openjdk 7 のソース コードをダウンロードして閲覧を開始しました。OpenJDK C++ ソース コードの System.arraycopy の実装を次の場所で見つけましたopenjdx/hotspot/src/share/vm/oops/objArrayKlass.cpp

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

要素が型チェックを必要としない場合 (たとえば、プリミティブ データ型配列の場合)、Copy::conjoin_oops_atomic が呼び出されます。

Copy::conjoint_oops_atomic関数は「copy.hpp」にあります。

// 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);
}

OS/アーキテクチャに基づいて、コピー操作の実装が異なるため、現在はプラットフォームに依存しています。例として 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 値) を反復処理し、それらを 1 つずつ (一見) コピーします。配列内の要素を繰り返し処理することによって、ネイティブ レベルであってもコピーが行われる理由を誰かが説明できますか?

4

1 に答える 1

6

これは、基本的にデータ バスの幅と同じサイズである古いハードウェア アーキテクチャに最も密接にマッピングjintされるためです。intWORD

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

ハードウェア アーキテクチャを紹介するときは、単純なものを紹介する必要があります。現代のコードはより多くのことを行うため、非効率に見えるコードが実際には非効率であると想定することはできません。たとえば、if ステートメントの条件を評価するためにメモリ ルックアップが実行される場合、多くの場合、ルックアップの実行中に if ステートメントの両方の分岐が実行され、データが評価可能になった後に処理の "false" 分岐が破棄されます。条件。効率的になりたい場合は、プロファイリングを行ってから、プロファイリングされたデータに基づいて行動する必要があります。

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

おそらく、MMX ルーチンを含めることができたかもしれませんが、SUN が最新のハードウェアで心配するほどのパフォーマンス向上ではないと考えていたことはわかりませんでした。

于 2012-06-26T15:22:14.277 に答える