Java中小型阵列与列表的基准测试:我的基准测试代码错了吗?
免责声明:我已经仔细阅读了 但他们两人都因小事故而出轨 详情及概述 优化是不必要的。 我真的需要所有我想要的表演 可以进入我当前的应用程序,即 接收处理喷出MIDI数据 实时的。它还需要扩大规模 尽可能好。 我将小列表的大量读取的Java中小型阵列与列表的基准测试:我的基准测试代码错了吗?,java,performance,microbenchmark,Java,Performance,Microbenchmark,免责声明:我已经仔细阅读了 但他们两人都因小事故而出轨 详情及概述 优化是不必要的。 我真的需要所有我想要的表演 可以进入我当前的应用程序,即 接收处理喷出MIDI数据 实时的。它还需要扩大规模 尽可能好。 我将小列表的大量读取的array性能与ArrayList以及手头有变量进行比较。我发现一个数组比ArrayList强2.5倍,甚至比对象引用强。 我想知道的是: 我的基准行吗我已切换了测试顺序和运行次数,没有任何更改。我也用毫秒代替了纳秒,但没有用 我应该指定任何Java选项来最小化这种差异
array
性能与ArrayList
以及手头有变量进行比较。我发现一个数组比ArrayList
强2.5倍,甚至比对象引用强。
我想知道的是:
Test[]
而不是ArrayList
,并输入转换它们所需的代码吗?显然我读的比写的多得多 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;
}
}
公共类arraysvslist{
静态整数运行=100000;
公共静态void main(字符串[]args){
长t1;
长t2;
test1=新测试();
test1.thing=(int)Math.round(100*Math.random());
测试2=新测试();
test2.thing=(int)Math.round(100*Math.random());
t1=系统.nanoTime();
对于(int i=0;i只有当您的实际用例与基准代码匹配时,您的基准才有效,即每个元素上的操作很少,因此执行时间在很大程度上取决于访问时间,而不是操作本身。如果是这种情况,那么是的,如果性能至关重要,您应该使用数组。但是,如果您真正使用cas每个元素涉及更多的实际计算,那么每个元素的访问时间将变得不那么重要。这可能是无效的。如果我了解JIT编译器的工作方式,编译一个方法不会影响对已经执行的方法的调用。因为main
方法只调用一次,它将我最终会被解释,因为大部分工作都是在该方法的主体中完成的,所以你得到的数字不会特别表明正常执行
JIT编译效果可能会在某种程度上解释为什么无集合的情况比数组的情况慢。这一结果与直觉相反,并且会对您报告的另一个基准测试结果产生怀疑。在Java这样的平台上,很难正确使用微基准测试。您肯定必须提取代码才能正确使用将它们分成不同的方法,在预热时运行数千次,然后进行测量。我已经这样做了(下面的代码),结果是通过引用直接访问的速度是通过数组的三倍,但收集速度仍然慢了2倍
这些数字基于JVM选项-server-XX:+DoEscapeAnalysis
。如果没有-server
,使用集合的速度会大大降低(但奇怪的是,直接访问和数组访问的速度要快得多,这表明发生了一些奇怪的事情).-XX:+DoEscapeAnalysis
为收集带来了另外30%的加速,但它是否也适用于您的实际生产代码仍有很大疑问
总的来说,我的结论是:忘掉微基准,它们太容易误导人了。在不重写整个应用程序的情况下,尽可能接近生产代码
import java.util.ArrayList;
public class ArrayTest {
static int RUNS_INNER = 1000;
static int RUNS_WARMUP = 10000;
static int RUNS_OUTER = 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());
for(int i=0; i<RUNS_WARMUP; i++)
{
testRefs(test1, test2);
}
t1 = System.nanoTime();
for(int i=0; i<RUNS_OUTER; i++)
{
testRefs(test1, test2);
}
t2 = System.nanoTime();
System.out.println((t2-t1)/1000000.0 + " 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();
for(int i=0; i<RUNS_WARMUP; i++)
{
testColl(list);
}
t1= System.nanoTime();
for(int i=0; i<RUNS_OUTER; i++)
{
testColl(list);
}
t2 = System.nanoTime();
System.out.println((t2-t1)/1000000.0 + " How long collection");
Test[] array = new Test[2];
list.toArray(array);
for(int i=0; i<RUNS_WARMUP; i++)
{
testArr(array);
}
t1= System.nanoTime();
for(int i=0; i<RUNS_OUTER; i++)
{
testArr(array);
}
t2 = System.nanoTime();
System.out.println((t2-t1)/1000000.0 + " How long array ");
}
private static void testArr(Test[] array)
{
for (int i=0; i<RUNS_INNER; i++) {
for (Test test : array) {
test.changeThing(i);
}
}
}
private static void testColl(ArrayList<Test> list)
{
for (int i=0; i<RUNS_INNER; i++) {
for (Test eachTest : list) {
eachTest.changeThing(i);
}
}
}
private static void testRefs(Test test1, Test test2)
{
for (int i=0; i<RUNS_INNER; i++) {
test1.changeThing(i);
test2.changeThing(i);
}
}
}
class Test {
int thing;
int thing2;
public void changeThing(int addThis) {
thing2 = addThis + thing;
}
}
import java.util.ArrayList;
公共类阵列测试{
静态int运行_内部=1000;
静态int运行_预热=10000;
静态整数=100000;
公共静态void main(字符串[]args){
长t1;
长t2;
test1=新测试();
test1.thing=(int)Math.round(100*Math.random());
测试2=新测试();
test2.thing=(int)Math.round(100*Math.random());
for(int i=0;iThanks Paul,理解并感谢:是的,像这样的真实案例遍布整个应用程序。事实上,加法比我通常做的还要重,也就是询问元素是否希望处理当前op(过滤器)。这是一种模式(它叫什么)我在整个应用程序中都使用它。真正的应用程序不做什么?在一个庞大的对象列表上调用小方法?不…它们不会重复地将相同的方法应用于2元素数组/列表的元素。在数组的情况下,编译器甚至可能正在展开内部循环。几个小时后再考虑,这是一个合理的问题。我最初认为非常短的列表会被频繁地迭代,但是这个测试确实没有关于长列表的内容。只是用较长的列表进行了测试,差别大致相同。但是,您关于staticmain
的说法可能是正确的。@yar:我宁愿完全独立地运行这两个测试然后我会确保首先“预热”虚拟机,以便热点的JIT生效。然后我会使用一些统计平均值。运行100个测试,只保留95%的测试,丢弃异常值。现在对于大量实时处理,默认的Java集合根本不会削减它。你可能想搜索“Trove”或“Javolution”。对于原语集合,Trove击败了默认Java集合的垃圾。对于实时内容,Javolution摇滚乐。感谢@WizardofOdds。如果您不介意将您的评论作为一个问题,我们可以适当地向上投票。无论如何,关于测试,是的,我将尝试两种方法