【问题标题】:OpenJDK implementation of System.arraycopySystem.arraycopy 的 OpenJDK 实现
【发布时间】:2012-06-26 15:08:51
【问题描述】:

在关于 JVM 基于 char[] 实现字符串创建方式的问题之后,我提到当 char[] 被复制到新字符串的内部时不会发生迭代,因为 System.arraycopy 得到最终调用,它使用诸如 memcpy 之类的函数在本机的、依赖于实现的级别 (the original question) 复制所需的内存。

我想亲自检查一下,所以我下载了 Openjdk 7 源代码并开始浏览它。 我在 OpenJDK C++ 源代码中找到了 System.arraycopy 的实现,在openjdx/hotspot/src/share/vm/oops/objArrayKlass.cpp

if (stype == bound || Klass::cast(stype)->is_subtype_of(bound)) {
  // elements are guaranteed to be subtypes, so no check necessary
  bs->write_ref_array_pre(dst, length);
  Copy::conjoint_oops_atomic(src, dst, length);
} else {
  // slow case: need individual subtype checks

如果元素不需要类型检查(例如,原始数据类型数组就是这种情况),则调用 Copy::conjoin_oops_atomic。

Copy::conjoint_oops_atomic 函数位于“copy.hpp”中:

// overloaded for UseCompressedOops
static void conjoint_oops_atomic(narrowOop* from, narrowOop* to, size_t count) {
  assert(sizeof(narrowOop) == sizeof(jint), "this cast is wrong");
  assert_params_ok(from, to, LogBytesPerInt);
  pd_conjoint_jints_atomic((jint*)from, (jint*)to, count);
}

现在我们依赖于平台,因为复制操作有不同的实现,基于操作系统/架构。我将以 Windows 为例。 openjdk\hotspot\src\os_cpu\windows_x86\vm\copy_windows_x86.inline.hpp:

static void pd_conjoint_oops_atomic(oop* from, oop* to, size_t count) {
// Do better than this: inline memmove body  NEEDS CLEANUP
if (from > to) {
  while (count-- > 0) {
    // Copy forwards
    *to++ = *from++;
  }
} else {
  from += count - 1;
  to   += count - 1;
  while (count-- > 0) {
    // Copy backwards
    *to-- = *from--;
  }
 }
}

而且...令我惊讶的是,它遍历元素(oop 值),一一复制它们(似乎)。有人可以通过遍历数组中的元素来解释为什么即使在本机级别也可以完成复制吗?

【问题讨论】:

    标签: c++ jvm java openjdk


    【解决方案1】:

    因为jint最接近映射到int,而int最接近映射到旧硬件架构WORD,其大小与数据总线的宽度基本相同。

    当今的内存架构和 cpu 处理旨在尝试在缓存未命中的情况下进行处理,并且内存位置倾向于预取块。您正在查看的代码在性能上并不像您想象的那么“糟糕”。硬件更智能,如果您不真正进行分析,您的“智能”获取例程实际上可能不会添加任何内容(甚至会减慢处理速度)。

    在向您介绍硬件架构时,必须向您介绍简单的架构。现代的代码做得更多,所以你不能假设看起来低效的代码实际上是低效的。例如,当执行内存查找以评估 if 语句的条件时,通常会在查找发生时执行 if 语句的两个分支,并且在数据可用于评估后丢弃处理的“假”分支条件。如果您想提高效率,您必须剖析然后对剖析的数据采取行动。

    查看 JVM 操作码部分的分支。你会看到它是(或者可能只是)一个 ifdef 宏的奇怪之处,它支持(一次)三种不同的跳转到处理操作码的代码的方式。这是因为这三种不同的方式实际上在不同的 Windows、Linux 和 Solaris 架构上产生了有意义的性能差异。

    也许他们可以包含 MMX 例程,但他们没有告诉我 SUN 认为现代硬件的性能提升不足以担心它。

    【讨论】:

    • 哇,谢谢!第一次浏览 OpenJDK 实现有点令人困惑,所以我预计会出错。 :P 那么你认为这种优化是如何发生的呢?我确实做了一些测试,System.arraycopy 复制 10000 个整数的速度是常规 Java 方式的两倍。在 C++ 中,类似的任务仍然明显更快,尽管结果可能会受到各种编译器优化的影响。
    • C++ 副本没有在单独线程上运行的垃圾收集器。即使你不产生垃圾,收集器也必须偷走几个周期来验证它没有工作要做。我不确定编译器是否正在展开 arraycopy 循环,或者硬件是否正在将整个数组块预取到缓存中。事实上,微码优化已经超出了我的知识深度。这就是分析如此重要的原因,它是证明优化值得的测试。
    猜你喜欢
    • 2016-05-19
    • 2012-04-23
    • 2023-04-06
    • 2019-11-02
    • 1970-01-01
    • 2013-08-16
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多