阵列访问优化

时间:2009-09-15 18:34:19

标签: java android arrays optimization

我在Java中有一个10x10数组,数组中的一些项目没有使用,我需要遍历所有元素作为方法的一部分。什么会更好:

  1. 使用2个for循环遍历所有元素并检查nulltype以避免错误,例如

    for(int y=0;y<10;y++){
        for(int x=0;x<10;x++){
           if(array[x][y]!=null)
                //perform task here
        }
    }
    
  2. 或者保留所有已使用地址的列表会更好吗?说一个点数的分类?

  3. 我没有提到的不同之处。

  4. 我期待任何答案:)

6 个答案:

答案 0 :(得分:5)

您尝试的任何解决方案都需要在尽可能类似生产条件的受控条件下进行测试。由于Java的本质,你需要稍微运用你的代码来获得可靠的性能统计数据,但我相信你已经知道了。

这就是说,你可以尝试一些事情,我已经用它来成功优化我的Java代码(但不是在Android JVM上

for(int y=0;y<10;y++){
    for(int x=0;x<10;x++){
       if(array[x][y]!=null)
            //perform task here
    }
}

在任何情况下都应该重新加入

for(int x=0;x<10;x++){
    for(int y=0;y<10;y++){
       if(array[x][y]!=null)
            //perform task here
    }
}

通常,您可以通过缓存行引用来提高性能。假设数组的类型为Foo[][]

for(int x=0;x<10;x++){
    final Foo[] row = array[x];
    for(int y=0;y<10;y++){
       if(row[y]!=null)
            //perform task here
    }
}

final与变量一起使用可以帮助JVM优化代码,但我认为现代JIT Java编译器在很多情况下可以自己弄清楚代码中是否更改了变量。另一方面,有时候这可能会更有效率,尽管我们肯定会进入微观优化领域:

Foo[] row;
for(int x=0;x<10;x++){
    row = array[x];
    for(int y=0;y<10;y++){
       if(row[y]!=null)
            //perform task here
    }
}

如果您不需要知道元素的索引以便对其执行任务,您可以将其写为

for(final Foo[] row: array){
    for(final Foo elem: row
       if(elem!=null)
            //perform task here
    }
}

您可以尝试的另一件事是展平数组并将元素存储在Foo[]数组中,以确保最大的引用位置。你没有内心循环需要担心,但是在引用特定的数组元素时需要做一些索引算法(而不是在整个数组上循环)。根据您的使用频率,它可能有益或无益。

由于大多数元素都不是null,因此将它们保存为稀疏数组对你没有好处,因为你失去了引用的局部性。

另一个问题是空测试。 null测试本身并不需要花费太多,但是跟随它的条件语句确实如此,因为你在代码中得到一个分支并且在错误的分支预测上浪费时间。你可以做的是使用一个“空对象”,任务可以执行,但相当于非操作或同等良性。根据您要执行的任务,它可能适用于您,也可能不适合您。

希望这有帮助。

答案 1 :(得分:1)

最好使用List而不是数组,尤其是因为您可能不使用整个数据集。这有几个好处。

  1. 您没有检查空值,也不会意外尝试使用空对象。
  2. 内存效率更高,因为你没有分配可能无法使用的内存。

答案 2 :(得分:1)

对于一百个元素,它可能不值得使用任何经典的稀疏数组 实现。但是,你没有说你的数组是多么稀疏,所以要对它进行分析,看看与你正在进行的任何处理相比,你花了多少时间来跳过空项目。

(正如Tom Hawtin - 提示)当你使用数组数组时,你应该尝试遍历每个数组的成员而不是循环遍历不同数组的相同索引。并非所有算法都允许您这样做。

for ( int x = 0; x < 10; ++x ) {
    for ( int y = 0; y < 10; ++y ) {
       if ( array[x][y] != null )
            //perform task here
    }
}

for ( Foo[] row : array ) {
    for ( Foo item : row ) {
       if ( item != null )
            //perform task here
    }
}

您可能还会发现使用null对象而不是测试null会更好,具体取决于您执行的操作的复杂程度。不要使用模式的多态版本 - 多态分派的成本至少与测试和分支一样多 - 但如果你在多个CPU上对具有零的对象求和的属性可能更快。

double sum = 0;

for ( Foo[] row : array ) {
    for ( Foo item : row ) {
       sum += item.value();
    }
}

至于什么适用于Android,我不确定;再次,您需要测试和分析任何优化。

答案 3 :(得分:0)

持有一个点列表将会“过度设计”这个问题。你有一个多维数组;迭代它的最好方法是使用两个嵌套的for循环。除非您可以更改数据的表示,否则它的效率大致相同。

请确保按行顺序排列,而不是列顺序。

答案 4 :(得分:0)

取决于矩阵的稀疏度/密度。

如果它是稀疏的,你最好存储一个点列表,如果它是密集的,则使用2D数组。如果介于两者之间,您可以使用存储子矩阵列表的混合解决方案。

无论如何,此实现细节应隐藏在类中,因此您的代码也可以随时在这些表示之间进行转换。

如果不对您的实际应用进行分析,我会阻止您解决任何这些解决方案。

答案 5 :(得分:0)

我同意使用null测试的数组是最好的方法,除非你期望稀疏填充的数组。

原因:

1-密集阵列的内存效率更高(列表需要存储索引)

2-密集阵列的计算效率更高(您只需将刚刚检索到的值与NULL进行比较,而不必从内存中获取索引)。

另外,一个小小的建议,但在Java中,尤其是你最好在可能的情况下使用一维数组伪造多维数组(2D中的方形/矩形阵列)。边界检查仅在每次迭代时发生一次,而不是两次。不确定这是否仍然适用于Android VM,但它传统上是一个问题。无论如何,如果循环不是瓶颈,你可以忽略它。