我之前做过一些性能测试,无法解释我获得的结果。
在运行下面的测试时,如果我取消注释private final List<String> list = new ArrayList<String>();
,性能会显着提高。在我的机器上,测试在该字段存在时以70-90毫秒运行,而在注释时为650毫秒。
我还注意到,如果我将print语句更改为System.out.println((end - start) / 1000000);
,则没有变量的测试运行时间为450-500毫秒而不是650毫秒。变量存在时无效。
我的问题:
ps:当按顺序运行时,3个场景(带有变量,没有变量,带有不同的print语句)都需要大约260ms。
public class SOTest {
private static final int ITERATIONS = 10000000;
private static final int THREADS = 4;
private volatile long id = 0L;
//private final List<String> list = new ArrayList<String>();
public static void main(String[] args) throws Exception {
ExecutorService executor = Executors.newFixedThreadPool(THREADS);
final List<SOTest> objects = new ArrayList<SOTest>();
for (int i = 0; i < THREADS; i++) {
objects.add(new SOTest());
}
//warm up
for (SOTest t : objects) {
getRunnable(t).run();
}
long start = System.nanoTime();
for (SOTest t : objects) {
executor.submit(getRunnable(t));
}
executor.shutdown();
executor.awaitTermination(10, TimeUnit.SECONDS);
long end = System.nanoTime();
System.out.println(objects.get(0).id + " " + (end - start) / 1000000);
}
public static Runnable getRunnable(final SOTest object) {
Runnable r = new Runnable() {
@Override
public void run() {
for (int i = 0; i < ITERATIONS; i++) {
object.id++;
}
}
};
return r;
}
}
编辑
请参阅下面的3个方案的10次运行结果:
1 657 473 261 74
2 641 501 261 78
3 651 465 259 86
4 585 462 259 78
5 639 506 259 68
6 659 477 258 72
7 653 479 259 82
8 645 486 259 72
9 650 457 259 78
10 639 487 272 79
答案 0 :(得分:10)
明确(错误)分享
由于内存中的布局,对象共享缓存行... 已经解释了很多次(甚至在这个网站上):这里有一个good source供进一步阅读。该问题适用于C#just as much(或C / C ++)
当您通过添加注释掉的行来填充对象时,共享会更少,您会看到性能提升。
编辑:我错过了第二个问题:
该print语句如何改变性能(特别是从那以后) 它出现在性能测量窗口之后)?
我想没有足够的变暖,打印GC和编译日志,这样你就可以确定没有干扰并且代码实际上是编译的。 java -server
需要10k次迭代,最好不要在主循环中全部生成良好的代码。
答案 1 :(得分:2)
您正在尝试执行硬件的微妙效果。您SOTest对象的内存非常小,因此所有4个实例都可以放入内存中的同一缓存行。由于您使用的是volatile,因此会导致不同内核之间的缓存丢弃(只有一个内核可能会使缓存行变脏)。
当你在ArrayList中发表评论时,内存布局会发生变化(在SOTest实例之间创建ArrayList),而volatile字段现在会进入不同的缓存行。 CPU的问题消失了,因此性能急剧上升。
证明:注释掉ArrayList并改为:
long waste1, waste2, waste3, waste4, waste5, waste6, waste7, waste8;
这会将您的SOTest对象扩大64个字节(奔腾处理器上一个缓存行的大小)。性能现在与.List中的ArrayList相同。
答案 2 :(得分:1)
这只是一个想法,我不知道如何验证它,但这可能与缓存有关。随着ArrayList的出现,你的对象变得更大,因此它们中较少的数量适合某些给定的缓存内存区域,从而导致更多的缓存未命中。
在实际可以尝试的事情上是使用不同大小的ArrayLists,从而改变类实例的内存占用量,看它是否对性能产生影响。
答案 3 :(得分:1)
非常有趣的旅程。这更像是“这是我的结果答案”。我怀疑/希望其他人会提出更好的回应。
你显然正在寻找一些有趣的优化点。我怀疑在objects.get(0).id
语句中添加println
会删除对id
字段使用的一些优化。除++
之外,id
没有其他用法,因此优化器可能会优化对volatile id
的一些访问,从而提高速度。只需使用id
访问long x = objects.get(0).id;
字段,效果就会相同。
List
字段更有趣。如果添加了字段private String foo = new String("weofjwe");
但不是,如果private String foo = "weofjwe";
没有创建对象,则会发生相同的性能提升,因为"..."
是在编译时完成的。我肯定 final
是相关的,但似乎不是。我只能推测这与构造函数优化有关,添加了new
导致优化被停止,尽管我会volatile
会更有效地完成。