- Java 双重比较
- java - 比较器与 Apache BeanComparator
- Objective-C 完成 block 导致额外的方法调用?
- database - RESTful URI 是否应该公开数据库主键?
假设您想计算给定的 char[]
包含多少个非 ASCII 字符。想象一下,性能真的很重要,所以我们可以跳过我们最喜欢的 slogan .
最简单的方法显然是
int simpleCount() {
int result = 0;
for (int i = 0; i < string.length; i++) {
result += string[i] >= 128 ? 1 : 0;
}
return result;
}
然后您认为许多输入都是纯 ASCII,分开处理它们可能是个好主意。为简单起见,假设您只写这个
private int skip(int i) {
for (; i < string.length; i++) {
if (string[i] >= 128) break;
}
return i;
}
这样一个简单的方法可能对更复杂的处理有用,在这里它不会有任何坏处,对吧?那么让我们继续
int smartCount() {
int result = 0;
for (int i = skip(0); i < string.length; i++) {
result += string[i] >= 128 ? 1 : 0;
}
return result;
}
它与simpleCount
相同。我称其为“智能”,因为要完成的实际工作更加复杂,因此快速跳过 ASCII 是有意义的。如果没有 ASCII 前缀或非常短的 ASCII 前缀,可能会多花费几个周期,但仅此而已,对吧?
也许你想像这样重写它,它是一样的,只是可能更可重用,对吧?
int smarterCount() {
return finish(skip(0));
}
int finish(int i) {
int result = 0;
for (; i < string.length; i++) {
result += string[i] >= 128 ? 1 : 0;
}
return result;
}
然后你运行了一个benchmark在一些很长的随机字符串上得到这个 这些参数决定了 ASCII 与非 ASCII 的比率以及非 ASCII 序列的平均长度,但如您所见,它们并不重要。尝试不同的种子和任何无关紧要的事情。 benchmark使用 caliper ,所以通常的陷阱不适用。结果相当可重复,末尾的小黑条表示最小和最大时间。
有人知道这里发生了什么吗?任何人都可以复制它吗?
最佳答案
明白了。
不同之处在于优化器/CPU 预测 for
中的循环次数的可能性。 .如果能够预先预测重复次数,则可以跳过实际检查 i < string.length
.因此,优化器需要预先知道 for 循环中的条件成功的频率,因此它必须知道 string.length
的值。和 i
.
我通过替换 string.length
做了一个简单的测试使用局部变量,在 setup
中设置一次方法。结果:smarterCount
运行时间约为 simpleCount
.变更前smarterCount
比 simpleCount
多花了大约 50% . smartCount
没有改变。
当调用另一个方法时,优化器似乎丢失了它必须执行多少循环的信息。这就是为什么 finish()
使用常数集立即跑得更快,但不是 smartCount()
, 作为 smartCount()
不知道什么i
将在skip()
之后步。所以我做了第二次测试,我从 skip()
复制了循环进入smartCount()
.
瞧,这三种方法都在同一时间(800-900 毫秒)内返回。
关于java - 对一个简单的程序进行无害的更改后,性能出现奇怪的下降,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/19751587/
我是一名优秀的程序员,十分优秀!