【问题标题】:Alternatives to System.arraycopy when destination array differs on size目标数组大小不同时 System.arraycopy 的替代方案
【发布时间】:2018-02-27 22:07:45
【问题描述】:

在上一个问题中Why clone() is the best way for copying arrays?@procrastinator 证明给定一个数组,original.clone() 平均比System.arraycopy(original, 0, destination, 0, length) 快两倍

但是我注意到,当使用clone 方法时,不能修改目标数组的长度,也不能只复制数组的一部分。使用System.arraycopy 我会这样做:

具有额外位置的新数组

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;

位置更少的新数组

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)

(请注意,为了清楚起见,示例中的数组很小,但在实际情况下它们更大)

有没有办法在任何场景中实现与clone 类似的性能?还是在这些情况下我们必须使用System.arraycopy 方法?

EDIT1:

正如@aUserHimself 所建议的那样,我已经尝试(没有任何成功)测量System.arraycopyStream 接口的性能。下面我提供 Benchmark 代码及其结果:

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

结果:

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

有人知道其他可能的方法吗?

EDIT2:

我已根据建议的修改更新了基准代码和结果,以便进行比较。但是,对于较大的实验,存在一些错误(可能是由于我想的堆大小),对于较小的实验,Arraycopy 仍然优于Stream所以我认为当目标大小不同时,没有比使用arraycopy 复制数组更好的方法了。

【问题讨论】:

  • @nullpointer 谢谢。查看我更新的问题
  • 我会说不,没有办法达到与clone 类似的性能。还有Arrays.copyOf,但它只是在内部使用arraycopy
  • 当然,如果您的性能热点是复制数组,那么您可能使用了错误的语言来完成这项工作?
  • @Kayaman 这不是我项目中唯一的性能问题,但假设我目前无法更改语言,因为我受益于其他 Java 实用程序(该项目有 100k loc)
  • 这是您项目中最重要的性能问题吗?

标签: java arrays copy clone


【解决方案1】:

您也可以尝试针对java8Streams 来衡量性能,当您需要过滤掉或向destination 数组添加新元素时,它会很有用:

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

更新:

我自己不是专家,但您的问题很有趣,我只是想到使用streams 以防您在将original 数组复制到destination 之前对其进行处理。 (参见copyBigger 示例)

对于copySmaller 示例,我们正在执行不同的操作:您将original 的前半部分复制到destination,我正在复制大于length / 2 的元素,在我的情况下需要完整的迭代通过original。您将如何使用System.arraycopy 实现这一点?

对于SystemArraycopyBigger,您只是将destination 数组大小设置为original 的两倍,但最后您只是复制size。请注意,在我的 StreamArraycopyBigger 中,destination 数组有 size ^ 2 元素,而不是 size * 2:对于 original 数组中的每个元素,我们有一个额外的 size 元素数量。

结果可能最终变化不大,但如果您想测试等效操作而不是比较苹果和橙子,请尝试此方法。

另外,为什么不尝试更大的样本量,例如10_0001_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;
}

我不确定这是你想要的。但我要指出的是:如果您已经处理了数组,那么您几乎没有机会比System.arraycopy 做得更好。但是如果您需要修改/处理并且只复制其中的一部分,streams 可能会更快。

【讨论】:

  • 正如建议的那样,我已经对Stream 界面进行了基准测试,但没有任何成功。我不是很熟悉它,但结果很差。您能否也检查一下我更新的答案,看看我是否遗漏了什么?
  • @CristianRamon-Cortes 您正在对不同的操作进行基准测试(在时间成本和效率方面),我的 stream 示例中有一些额外的步骤,显然更多比你简单的复制更费时间。请参阅上面的更新,以便获得更准确的结果。
  • 我已经用新的基准和结果更新了这个问题。尽管arraycopy 仍然优于stream 实现,但我想没有其他方法,所以我会将您的答案标记为已接受,因为它提供了将arraycopystream 进行比较的解决方案。跨度>
猜你喜欢
  • 2021-03-31
  • 1970-01-01
  • 1970-01-01
  • 2016-08-17
  • 1970-01-01
  • 1970-01-01
  • 2018-07-03
  • 2014-03-08
  • 2022-01-23
相关资源
最近更新 更多