40

System.arraycopy(Object src, int srcPos, Object dest, int destPos, int length) is a native method.

What is the time complexity for this method?

OmG
  • 18,337
  • 10
  • 57
  • 90
Kowser
  • 8,123
  • 7
  • 40
  • 63

5 Answers5

30

It will have to go through all the elements in the array to do this. Array is a unique data structure where you have to specify a size when you initialize it. Order would be the source array's size or in Big O terms its O(length).

Infact this happens internally in an ArrayList. ArrayList wraps an array. Although ArrayList looks like a dynamically growing collection, internally it does an arrycopy when it has to expand.

bragboy
  • 34,892
  • 30
  • 114
  • 171
  • depends on what you pass in for `length` - why would this be O(n)? – BrokenGlass Aug 23 '11 at 18:14
  • 5
    O(n) is close enough, but actually I think it will just be O(length), i.e. the length to be copied, not the length of the source array. – Robert Harvey Aug 23 '11 at 18:15
  • Just wondering if there is any special method call, which can reduce this time complexity. – Kowser Aug 23 '11 at 18:16
  • @BrokenGlass and Robert : Thanks. Corrected – bragboy Aug 23 '11 at 18:16
  • Be aware that `System.arraycopy` will try to cast from one type to another in the case of arrays of differing types. This could be considered O(n log m) for int to string, for instance, where m is the value size. In practice, however, this is Java, so it is O(n). – Alex Churchill Aug 23 '11 at 18:17
  • 5
    @alex c - can you show an example of a conversion from int to string? I don't think arraycopy does any conversion, not according the documentation: `Otherwise, if any of the following is true, an ArrayStoreException is thrown ... °The src argument refers to an array with a primitive component type and the dest argument refers to an array with a reference component type °...` – user85421 Aug 23 '11 at 18:38
  • @carlos: agreed about data conversion. – Kowser Aug 23 '11 at 18:43
  • @Carlos I meant `Integer` not `int`, which arraycopy will try to convert by assignment (it will throw exceptions only if they differ in primitive type, but if they are both objects...). But come to think of it, I think that would throw an error on type conversion; I was convoluting Java object assignment with C++ object assignment. Good catch. – Alex Churchill Aug 23 '11 at 18:48
  • @Kowser: Much appreciate your insight !! – bragboy Aug 25 '11 at 13:22
  • 9
    The response is not correct ! The system.arrayCopy is a native method and it can be implemented with a single memcpy / memmove – jdramaix Jan 15 '14 at 22:53
  • @jdramaix will be great to have more insight – Kowser Aug 31 '15 at 23:28
  • 3
    It doesn't have to iterate through all the elements for all data types. For many data types it is able to do block copies, which can be a lot faster. So long as the array types are the same, it shouldn't need to do any type checking. Using the same array as the source and destination will slow things down a bit (depending on platform, maybe a lot!), but in general System.arraycopy is frequently better and rarely worse than iterating. – Captain Ford Sep 27 '16 at 16:15
  • Captain Ford's comment seems meaningful to me. I added [an answer below](http://stackoverflow.com/a/40008713/99717), which includes some code to back it up. – Hawkeye Parker Oct 12 '16 at 21:34
  • 4
    @jdramaix: A single memcpy/memmove still takes time proportional to the amount of copying done, and it's not always a single memcpy/memmove (due to type checking and conversion). – user2357112 Apr 01 '18 at 20:38
5

Here's some relevant source code from OpenJDK 8 (openjdk-8-src-b132-03_mar_2014). I found it with help from Java native method source code (note: instructions there are confusing; I just searched the source for relevant identifiers). I think Captain Ford's comment is correct; i.e., there are (many) cases where iterating each element isn't necessary. Note that not iterating each element doesn't necessarily mean O(1), it just means "faster". I think that, regardless, an array copy must be fundamentally O(x), even if x isn't the number of items in the array; i.e., however you do it, copying gets more expensive with more elements in the array, and if you have a Very Big array, it will take a linearly Long Time. Caveat: I don't know for certain that this is the actual source code for the Java you are running; only that this is the only implementation I could find in the OpenJDK 8 source. I think that this is a cross-platform implementation, but I might be wrong -- I definitely haven't figured out how to build this code. See also: Differences between Oracle JDK and Open JDK. The following comes from: /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);
}
Community
  • 1
  • 1
Hawkeye Parker
  • 7,817
  • 7
  • 44
  • 47
5

I did some investigation and later decided to write a test code, here is what I have.

My testing code is given below:

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

}

It produces result shown below

for loop: 47
System.arrayCopy: 24

for loop: 31
System.arrayCopy: 22

for loop: 36
System.arrayCopy: 22

So, Bragboy is correct.

Kowser
  • 8,123
  • 7
  • 40
  • 63
  • 1
    +1 - What operating system was that test run on? Because as detailed in my recent answer, the implementation of `arraycopy()` may well be platform dependent. – Rudi Kershaw Aug 25 '14 at 21:43
  • The latest ubuntu OS at that time. I was using lenovo thinkpad i5 processor with 8GB ram. I can't remember exact detail of the OS and machine. – Kowser Aug 26 '14 at 22:18
  • Did you test it on warming java and big values? – Grigory Kislin Oct 08 '15 at 08:33
  • @GKislin No, just simple run. – Kowser Oct 08 '15 at 08:56
  • @Kowser but then this figures mean nothing. I've fond some other measure: https://faisalferoz.wordpress.com/2007/12/24/loop-vs-systemarraycopy/ but not sure in its correcteness also. – Grigory Kislin Oct 08 '15 at 14:42
  • @GKislin nice finding. I would like to see some average for multiple run. It is interesting to read the article too. It considered different array size, which is a good thing, and different array size took different time as we can see. Then what is the `O(?)` here? Obviously my performance analysis is not bullet proof ;-) and I am curious to know. Digging down to JDK source might be helpful. Any thoughts? – Kowser Oct 08 '15 at 16:45
2

Just to sum up relevant comments on another question (flagged as a duplicate of this one).

Surely, it's just adding to the new array with all the entries of the other? Which would be O(n) where n is the number of values to be added.

bragboy's answer agrees of course, but then I had thought that the only way to get a sure answer was to find the source code to get a canonical answer, but that isn't possible. Here is the declaration for System.arraycopy();

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

It's native, written in the language of the operating system, which means that the implementation of arraycopy() is platform dependant.

So, in conclusion it's likely O(n), but maybe not.

Rudi Kershaw
  • 12,332
  • 7
  • 52
  • 77
  • "...to find the source code..., but that isn't possible...". You can certainly get the source code for native methods. See [Java native method source code](http://stackoverflow.com/questions/12594046/java-native-method-source-code) – Hawkeye Parker Oct 12 '16 at 20:47
  • 1
    @HawkeyeParker, you can get the source code for a native method on a specific platform. Sure, but that won't tell you what that native method could be like on all other platforms. It's entirely possible there are terrible implementations for novel systems out there. – Rudi Kershaw Oct 13 '16 at 17:07
  • I agree. To know for certain, you would of course need the actual source code of whatever you are actually running. – Hawkeye Parker Oct 13 '16 at 20:00
1

I don't understand how Kowser's answer answers his own question. I thought to check the time complexity of an algorithm You have to compare its running time for inputs of different sizes, like this:

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

}

Output:

10
22
42
87
147
MaxNevermind
  • 2,784
  • 2
  • 25
  • 31