Java 中的小型数组与列表的基准测试:我的基准测试代码是否错误?
免责声明:我已经浏览过这个 问题和这个问题 但他们都被小事出轨了 细节和一般 优化是不必要的担忧。 我真的需要我所有的表现 可以进入我当前的应用程序,即 接收-处理-喷出 MIDI 数据 实时。还需要扩大规模 尽可能。
我将小列表的大量读取的array
性能与ArrayList
进行比较,并且还与仅将变量放在手。我发现数组优于 ArrayList 2.5 倍,甚至优于仅具有对象引用的数组。
我想知道的是:
- 我的基准测试还好吗? 我已经更改了测试顺序和运行次数,没有任何变化。我也使用毫秒而不是纳秒,但无济于事。
- 我应该指定任何 Java 选项来最小化这种差异吗?
- 如果这种差异确实存在,在这种情况下我不应该更喜欢
Test[]
而不是ArrayList
在这种情况下并放入转换它们所需的代码? 显然我读的比写的多。
JVM 是 OSX 上的 Java 1.6.0_17,并且它肯定运行在 Hotspot 模式下。
public class ArraysVsLists {
static int RUNS = 100000;
public static void main(String[] args) {
long t1;
long t2;
Test test1 = new Test();
test1.thing = (int)Math.round(100*Math.random());
Test test2 = new Test();
test2.thing = (int)Math.round(100*Math.random());
t1 = System.nanoTime();
for (int i=0; i<RUNS; i++) {
test1.changeThing(i);
test2.changeThing(i);
}
t2 = System.nanoTime();
System.out.println((t2-t1) + " How long NO collection");
ArrayList<Test> list = new ArrayList<Test>(1);
list.add(test1);
list.add(test2);
// tried this too: helps a tiny tiny bit
list.trimToSize();
t1= System.nanoTime();
for (int i=0; i<RUNS; i++) {
for (Test eachTest : list) {
eachTest.changeThing(i);
}
}
t2 = System.nanoTime();
System.out.println((t2-t1) + " How long collection");
Test[] array = new Test[2];
list.toArray(array);
t1= System.nanoTime();
for (int i=0; i<RUNS; i++) {
for (Test test : array) {
test.changeThing(i);
}
}
t2 = System.nanoTime();
System.out.println((t2-t1) + " How long array ");
}
}
class Test {
int thing;
int thing2;
public void changeThing(int addThis) {
thing2 = addThis + thing;
}
}
Disclaimer: I have looked through this
question and this question
but they both got derailed by small
details and general
optimization-is-unnecessary concerns.
I really need all the performance I
can get in my current app, which is
receiving-processing-spewing MIDI data
in realtime. Also it needs to scale up
as well as possible.
I am comparing array
performance on a high number of reads for small lists to ArrayList
and also to just having the variables in hand. I'm finding that an array beats ArrayList
by a factor of 2.5 and even beats just having the object references.
What I would like to know is:
- Is my benchmark okay? I have switched the order of the tests and number of runs with no change. I've also used milliseconds instead of nanoseconds to no avail.
- Should I be specifying any Java options to minimize this difference?
- If this difference is real, in this case shouldn't I prefer
Test[]
toArrayList<Test>
in this situation and put in the code necessary to convert them? Obviously I'm reading a lot more than writing.
JVM is Java 1.6.0_17 on OSX and it is definitely running in Hotspot mode.
public class ArraysVsLists {
static int RUNS = 100000;
public static void main(String[] args) {
long t1;
long t2;
Test test1 = new Test();
test1.thing = (int)Math.round(100*Math.random());
Test test2 = new Test();
test2.thing = (int)Math.round(100*Math.random());
t1 = System.nanoTime();
for (int i=0; i<RUNS; i++) {
test1.changeThing(i);
test2.changeThing(i);
}
t2 = System.nanoTime();
System.out.println((t2-t1) + " How long NO collection");
ArrayList<Test> list = new ArrayList<Test>(1);
list.add(test1);
list.add(test2);
// tried this too: helps a tiny tiny bit
list.trimToSize();
t1= System.nanoTime();
for (int i=0; i<RUNS; i++) {
for (Test eachTest : list) {
eachTest.changeThing(i);
}
}
t2 = System.nanoTime();
System.out.println((t2-t1) + " How long collection");
Test[] array = new Test[2];
list.toArray(array);
t1= System.nanoTime();
for (int i=0; i<RUNS; i++) {
for (Test test : array) {
test.changeThing(i);
}
}
t2 = System.nanoTime();
System.out.println((t2-t1) + " How long array ");
}
}
class Test {
int thing;
int thing2;
public void changeThing(int addThis) {
thing2 = addThis + thing;
}
}
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论
评论(3)
在 Java 这样的平台上获得正确的微基准测试非常非常困难。您肯定必须将要进行基准测试的代码提取到单独的方法中,作为预热运行它们几千次,然后进行测量。我已经这样做了(下面的代码),结果是通过引用的直接访问速度是通过数组的三倍,但集合仍然慢了 2 倍。
这些数字基于 JVM 选项 <代码>-server -XX:+DoEscapeAnalysis。如果没有
-server
,使用集合会大大变慢(但奇怪的是,直接访问和数组访问要快得多,这表明发生了一些奇怪的事情)。-XX:+DoEscapeAnalysis
为集合带来了另外 30% 的加速,但它是否适用于您的实际生产代码是非常值得怀疑的。总的来说,我的结论是:忘记微基准,它们很容易产生误导。尽可能接近生产代码进行测量,而无需重写整个应用程序。
Microbenchmarks are very, very hard to get right on a platform like Java. You definitely have to extract the code to be benchmarked into separate methods, run them a few thousand times as warmup and then measure. I've done that (code below) and the result is that direct access through references is then three times as fast as through an array, but the collection is still slower by a factor of 2.
These numbers are based on the JVM options
-server -XX:+DoEscapeAnalysis
. Without-server
, using the collection is drastically slower (but strangely, direct and array access are quite a bit faster, indicating that there is something weird going on).-XX:+DoEscapeAnalysis
yields another 30% speedup for the collection, but it's very much questionabled whether it will work as well for your actual production code.Overall my conclusion would be: forget about microbenchmarks, they can too easily be misleading. Measure as close to production code as you can without having to rewrite your entire application.
仅当您的实际用例与基准代码匹配时,您的基准测试才有效,即每个元素上的操作很少,因此执行时间很大程度上取决于访问时间而不是操作本身。如果是这种情况,那么是的,如果性能至关重要,您应该使用数组。然而,如果您的实际用例涉及每个元素更多的实际计算,那么每个元素的访问时间将变得不那么重要。
Your benchmark is only valid if your actual use case matches the benchmark code, i.e. very few operations on each element, so that execution time is largely determined by access time rather than the operations themselves. If that is the case then yes, you should be using arrays if performance is critical. If however your real use case involves a lot more actual computation per element, then the access time per element will become a lot less significant.
它可能无效。如果我了解 JIT 编译器的工作方式,编译方法不会影响对已执行的该方法的调用。由于
main
方法仅被调用一次,因此它最终会被解释,并且由于大部分工作是在该方法的主体中完成的,因此您得到的数字不会特别指示正常情况执行。JIT 编译效果可能在某种程度上解释了为什么没有集合的情况比数组的情况慢。这个结果是违反直觉的,并且它对您报告的其他基准结果产生了疑问。
It is probably not valid. If I understand the way that JIT compilers work, compiling a method won't affect a call to that method that is already executing. Since the
main
method is only called once, it will end up being interpreted, and since most of the work is done in the body of that method, the numbers you get won't be particularly indicative of normal execution.JIT compilation effects may go some way to explain why the no collections case was slower that the arrays case. That result is counter-intuitive, and it places a doubt on the other benchmark result that you reported.