使用访问器/获取器可以更快吗? >背景>:>我计划从C++到java。代码处理大小为n的d维点列表,需要计算标量积等。我想让我的代码独立于点的存储格式,并为此目的引入了一个接口 公共接口点集访问器 { 浮动坐标(INTP、INTC); }
这允许我得到第c坐标(0≤ c使用访问器/获取器可以更快吗? >背景>:>我计划从C++到java。代码处理大小为n的d维点列表,需要计算标量积等。我想让我的代码独立于点的存储格式,并为此目的引入了一个接口 公共接口点集访问器 { 浮动坐标(INTP、INTC); },java,performance,jvm-hotspot,Java,Performance,Jvm Hotspot,这允许我得到第c坐标(0≤ c
points[p][c]
这样的直接访问模式相比,points
是一个由n个数组组成的数组,每个数组都保持d点坐标
令人惊讶的是,情况正好相反:通过PointSetAccessor
的“间接”访问,代码(见下文)的速度提高了20%。(我使用timejava-server-XX:+AggressiveOpts-cp bin Speedo测量了这个值,前者得到14秒左右,后者得到11秒左右。)
问题:知道为什么会这样吗?似乎Hotspot决定更积极地进行优化,或者在后一版本中有更大的自由度进行优化
代码(用于计算无意义):
公共级Speedo
{
公共接口点集访问器
{
浮动坐标(INTP、INTC);
}
公共静态最终类ArrayPointSetAccessor实现PointSetAccessor
{
专用最终浮点[][]数组;
公共阵列点集访问器(浮点[][]阵列)
{
this.array=数组;
}
公共浮动坐标(整数点,整数dim)
{
返回数组[点][dim];
}
}
公共静态void main(字符串[]args)
{
最终整数n=50000;
最终积分d=10;
//在维度d中生成n个点
final java.util.Random r=new java.util.Random(314);
最终浮动[][]a=新浮动[n][d];
对于(int i=0;i
如果这些短方法是热的(使用默认设置调用超过10000次),它们将由hotspot内联,因此您不应该注意性能的差异(测量性能的方式忽略了许多影响,例如预热时间,这可能会导致错误的结果)
当运行代码并要求hotspot显示什么是内联的(-server-XX:+UnlockDiagnosticVMOptions-XX:+PrintCompilation-XX:+PrintInline
)时,您会得到以下输出,这表明coord
和product
都是内联的:
76 1 % javaapplication27.Speedo::main @ -2 (163 bytes) made not entrant
77 6 javaapplication27.Speedo$ArrayPointSetAccessor::coord (9 bytes)
78 7 javaapplication27.Speedo::product (45 bytes)
@ 18 javaapplication27.Speedo$ArrayPointSetAccessor::coord (9 bytes) inline (hot)
@ 27 javaapplication27.Speedo$ArrayPointSetAccessor::coord (9 bytes) inline (hot)
80 2 % javaapplication27.Speedo::main @ 101 (163 bytes)
@ 118 javaapplication27.Speedo::product (45 bytes) inline (hot)
@ 18 javaapplication27.Speedo$ArrayPointSetAccessor::coord (9 bytes) inline (hot)
@ 27 javaapplication27.Speedo$ArrayPointSetAccessor::coord (9 bytes) inline (hot)
如果您真的担心性能,那么您应该研究去掉二维数组(用一维数组替换)会给您带来什么
java中的多维数组比大多数其他语言中的多维数组成本更高,因为java将它们实现为数组数组(即N维的数组,任何小于N维的数组都是对下一维的引用数组)
对于浮点数[50000][10],这意味着有一个包含50000个浮点数[10]引用的数组。因为每个数组也是一个对象(有几个字节的头)。由于最后一个维度非常小(10),因此在内存使用方面,开销非常大(反向大小写float[10][50000]的内存占用空间非常小)
尝试这样的内存布局:
public static final class ArrayPointSetAccessor implements PointSetAccessor {
private final int dimSize;
private final float[] array;
public ArrayPointSetAccessor(float[] array, int dimSize) {
this.dimSize = dimSize;
this.array = array;
}
public float coord(int point, int dim) {
return array[dim * dimSize + point];
}
}
我希望访问器在一个非平凡的场景中(例如,当接口有多个实现时)会降低一点性能。但无论如何还是要使用访问器界面——灵活性和可维护性通常比性能的几%更重要。最有可能的是,您的微基准存在缺陷。切换测试顺序和直接/间接,看看它对结果的影响有多大。为了避免预热问题,在测量之前至少执行一次整个测试套件。正确!在扩展程序以运行两次测试(在同一个VM中)并测量第二次调用的CPU时间之后,我确实看到有时这两个变量的速度相等。然而,有时并非如此——在这些情况下,PointSetAccessor
更快(在我的实验中)。我还没有发现为什么有时候“直接”方法会慢一些。谢谢,@Durandal。在此期间,我们了解了更多关于JVM基准测试的知识,应该使用类似的方法。感谢您的评论,我验证了这一点:使用一维数组确实会稍微快一点是的,内存占用更好。事实上,内联可以在编译之前发生,请参见。