我有两种方法可以读取字符串并创建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");
您的测量确实显示出真实的效果。
它之所以这样做是偶然的,因为您的基准测试存在许多技术缺陷,而它所暴露的效果可能并非您所想到的。
当且仅当 HotSpot的转义分析成功证明可以将生成的实例安全地分配到堆栈而不是堆上时,该new Character()
方法才会更快。因此,效果不如您的问题所暗示的那么普遍。 __
new Character()
更快的原因是 引用的局部性 :您的实例在堆栈中,并且通过CPU高速缓存命中对其进行的所有访问。重用缓存的实例时,必须
static
字段;Character
实例;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();
}
justChar
和newChar
;...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
justChar``newChar
newChar
变体,添加charValue
没有任何作用,因此显然是DCE的;justChar
,charValue
确实有作用,因此似乎没有被消除;newCharUsed
和之间的稳定差异可以证明DCE的总体影响较小justCharUsed
。那么为什么这些更精确的方法会产生明显更好的性能呢?这是Ruby和/或所有语言中的通用公理吗?
问题内容: 我正在网上关注Java教程,尝试学习该语言,并且它在使用数组的两种语义之间反弹。 和: 该教程从未真正提到过为什么它会在两者之间来回切换,所以我对该主题进行了一些搜索。我目前的理解是,操作员正在创建“ longs数组”类型的对象。我 不 明白的是为什么我要那个,那有什么后果? 是否存在某些特定于“数组”的方法,除非它是“数组对象”,否则这些方法对数组不起作用? 有什么我 不能 用普通数
问题内容: 在JavaScript中的关键字可能会相当混乱首次遇到它的时候,人们往往会认为JavaScript是不是面向对象的编程语言。 它是什么? 它解决什么问题? 什么时候合适,什么时候不合适? 问题答案: 它做五件事: 它创建一个新对象。这个对象的类型就是 object 。 它将这个新对象的内部不可访问的 [[prototype]] (即 proto )属性设置为构造函数的外部可访问 原型
我正在使用Node.js开发后端项目,并打算实现产品排序功能。我研究了一些文章,有几篇文章说泡泡排序不是有效的。泡泡排序在我以前的项目中使用,我很惊讶为什么它是不好的。有人能解释一下为什么效率低下吗?如果您能用c编程或汇编命令来解释,将不胜感激。
我正在学习颤振(主要来自Youtube) 为什么我们需要在赋值运算符后面使用关键字,因为我们已经将其设置为
} 为什么和什么时候我们使用返回类型的new关键字,有谁能解释一下代码的最后一行吗??