0

背景:我打算将我编写的库从 C++ 移植到 Java。该代码处理大小为nd维点列表,并且需要计算标量积等。我想让我的代码独立于点的存储格式,并为此目的引入了一个接口,

public interface PointSetAccessor
{
  float coord(int p, int c);
}

这允许我获得第p个点 (0 ≤ p < n ) 的第c个坐标 (0 ≤ c < d ) 。

问题:points[p][c]由于代码必须非常快,我想知道这points是否会降低性能,这与直接访问模式(

令人惊讶的是,情况正好相反:代码(见下文通过PointSetAccessor. (我使用它进行time java -server -XX:+AggressiveOpts -cp bin Speedo了测量,前者大约 14 秒,后者大约 11 秒。)

问题:知道为什么会这样吗?似乎 Hotspot 决定更积极地优化,或者在后一个版本中更自由地这样做?

代码(计算无意义):

public class Speedo
{
  public interface PointSetAccessor
  {
    float coord(int p, int c);
  }

  public static final class ArrayPointSetAccessor implements PointSetAccessor
  {
    private final float[][] array;

    public ArrayPointSetAccessor(float[][] array)
    {
      this.array = array;
    }

    public float coord(int point, int dim)
    {
      return array[point][dim];
    }
  }

  public static void main(String[] args)
  {
    final int n = 50000;
    final int d = 10;

    // Generate n points in dimension d
    final java.util.Random r = new java.util.Random(314);
    final float[][] a = new float[n][d];
    for (int i = 0; i < n; ++i)
      for (int j = 0; j < d; ++j)
        a[i][j] = r.nextFloat();

    float result = 0.0f;
    if (true)
    {
      // Direct version
      for (int i = 0; i < n; i++)
        for (int j = i + 1; j < n; ++j)
        {
          float prod = 0.0f;
          for (int k = 0; k < d; ++k)
            prod += a[i][k] * a[j][k];
          result += prod;
        }
    }
    else
    {
      // Accessor-based version
      final PointSetAccessor ac = new ArrayPointSetAccessor(a);
      for (int i = 0; i < n; i++)
        for (int j = i + 1; j < n; ++j)
        {
          result += product(ac, d, i, j);
        }
    }
    System.out.println("result = " + result);
  }

  private final static float product(PointSetAccessor ac, int d, int i, int j)
  {
    float prod = 0.0f;
    for (int k = 0; k < d; ++k)
      prod += ac.coord(i, k) * ac.coord(j, k);
    return prod;
  }
}
4

2 回答 2

5

如此短的方法,如果它们很热(默认设置调用超过 10,000 次),将被热点内联,因此您应该不会注意到性能差异(您测量性能的方式忽略了许多影响,例如预热时间例如,这可能导致错误的结果)。

当运行您的代码并要求热点显示内联-server -XX:+UnlockDiagnosticVMOptions -XX:+PrintCompilation -XX:+PrintInliningcoord内容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)
于 2013-04-17T16:43:19.817 回答
2

如果你真的担心性能,你应该调查一下摆脱二维数组(用一维数组代替它)会给你带来什么。

java 中的多维数组比大多数其他语言更昂贵,因为 java 将它们实现为数组数组(即 N 维,任何小于 N 的维都是对下一维的引用数组)。

对于您的 float[50000][10],这意味着有一个包含 50000 个对 float[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];
    }
}

我希望访问器在不平凡的情况下会花费一点性能(例如,当接口具有多个实现时)。但是无论如何都要使用访问器接口 - 灵活性和可维护性通常比性能的几个百分点更值得。

于 2013-04-17T17:05:16.463 回答