【问题标题】:Can System.arraycopy(…) be faster than O(n)?System.arraycopy(…) 可以比 O(n) 快吗?
【发布时间】:2020-01-15 08:51:01
【问题描述】:

假设我在 64 位 POSIX 系统中有一个 64 位数组。

假设处理器缓存包含我的数组,并且寄存器的长度超过 64 位。

我可以期望 System.arraycopy(...) 的时间为 O(1) 吗?从某种意义上说,复制的时间并不取决于我的数组的长度。

问题与Time complexity of System.arraycopy(...)? 有关。

我觉得我可以期待它,但它在引擎盖下是否像这样工作?在这种情况下,我会变得依赖系统和 JVM 吗?

【问题讨论】:

  • 它不可能是 O(1),除非有一些奇怪的表映射正在进行,而这几乎肯定不会发生。它可以比复制数据的简单循环要快得多,但这种差异并没有在 big-O 表示法中捕获(这实际上更像是一种理论工具,对于比较两个不同的实现相同基本算法的方法)。
  • 如果你将n设置为一个特定的数字,你可以替换Big-O中的所有n,从而得到恒定的时间。这根本没用。

标签: java jvm time-complexity


【解决方案1】:

假设处理器缓存包含我的数组

这并不重要。

相关的:首先是底层数组数据的类型。

如:只有当你说 byte data[] = new byte[8] 时,你才会有 64 位真正地,一个接一个地依次存放在内存中。

因为,当你有 Byte data[] = new Byte[8] 时,你已经坏了,因为数组中的槽只是指针,指向堆上的某个地方!

所以,我们换个说法:假设您有一个 64 位架构,那么确定:CPU 应该能够用一条指令处理 64 位。将整个数组读入 CPU 缓存或 CPU 寄存器,将数据复制到内存中的不同位置。

但我完全同意用户Joachim 的评论:Big-O 在这里并不适用。我们使用 Big-O 来确定/估计算法的“成本”。它不是评估实现之间差异的工具。特别是考虑到 OP 在这里混合了这么多不同的级别/层。 CPU 缓存或“寄存器宽度”等细节对 Big-O 来说并不重要!

因此,如前所述,我们可以说:在支持 64 位从上到下的系统上,64 位可以“一次”移动。

【讨论】:

  • 涉及多少层并不重要。实际的重点是,大 O 表示法告诉我们,算法如何随大输入扩展。因此,小甚至固定大小的输入的行为与时间复杂度完全无关。无论架构如何,您都可以假设复制 200 万个元素所花费的时间大约是复制 100 万个元素所用时间的两倍。这就是O(n) 的意思。
  • @Holger 它在一定程度上起作用:因为它定义了你所处的上下文。对我来说,Big-O 主要是一个理论概念,可以帮助我们对算法进行分类。算法不是程序。实现取决于所涉及层的所有方面。但我明白你的意思,我会考虑改进措辞。
  • 实现可能会表现出与大 O 复杂性的理论预测不匹配的行为。我想这就是你要去的地方。但如果大 O 没有首先应用这个问题,那就没有实际意义了。
【解决方案2】:

64 位 POSIX 系统。

POSIX 与分块数据复制相关的 CPU 特性无关

假设处理器缓存包含我的数组

在执行复制指令时会从主存跳闸中保存,但不影响大O符号的顺序。

寄存器的长度超过 64 位。

即使你的架构上支持 AVX512 支持 512 位宽 zmm 寄存器和 JDK 9+(AFAIK 运行时知道 AVX512 启动 JDK 9+),它也允许你复制 8 个打包的 64 位整数每个指令,但不影响复杂性的顺序。

因此,要复制 1024 个 64 位整数,您需要再次执行至少 128 个向量指令,从而产生 O(n) 复杂度,但常数较低


HotSpot 实施说明

arraycopy 实现的体系结构相关代码是在此处StubRoutines::initialize2 的 JVM 全局引导“阶段”上生成的。

特别是分块复制例程代码生成是在 HotSpot 代码的平台相关部分使用copy_bytes_forward 函数完成的(它是通过 HotSpot 自己的宏汇编器实现完成的)。

它的关键部分是 CPU 功能检查,例如

if (UseAVX > 2) {
  __ evmovdqul(xmm0, Address(end_from, qword_count, Address::times_8, -56), Assembler::AVX_512bit);
  __ evmovdqul(Address(end_to, qword_count, Address::times_8, -56), xmm0, Assembler::AVX_512bit);
} else if (UseAVX == 2) {
  __ vmovdqu(xmm0, Address(end_from, qword_count, Address::times_8, -56));
  __ vmovdqu(Address(end_to, qword_count, Address::times_8, -56), xmm0);
  __ vmovdqu(xmm1, Address(end_from, qword_count, Address::times_8, -24));
  __ vmovdqu(Address(end_to, qword_count, Address::times_8, -24), xmm1);
} else {
    //...
}

它根据可用的 CPU 功能生成代码。特征检测器是基于cpuid指令在依赖于架构的生成器generate_get_cpu_info中生成和调用的。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2014-05-03
    • 2021-09-25
    • 1970-01-01
    • 2023-04-10
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多