System.arrayCopy is slow

流过昼夜 提交于 2019-11-30 03:58:51

Actually, HotSpot compiler is smart enough to unroll and vectorize manual copy loop - that's why the result code appears to be well optimized.

Why is System.arraycopy slower then? It is originally a native method, and you have to pay for a native call until the compiler optimizes it as JVM intrinsic.

However, in your test the compiler does not have a chance for such optimization, because enlarge method is not called many enough times (i.e. it is not considered as hot).

I'll show you a funny trick to force the optimization. Rewrite enlarge method as follows:

private static int[] enlarge(int[] array, int size) {
    for (int i = 0; i < 10000; i++) { /* fool the JIT */ }

    int[] newArray = new int[array.length + size];
    System.arraycopy(array, 0, newArray, 0, array.length);
    return newArray;
}

An empty loop triggers a backedge counter overflow, which in turn triggers the compilation of enlarge method. The empty loop is then eliminated from the compiled code, so it is harmless. Now enlarge method gets about 1.5x faster than the manual loop!

It is important that System.arraycopy immediately follows new int[]. In this case HotSpot can optimize away the redundant zeroing of newly allocated array. You know, all Java objects must be zeroed right after creation. But as far as compiler detects that the array is filled right after creation, it may eliminate zeroing, thus making the result code yet faster.

P.S. @assylias' benchmark is good, but it also suffers from the fact that System.arraycopy is not intrinsified for the large arrays. In case of small arrays arrayCopy benchmark is called many times per second, JIT considers it hot and optimizes well. But for large arrays each iteration is longer, so there is a lot less iterations per second, and JIT does not treat arrayCopy as hot.

Using jmh, I get the results shown in the table below (size is the size of the array, score is the time in microseconds, error shows the confidence interval at 99.9%):

Benchmark              (size)  Mode  Cnt      Score     Error  Units
ArrayCopy.arrayCopy        10  avgt   60      0.022 ±   0.001  us/op
ArrayCopy.arrayCopy     10000  avgt   60      4.959 ±   0.068  us/op
ArrayCopy.arrayCopy  10000000  avgt   60  11906.870 ± 220.850  us/op
ArrayCopy.clone_           10  avgt   60      0.022 ±   0.001  us/op
ArrayCopy.clone_        10000  avgt   60      4.956 ±   0.068  us/op
ArrayCopy.clone_     10000000  avgt   60  10895.856 ± 208.369  us/op
ArrayCopy.copyOf           10  avgt   60      0.022 ±   0.001  us/op
ArrayCopy.copyOf        10000  avgt   60      4.958 ±   0.072  us/op
ArrayCopy.copyOf     10000000  avgt   60  11837.139 ± 220.452  us/op
ArrayCopy.loop             10  avgt   60      0.036 ±   0.001  us/op
ArrayCopy.loop          10000  avgt   60      5.872 ±   0.095  us/op
ArrayCopy.loop       10000000  avgt   60  11315.482 ± 217.348  us/op

In substance, loop seems to perform slightly better than arrayCopy for large arrays indeed - probably because the JIT is quite good at optimising such a simple loop. For smaller arrays, arrayCopy seems better (although the difference is quite small).

Note however that clone seems to be consistently as good as, or better than, the other options, depending on size. So I would go for clone, which also happens to be easier to use.


For reference, benchmark code, run with -wi 5 -w 1000ms -i 30 -r 1000ms -t 1 -f 2 -tu us:

@State(Scope.Thread)
@BenchmarkMode(Mode.AverageTime)
public class ArrayCopy {

  @Param({"10", "10000", "10000000"}) int size;

  private int[] array;

  @Setup(Level.Invocation) public void setup() {
    array = new int[size];
    for (int i = 0; i < size; i++) {
      array[i] = i;
    }
  }

  @Benchmark
  public int[] clone_() {
    int[] copy = array.clone();
    return copy;
  }

  @Benchmark
  public int[] arrayCopy() {
    int[] copy = new int[array.length];
    System.arraycopy(array, 0, copy, 0, array.length);
    return copy;
  }

  @Benchmark
  public int[] copyOf() {
    int[] copy = Arrays.copyOf(array, array.length);
    return copy;
  }

  @Benchmark
  public int[] loop() {
    int[] copy = new int[array.length];
    for (int i = 0; i < array.length; i++) {
      copy[i] = array[i];
    }
    return copy;
  }
}
易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!