我正在分析Java应用程序并发现对象分配发生的速度比我预期的要慢得多。我运行了一个简单的基准测试来尝试建立小对象分配的整体速度,我发现在我的机器上分配一个小对象(3个浮点数的向量)似乎需要大约200纳秒。我正在运行(双核)2.0 GHz处理器,因此大约有400个CPU周期。我想在此之前询问那些已经分析过Java应用程序的人,然后再预测这种速度。这对我来说似乎有点残忍和不寻常。毕竟,我认为像Java这样可以压缩堆并重定位对象的语言会让对象分配如下所示:
int obj_addr = heap_ptr;
heap_ptr += some_constant_size_of_object
return obj_addr;
....这是几行装配。至于垃圾收集,我没有分配或丢弃足够的对象来进行游戏。当我通过重用对象来优化我的代码时,我得到的性能大约为15纳秒/对象我需要处理而不是每个对象需要处理200 ns,因此重用对象可以极大地提高性能。我真的不想重用对象,因为这会使符号变得多毛(许多方法需要接受receptacle
参数而不是返回值)。
所以问题是:对象分配花费这么长时间是否正常?或者在我的机器上可能出现问题,一旦修复,可能会让我在这方面有更好的表现?小对象分配通常需要多长时间用于其他人,并且是否有典型值?我正在使用客户端机器,目前不使用任何编译标志。如果您的机器上的速度更快,那么您机器的JVM版本和操作系统是什么?
我意识到个人里程在性能方面可能会有很大差异,但我只想问我上面提到的数字是否正好在他们的正确球场。
答案 0 :(得分:4)
当对象很小且没有GC成本时,创建对象非常快。
final int batch = 1000 * 1000;
Double[] doubles = new Double[batch];
long start = System.nanoTime();
for (int j = 0; j < batch; j++)
doubles[j] = (double) j;
long time = System.nanoTime() - start;
System.out.printf("Average object allocation took %.1f ns.%n", (double) time/batch);
以-verbosegc
Average object allocation took 13.0 ns.
注意:没有发生GC。但是增加了大小,程序需要等待在GC中复制内存。
final int batch = 10 *1000 * 1000;
打印
[GC 96704K->94774K(370496K), 0.0862160 secs]
[GC 191478K->187990K(467200K), 0.4135520 secs]
[Full GC 187990K->187974K(618048K), 0.2339020 secs]
Average object allocation took 78.6 ns.
我怀疑你的分配相对较慢,因为你正在执行GC。解决此问题的一种方法是增加应用程序可用的内存。 (虽然这可能会延迟成本)
如果我使用-verbosegc -XX:NewSize=1g
Average object allocation took 9.1 ns.
答案 1 :(得分:2)
我不知道你如何衡量分配时间。它可能至少相当于
intptr_t obj_addr = heap_ptr;
heap_ptr += CONSTANT_SIZE;
if (heap_ptr > young_region_limit)
call_the_garbage_collector ();
return obj_addr;
但它比那更复杂,因为你必须填补obj_addr
;然后,可能会发生一些JIT compilation或class loading;很可能,前几个单词被初始化(例如,类指针和哈希码,可能涉及一些随机数生成......),并调用对象构造函数。它们可能需要同步等。
更重要的是,新分配的对象可能不在最近的一级缓存中,因此可能会发生一些缓存未命中。
因此,虽然我不是Java专家,但我并没有因你的措施而感到高兴。我相信分配新鲜对象会使代码更清晰,更易于维护,而不是尝试重用旧对象。
答案 2 :(得分:1)
是。你认为它应该做的和它实际做的之间的差异可能非常大。池化可能很混乱,但是当分配和垃圾收集占执行时间的很大一部分时,它肯定是可以的,池化是一个很大的胜利,在性能方面。
要汇总的对象是您在分配过程中经常通过堆栈样本找到的对象。
以下是C ++中这样的示例。在Java中,细节是不同的,但这个想法是相同的:
... blah blah system stuff ...
MSVCRTD! 102129f9()
MSVCRTD! 1021297f()
operator new() line 373 + 22 bytes
operator new() line 65 + 19 bytes
COpReq::Handler() line 139 + 17 bytes <----- here is the line that's doing it
doit() line 346 + 12 bytes
main() line 367
mainCRTStartup() line 338 + 17 bytes
KERNEL32! 7c817077()
V------ and that line shows what's being allocated
COperation* pOp = new COperation(iNextOp++, jobid);