Cristian Ramon-Cortes
Cristian Ramon-Cortes

Reputation: 1888

Alternatives to System.arraycopy when destination array differs on size

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.

Upvotes: 3

Views: 2955

Answers (1)

aUserHimself
aUserHimself

Reputation: 1597

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.

Upvotes: 1

Related Questions