3

In a previous question Why clone() is the best way for copying arrays? @procrastinator demonstrates that given an array, original.clone() is in average two times faster than System.arraycopy(original, 0, destination, 0, length)

However I have noticed that when using the clone method it is not possible to modify the length of the destination array, nor copy only a portion of the array. Using System.arraycopy I would do:

New array with extra positions

int[] original = new int[] {1,2,3,4,5};
int originalSize = original.length;
int newSize = originalSize + 1;
int[] destination = new int[newSize];

System.arraycopy(original, 0, destination, 0)
destination[newSize - 1] = newValue;

New array with less positions

int[] original = new int[] {1,2,3,4,5};
int originalSize = original.length;
int newSize = originalSize - 1;
int[] destination = new int[newSize];

System.arraycopy(original, 1, destination, 0)

(Notice that the arrays in the example are small for the sake of clarity but in the real case they are bigger)

Is there a way to achieve a similar performance to clone in any of the scenarios? Or in these cases we must use the System.arraycopy method?

EDIT1 :

As suggested by @aUserHimself I have tried (without any success) to measure the performance of the System.arraycopy against the Stream interface. Below I provide the Benchmark code and its results:

@Benchmark
public int[] SystemArraycopySmaller() {
    final int length = this.size;
    int[] destination = new int[length / 2];
    System.arraycopy(this.original, 0, destination, 0, length / 2);
    return destination;
}

@Benchmark
public int[] StreamArraycopySmaller() {
    final int length = this.size;
    int[] destination = Arrays.stream(this.original).filter(i -> i < length / 2).toArray();
    return destination;
}

@Benchmark
public int[] SystemArraycopyBigger() {
    final int length = this.size;
    int[] destination = new int[length * length];
    for (int i = 0; i < length; i++) {
        System.arraycopy(this.original, 0, destination, i * length, length);
    }
    return destination;
}

@Benchmark
public int[] StreamArraycopyBigger() {
    int[] destination = Arrays.stream(this.original).flatMap(i -> Arrays.stream(this.original).map(j -> j)).toArray();
    return destination;
}

Results:

Benchmark                               (size)   Mode  Cnt      Score      Error  Units
SampleBenchmark.StreamArraycopyBigger    10000  thrpt   10        ≈ 0             ops/s
SampleBenchmark.StreamArraycopyBigger     1000  thrpt   10        ≈ 0             ops/s
SampleBenchmark.StreamArraycopyBigger      100  thrpt   10     11,997 ±    0,002  ops/s
SampleBenchmark.StreamArraycopyBigger       10  thrpt   10    608,899 ±    8,975  ops/s
SampleBenchmark.StreamArraycopyBigger        1  thrpt   10   6373,457 ±  313,626  ops/s
SampleBenchmark.StreamArraycopySmaller   10000  thrpt   10     36,692 ±    0,728  ops/s
SampleBenchmark.StreamArraycopySmaller    1000  thrpt   10    328,875 ±    2,259  ops/s
SampleBenchmark.StreamArraycopySmaller     100  thrpt   10   2141,368 ±    8,832  ops/s
SampleBenchmark.StreamArraycopySmaller      10  thrpt   10   9018,659 ±  118,933  ops/s
SampleBenchmark.StreamArraycopySmaller       1  thrpt   10  12954,709 ±  114,621  ops/s
SampleBenchmark.SystemArraycopyBigger    10000  thrpt   10        ≈ 0             ops/s
SampleBenchmark.SystemArraycopyBigger     1000  thrpt   10        ≈ 0             ops/s
SampleBenchmark.SystemArraycopyBigger      100  thrpt   10    161,004 ±    1,361  ops/s
SampleBenchmark.SystemArraycopyBigger       10  thrpt   10  10039,397 ±  123,553  ops/s
SampleBenchmark.SystemArraycopyBigger        1  thrpt   10  42539,869 ± 1965,589  ops/s
SampleBenchmark.SystemArraycopySmaller   10000  thrpt   10    399,816 ±    6,503  ops/s
SampleBenchmark.SystemArraycopySmaller    1000  thrpt   10   3189,271 ±  117,936  ops/s
SampleBenchmark.SystemArraycopySmaller     100  thrpt   10  22533,102 ±  183,870  ops/s
SampleBenchmark.SystemArraycopySmaller      10  thrpt   10  45577,443 ± 1656,788  ops/s
SampleBenchmark.SystemArraycopySmaller       1  thrpt   10  41657,519 ±  183,266  ops/s

Does anyone know about any other possible approach?

EDIT2 :

I have updated the benchmark codes and results with the suggested modifications to make the comparison even. However, for the bigger experimentation there is some error (probably due to the heap size I suppose) and for the smaller experimentation the Arraycopy still outperforms the Stream one. So I suppose there is no better way to copy arrays when the destination differs on size than using arraycopy.

Cristian Ramon-Cortes
  • 1,838
  • 1
  • 19
  • 32
  • @nullpointer Thanks. See my updated question – Cristian Ramon-Cortes Sep 19 '17 at 08:08
  • I would say no, there is no way to achieve similar performance to `clone`. There is also `Arrays.copyOf` but it just uses `arraycopy` internally. – Oleg Sep 19 '17 at 08:14
  • Of course if your performance hotspot is in copying arrays, maybe you're using the wrong language for the job? – Kayaman Sep 19 '17 at 08:14
  • @Kayaman It's not the only performance issue in my project but lets assume I currently cannot change language because I benefit from other Java utilities (the project has 100k loc) – Cristian Ramon-Cortes Sep 19 '17 at 14:08
  • Is it the most important performance issue in your project? – Kayaman Sep 19 '17 at 14:48
  • @Kayaman the array copies are on the critical path but I could also move them to a supporting thread and then synchronize only when needed. However I was just looking for a way to avoid that or, at lest, perform the copies as fast as possible. – Cristian Ramon-Cortes Sep 20 '17 at 07:21

1 Answers1

1

You can try to measure the performance against java8's Streams as well, it can be useful when you need to filter out or add new elements to the destination array:

public static void copyBigArrayAndFilter() {
    long time = System.currentTimeMillis();
    int[] original = IntStream.range(0, 10_000).toArray();
    int[] destination = Arrays.stream(original).filter(i -> i > 1_000 && i < 9_000).toArray();
    System.out.println("Original size: " + original.length);
    System.out.println("Destination size: " + destination.length);
    System.out.println("Duration: " + (System.currentTimeMillis() - time) + " ms." );
}

public static void copyBigArrayAndAdd() {
    long time = System.currentTimeMillis();
    int[] original = IntStream.range(0, 10_000).toArray();
    int[] destination = Arrays.stream(original).flatMap(i -> Arrays.stream(original).map(j -> i + j)).toArray();
    System.out.println("Original size: " + original.length);
    System.out.println("Destination size: " + destination.length);
    System.out.println("Duration: " + (System.currentTimeMillis() - time) + " ms." );
}

UPDATE:

I am not an expert myself, but your question is interesting and I just had the idea of using streams in case you have processing to do on the original array before it is copied to destination. (see copyBigger examples)

For copySmaller examples, we are doing different operations: you are copying the first half of the original into destination, I am copying elements that are greater than length / 2, requiring in my case a full iteration over the original. How would you implement this using System.arraycopy?

For SystemArraycopyBigger you are just setting your destination array size to be double the original one, but you are only copying size in the end. Notice that in my StreamArraycopyBigger the destination array has size ^ 2 elements, not size * 2: for each element in the original array, we had an extra size number of elements.

The results may not change by much in the end, but please try this instead if you want to test equivalent operations and not compare apples and oranges.

Also, why not try bigger sample sizes as well, like 10_000 or 1_000_000?

@Benchmark
public int[] SystemArraycopyBigger() {
    int i;
    final int length = this.size;
    int[] destination = new int[length * length];
    for(i = 0; i < length; i++) {
        System.arraycopy(this.original, 0, destination, i * length, length);
    }
    return destination;
}

@Benchmark
public int[] StreamArraycopyBigger() {
    int[] destination = Arrays.stream(this.original).flatMap(i -> Arrays.stream(this.original).map(j -> j)).toArray();
    return destination;
}

I am not sure this is what you want. But what I am trying to point out is: if you already have the array processed, there are little chances you can do better than System.arraycopy. But if you need to modify/process and only copy a part of it, streams might prove faster.

aUserHimself
  • 1,589
  • 2
  • 17
  • 26
  • As suggested I have bechmarked against the `Stream` interface without any success. I am not very familiar with it but the results are quite poor. Could you check my updated answer too see if I am missing something? – Cristian Ramon-Cortes Sep 19 '17 at 14:38
  • **@CristianRamon-Cortes** You are benchmarking different operations (in terms of time cost and efficiency) and there are some extra steps in my `stream` examples that will be obviously more time-consuming than your simple copy. See the update above so that you get more accurate results. – aUserHimself Sep 21 '17 at 06:46
  • I have updated the question with the new benchmarks and results. Although the `arraycopy` still outperforms the `stream` implementation I suppose there is no other way so I will mark your answer as accepted since it is the one that provides the solution to compare the `arraycopy` against the `stream`. – Cristian Ramon-Cortes Sep 21 '17 at 07:08