当前位置: 首页 > 面试题库 >

为什么“ new”关键字比赋值效率高得多?

施英哲
2023-03-14
问题内容

我有两种方法可以读取字符串并创建Character对象:

static void newChar(String string) {
    int len = string.length();
    System.out.println("Reading " + len + " characters");
    for (int i = 0; i < len; i++) {
        Character cur = new Character(string.charAt(i));

    }       
}

static void justChar(String string) {
    int len = string.length();
    for (int i = 0; i < len; i++) {
        Character cur = string.charAt(i);

    }
}

当我使用18554760字符串运行方法时,我得到的运行时间截然不同。我得到的输出是:

newChar took: 20 ms
justChar took: 41 ms

使用较小的输入(4,638,690个字符)时,时间没有变化。

newChar took: 12 ms
justChar took: 13 ms

在这种情况下,为什么新的效率更高?

编辑:

我的基准代码很hacky。

start = System.currentTimeMillis();
newChar(largeString);
end = System.currentTimeMillis();
diff = end-start;
System.out.println("New char took: " + diff + " ms");

start = System.currentTimeMillis();
justChar(largeString);
end = System.currentTimeMillis();
diff = end-start;
System.out.println("just char took: " + diff+ " ms");

问题答案:

TL; DR部分

好消息

您的测量确实显示出真实的效果。

消息

它之所以这样做是偶然的,因为您的基准测试存在许多技术缺陷,而它所暴露的效果可能并非您所想到的。

当且仅当 HotSpot的转义分析成功证明可以将生成的实例安全地分配到堆栈而不是堆上时,该new Character()方法才会更快。因此,效果不如您的问题所暗示的那么普遍。 __

效果说明

new Character()更快的原因是 引用的局部性 :您的实例在堆栈中,并且通过CPU高速缓存命中对其进行的所有访问。重用缓存的实例时,必须

  1. 访问远程static字段;
  2. 解引用到远程数组;
  3. 将数组条目取消引用到远程Character实例;
  4. 访问char该实例中包含的内容。

每个取消引用都是潜在的CPU高速缓存未命中。此外,它强制将一部分高速缓存重定向到那些远程位置,从而在输入字符串和/或堆栈位置上导致更多的高速缓存未命中。

细节

我已经使用以下代码运行了此代码jmh

@OutputTimeUnit(TimeUnit.MICROSECONDS)
@BenchmarkMode(Mode.AverageTime)
public class Chars {
  static String string = "12345678901234567890"; static {
    for (int i = 0; i < 10; i++) string += string;
  }

  @GenerateMicroBenchmark
  public void newChar() {
    int len = string.length();
    for (int i = 0; i < len; i++) new Character(string.charAt(i));
  }

  @GenerateMicroBenchmark
  public void justChar() {
    int len = string.length();
    for (int i = 0; i < len; i++) Character.valueOf(string.charAt(i));
  }
}

这保留了代码的本质,但消除了一些系统性错误,如预热和编译时间。结果如下:

Benchmark              Mode Thr    Cnt  Sec         Mean   Mean error    Units
o.s.Chars.justChar     avgt   1      3    5       39.062        6.587  usec/op
o.s.Chars.newChar      avgt   1      3    5       19.114        0.653  usec/op

这是我对发生的事情的最佳猜测:

  • newChar您创建一个 的实例Character。HotSpot的转义分析可以证明实例永不转义,因此它允许堆栈分配,或者在特殊情况下Character,可以完全取消分配,因为证明来自该数据从未使用过。

  • justChar你涉及查找到Character高速缓存阵列,其中有 一些 成本。

更新

为了回应Aleks的批评,我在基准测试中添加了更多方法。主要效果保持稳定,但是我们获得了有关次优效果的更多细粒度信息。

  @GenerateMicroBenchmark
  public int newCharUsed() {
    int len = string.length(), sum = 0;
    for (int i = 0; i < len; i++) sum += new Character(string.charAt(i));
    return sum;
  }

  @GenerateMicroBenchmark
  public int justCharUsed() {
    int len = string.length(), sum = 0;
    for (int i = 0; i < len; i++) sum += Character.valueOf(string.charAt(i));
    return sum;
  }

  @GenerateMicroBenchmark
  public void newChar() {
    int len = string.length();
    for (int i = 0; i < len; i++) new Character(string.charAt(i));
  }

  @GenerateMicroBenchmark
  public void justChar() {
    int len = string.length();
    for (int i = 0; i < len; i++) Character.valueOf(string.charAt(i));
  }

  @GenerateMicroBenchmark
  public void newCharValue() {
    int len = string.length();
    for (int i = 0; i < len; i++) new Character(string.charAt(i)).charValue();
  }

  @GenerateMicroBenchmark
  public void justCharValue() {
    int len = string.length();
    for (int i = 0; i < len; i++) Character.valueOf(string.charAt(i)).charValue();
  }

描述:

  • 基本版本是justCharnewChar;
  • ...Value方法将charValue调用添加到基本版本中;
  • ...Used方法会charValue隐式地添加调用,并 使用 该值排除任何死代码消除。

结果:

Benchmark                   Mode Thr    Cnt  Sec         Mean   Mean error    Units
o.s.Chars.justChar          avgt   1      3    1      246.847        5.969  usec/op
o.s.Chars.justCharUsed      avgt   1      3    1      370.031       26.057  usec/op
o.s.Chars.justCharValue     avgt   1      3    1      296.342       60.705  usec/op
o.s.Chars.newChar           avgt   1      3    1      123.302       10.596  usec/op
o.s.Chars.newCharUsed       avgt   1      3    1      172.721        9.055  usec/op
o.s.Chars.newCharValue      avgt   1      3    1      123.040        5.095  usec/op
  • 有证据表明,在和变体中都消除了 某些 死代码(DCE),但这只是部分的;justChar``newChar
  • 对于newChar变体,添加charValue没有任何作用,因此显然是DCE的;
  • justCharcharValue确实有作用,因此似乎没有被消除;
  • newCharUsed和之间的稳定差异可以证明DCE的总体影响较小justCharUsed


 类似资料:
  • 那么为什么这些更精确的方法会产生明显更好的性能呢?这是Ruby和/或所有语言中的通用公理吗?

  • 问题内容: 我正在网上关注Java教程,尝试学习该语言,并且它在使用数组的两种语义之间反弹。 和: 该教程从未真正提到过为什么它会在两者之间来回切换,所以我对该主题进行了一些搜索。我目前的理解是,操作员正在创建“ longs数组”类型的对象。我 不 明白的是为什么我要那个,那有什么后果? 是否存在某些特定于“数组”的方法,除非它是“数组对象”,否则这些方法对数组不起作用? 有什么我 不能 用普通数

  • 问题内容: 在JavaScript中的关键字可能会相当混乱首次遇到它的时候,人们往往会认为JavaScript是不是面向对象的编程语言。 它是什么? 它解决什么问题? 什么时候合适,什么时候不合适? 问题答案: 它做五件事: 它创建一个新对象。这个对象的类型就是 object 。 它将这个新对象的内部不可访问的 [[prototype]] (即 proto )属性设置为构造函数的外部可访问 原型

  • 我正在使用Node.js开发后端项目,并打算实现产品排序功能。我研究了一些文章,有几篇文章说泡泡排序不是有效的。泡泡排序在我以前的项目中使用,我很惊讶为什么它是不好的。有人能解释一下为什么效率低下吗?如果您能用c编程或汇编命令来解释,将不胜感激。

  • 我正在学习颤振(主要来自Youtube) 为什么我们需要在赋值运算符后面使用关键字,因为我们已经将其设置为

  • } 为什么和什么时候我们使用返回类型的new关键字,有谁能解释一下代码的最后一行吗??