web-dev-qa-db-ja.com

System.arraycopy(...)の時間計算量?

System.arraycopy(Object src, int srcPos, Object dest, int destPos, int length)はネイティブメソッドです。

この方法の時間計算量はどれくらいですか?

32
Kowser

これを行うには、配列内のすべての要素を通過する必要があります。配列は、初期化時にサイズを指定する必要がある一意のデータ構造です。順序は、ソース配列のサイズ、またはBig Oの用語ではそのO(長さ)になります。

実際、これはArrayListの内部で発生します。 ArrayListは配列をラップします。 ArrayListは動的に成長するコレクションのように見えますが、内部的には展開する必要があるときにarrycopyを実行します。

20
bragboy

私はいくつかの調査を行い、後でテストコードを書くことにしました。これが私が持っているものです。

私のテストコードを以下に示します。

import org.junit.Test;

public class ArrayCopyTest {

  @Test
  public void testCopy() {
    for (int count = 0; count < 3; count++) {
      int size = 0x00ffffff;
      long start, end;
      Integer[] integers = new Integer[size];
      Integer[] loopCopy = new Integer[size];
      Integer[] systemCopy = new Integer[size];

      for (int i = 0; i < size; i++) {
        integers[i] = i;
      }

      start = System.currentTimeMillis();
      for (int i = 0; i < size; i++) {
        loopCopy[i] = integers[i];
      }
      end = System.currentTimeMillis();
      System.out.println("for loop: " + (end - start));

      start = System.currentTimeMillis();
      System.arraycopy(integers, 0, systemCopy, 0, size);
      end = System.currentTimeMillis();
      System.out.println("System.arrayCopy: " + (end - start));
    }
  }

}

以下のような結果が得られます。

for loop: 47
System.arrayCopy: 24

for loop: 31
System.arrayCopy: 22

for loop: 36
System.arrayCopy: 22

だから、ブラッグボーイは正しいです。

4
Kowser

OpenJDK 8(openjdk-8-src-b132-03_mar_2014)の関連するソースコードを次に示します。 Javaネイティブメソッドのソースコード (注:手順がわかりにくいため、ソースで関連する識別子を検索しただけです)の助けを借りて見つけました。 フォード大尉のコメント は正しいと思います。つまり、各要素を反復する必要がない(多くの)場合があります。各要素を反復しないことは、必ずしも必ずしもO(1)を意味するのではなく、単に「より速い」ことを意味することに注意してください。 Ithinkとはいえ、xが配列内の項目の数でなくても、配列のコピーは基本的にO(x)でなければならない。つまり、どのように実行しても、配列内の要素が多いほどコピーのコストが高くなり、非常に大きな配列がある場合は、直線的に長い時間がかかります。警告:これが実行中のJava;の実際のソースコードであるかどうかは確かにわかりません。これがOpenJDK8ソースで見つけたonly実装であるということだけです。Ithinkこれはクロスプラットフォームの実装ですが、間違っている可能性があります-このコードの作成方法がわかりません。参照: OracleJDKとOpenJDKの違い 。以下は、/ openjdk/hotspot/src/share/vm/oops /objArrayKlass.cppからのものです。

// Either oop or narrowOop depending on UseCompressedOops.
template <class T> void ObjArrayKlass::do_copy(arrayOop s, T* src,
                               arrayOop d, T* dst, int length, TRAPS) {

  BarrierSet* bs = Universe::heap()->barrier_set();
  // For performance reasons, we assume we are that the write barrier we
  // are using has optimized modes for arrays of references.  At least one
  // of the asserts below will fail if this is not the case.
  assert(bs->has_write_ref_array_opt(), "Barrier set must have ref array opt");
  assert(bs->has_write_ref_array_pre_opt(), "For pre-barrier as well.");

  if (s == d) {
    // since source and destination are equal we do not need conversion checks.
    assert(length > 0, "sanity check");
    bs->write_ref_array_pre(dst, length);
    Copy::conjoint_oops_atomic(src, dst, length);
  } else {
    // We have to make sure all elements conform to the destination array
    Klass* bound = ObjArrayKlass::cast(d->klass())->element_klass();
    Klass* stype = ObjArrayKlass::cast(s->klass())->element_klass();
    if (stype == bound || 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
      // note: don't use obj_at_put below because it includes a redundant store check
      T* from = src;
      T* end = from + length;
      for (T* p = dst; from < end; from++, p++) {
        // XXX this is going to be slow.
        T element = *from;
        // even slower now
        bool element_is_null = oopDesc::is_null(element);
        oop new_val = element_is_null ? oop(NULL)
                                      : oopDesc::decode_heap_oop_not_null(element);
        if (element_is_null ||
            (new_val->klass())->is_subtype_of(bound)) {
          bs->write_ref_field_pre(p, new_val);
          *p = *from;
        } else {
          // We must do a barrier to cover the partial copy.
          const size_t pd = pointer_delta(p, dst, (size_t)heapOopSize);
          // pointer delta is scaled to number of elements (length field in
          // objArrayOop) which we assume is 32 bit.
          assert(pd == (size_t)(int)pd, "length field overflow");
          bs->write_ref_array((HeapWord*)dst, pd);
          THROW(vmSymbols::Java_lang_ArrayStoreException());
          return;
        }
      }
    }
  }
  bs->write_ref_array((HeapWord*)dst, length);
}
2
Hawkeye Parker

別の質問に関連するコメントを要約するだけです(この質問の重複としてフラグが付けられています)。

確かに、それは他のすべてのエントリで新しい配列に追加しているだけですか?これはO(n)ここで、nは追加される値の数です。

ブラッグボーイの答えはもちろん同意しますが、確実な答えを得る唯一の方法は、取得するソースコードを見つけることだと思っていました正規の答え、しかしそれは不可能です。 System.arraycopy();の宣言は次のとおりです

_public static native void arraycopy(Object src, int src_position,  
                                    Object dst, int dst_position,  
                                    int length);
_

これは、オペレーティングシステムの言語で記述されたnativeです。つまり、arraycopy()の実装はプラットフォームに依存します。

したがって、結論として、おそらくO(n)ですが、そうではないかもしれません。

1
Rudi Kershaw

Kowserの答えが彼自身の質問にどのように答えているのかわかりません。アルゴリズムの時間計算量を確認しようと思いました。次のように、さまざまなサイズの入力の実行時間を比較する必要があります。

import org.junit.Test;

public class ArrayCopyTest {

  @Test
  public void testCopy() {
    int size = 5000000;
    for (int count = 0; count < 5; count++) {
      size = size * 2;
      long start, end;
      Integer[] integers = new Integer[size];
      Integer[] systemCopy = new Integer[size];

      start = System.currentTimeMillis();
      System.arraycopy(integers, 0, systemCopy, 0, size);
      end = System.currentTimeMillis();
      System.out.println(end - start);
    }
  }

}

出力:

10
22
42
87
147
0
MaxNevermind