【问题标题】:Iterate over offset for 2D array迭代二维数组的偏移量
【发布时间】:2017-09-14 15:13:35
【问题描述】:

我想遍历二维数组中单元格的邻居。所以我用:

for (int xOffset = -1; xOffset <= 1; xOffset++) {
    for (int yOffset = -1; yOffset <= 1; yOffset++) {
        if (xOffset == 0 && yOffset == 0) {
            continue;
        }
        //My Code
    }
}

我的问题是,这是否是最有效的方法,还是只使用 if 调用更好?如果有比在循环中使用这个 if 来摆脱零零情况更好的方法吗?

【问题讨论】:

  • 您认为最具可读性和可维护性的解决方案是什么?这里有实际的性能问题需要解决吗?
  • 是的,这是一种性能问题,我会搜索所有我能优化的东西。这是在数百万个对象的更新循环中运行的。
  • 那么,使用您当前的解决方案,性能是否存在实际问题?
  • 它是关于在每个滴答声中使用数百万次的事物中找到几纳秒或几毫秒
  • 我明白这一点,但 OP 正在询问一个可能存在也可能不存在的问题。也许这已经是最快的解决方案了。他们似乎已经有了一些不同的解决方案,所以我对 OP 的挑战就是与你的马赛跑。

标签: java performance for-loop if-statement


【解决方案1】:

尚未对其进行测试,但以下内容基于@Zefick 的回答,应该更高效:

int dirs[] = { -1, -1, -1, 0, -1, 1, 0, -1, 0, 1, 1, -1, 1, 0, 1, 1 };

for (int i = 0; i < dirs.length; i += 2) {
    int xOffset = dirs[i];
    int yOffset = dirs[i + 1];
    // my code
}       

【讨论】:

    【解决方案2】:

    我运行了一个 JMH 基准测试,其中包含该问题的各种解决方案。我总是在黑洞中消耗 2 个偏移量。结果如下:

    Benchmark                                  Mode  Cnt         Score         Error  Units
    Offset2DBenchmark.doubleLoopWithIf        thrpt    3  35425373,827 ± 4242716,439  ops/s
    Offset2DBenchmark.loopOverFlatIndex       thrpt    3  35929636,197 ±  935681,592  ops/s
    Offset2DBenchmark.loopOverIndex           thrpt    3  31438051,279 ± 3286314,668  ops/s
    Offset2DBenchmark.unrolledLoop            thrpt    3  40495297,238 ± 6423961,118  ops/s
    Offset2DBenchmark.unrolledLoopWithLambda  thrpt    3  27526581,417 ± 1712524,060  ops/s
    
    
    doubleLoopWithIf       = Nested Loops with If to filter 0,0 (TheSorm)
    loopOverFlatIndex      = Single loop with flattend indizes (Oleg)
    loopOverIndex          = Single Loop with 2d indizes (Zefick)
    unrolledLoop           = Completely Unrolled Loop
    unrolledLoopWithLambda = Unrolled Loop consuming a Bifunction<Integer, Integer>
    

    因此,展开的循环是最快的。稍慢的是带有 if 语句的双循环和 Oleg 提出的扁平数组。 Zefick 的 2D 数组甚至比您的解决方案还要慢。

    作为一个演示,下面是测试的样子:

    @Fork(value = 1)
    @Warmup(iterations = 3)
    @Measurement(iterations = 3)
    @Benchmark
    public void unrolledLoopWithLambda(Blackhole bh) {
        outerOffsets((x, y) -> {
            bh.consume(x);
            bh.consume(y);
        });
    }
    
    private void outerOffsets(BiConsumer<Integer, Integer> consumer) {
        consumer.accept(-1, -1);
        consumer.accept(-1, 0);
        consumer.accept(-1, 1);
        consumer.accept(0, -1);
        consumer.accept(0, 1);
        consumer.accept(1, -1);
        consumer.accept(1, 0);
        consumer.accept(1, 1);
    }
    

    因此,除非您想手动展开循环,否则您无法提高性能。

    不幸的是,您没有告诉使用循环内的代码是什么样的。如果它甚至有点耗时,那么你如何循环的问题可能会被忽略......

    但你的标题表明你希望这个作为二维数组的偏移量。我怀疑您可能会在 xy 的更大循环中使用它。您可以通过使用一维数组并自己计算索引来获得更多性能。

    代替:

    array[x][y]
    

    使用

    array[xy] with xy = x + y * xsize 
    

    你应该能够完全避免做乘法。

    (这是我第一次使用 JMH 运行基准测试,如果我完全错误地使用它,请告诉我......)

    【讨论】:

    • 哇,感谢您所做的所有工作 :) 对于展开循环,您的意思是只有一个 for 循环,不是吗?
    • 我的意思是展开循环,你根本不使用循环。您复制并粘贴您的代码,直到您有 8 个副本。然后硬编码 xOffset 和 yOffset 的值。应该没问题,因为你追求表现。就可维护性而言,它是最丑陋的。
    • 干得好,可能可以通过增加预热和迭代次数来减少误差偏差,但即使结果是有意义的,也表明这里没有太多可以优化的地方。
    【解决方案3】:

    我通常使用以下方法:

    int dirs[][] = {{-1, -1}, {-1, 0}, {-1, 1}, {0, -1}, {0, 1}, {1, -1}, {1, 0}, {1, 1}};
    
    for (int dir[] : dirs) {
        int xOffset = dir[0];
        int yOffset = dir[1];
        // my code
    }
    

    【讨论】:

    • ok 将 xOffset, yOffset 声明置于循环之外,但接缝是一件好事,谢谢 :)
    • @TheSorm 那会被 JIT 编译器(或者只是普通的编译器)优化,不需要这样做。
    • @Oleg 哦,好吧,我从不 shure 编译器做什么,所以我尝试自己做这样的事情来获得 shure :D
    • @TheSorm 根据这​​个答案,StackOverflow 上有很多关于它的问题:stackoverflow.com/a/407323/1398418 在循环内声明甚至会更快!一般来说,我建议专注于编写可读代码,仅在发现问题后进行微优化。
    • @Zefick 我发现即使我将 dirs 作为最终全局变量,您的实现速度也只有我的一半。不过还是谢谢你的帮助:)
    猜你喜欢
    • 1970-01-01
    • 2011-12-28
    • 2015-04-04
    • 2014-05-30
    • 2020-06-09
    • 1970-01-01
    • 1970-01-01
    • 2018-04-08
    • 2013-09-13
    相关资源
    最近更新 更多