【问题标题】:Optimization of moves calculation in Othello (Bitboard)Othello (Bitboard) 中移动计算的优化
【发布时间】:2011-05-10 01:23:25
【问题描述】:

我一直致力于在 Java 中实现游戏奥赛罗,我将用于研究目的,我需要有一个快速的实现来运行尽可能多的游戏,所以这就是我所做的:

Board[][] 实现

我的第一种方法是使用 board[][] 多维数组来表示板,只是为了让它工作,并确保它正常工作。我完成了,我很高兴。但是,正如大多数人所知,这并不是很快,因为我每秒只能玩 1,500 场比赛(随机移动,只是为了测试)。

比特板

然后,我将棋盘实现为 BitBoard,这大大加快了移动计算,并且与之前的实现相比速度快得惊人。通过这种实现,它每秒可以玩多达 20k 游戏。这是我用于移动计算的代码,效果很好,但重复了:

private void calculateMoves() {
    legal = 0L;
    long potentialMoves;
    long currentBoard = getCurrentBoard();
    long opponentBoard = getOpponentBoard();
    long emptyBoard = emptyBoard();
    // UP
    potentialMoves = (currentBoard >> SIZE) & DOWN_MASK & opponentBoard;
    while (potentialMoves != 0L) {
        long tmp = (potentialMoves >> SIZE) & DOWN_MASK;
        legal |= tmp & emptyBoard;
        potentialMoves = tmp & opponentBoard;
    }
    // DOWN
    potentialMoves = (currentBoard << SIZE) & UP_MASK & opponentBoard;
    while (potentialMoves != 0L) {
        long tmp = (potentialMoves << SIZE) & UP_MASK;
        legal |= tmp & emptyBoard;
        potentialMoves = tmp & opponentBoard;
    }
    // LEFT
    potentialMoves = (currentBoard >> 1L) & RIGHT_MASK & opponentBoard;
    while (potentialMoves != 0L) {
        long tmp = (potentialMoves >> 1L) & RIGHT_MASK;
        legal |= tmp & emptyBoard;
        potentialMoves = tmp & opponentBoard;
    }
    // RIGHT
    potentialMoves = (currentBoard << 1L) & LEFT_MASK & opponentBoard;
    while (potentialMoves != 0L) {
        long tmp = (potentialMoves << 1L) & LEFT_MASK;
        legal |= tmp & emptyBoard;
        potentialMoves = tmp & opponentBoard;
    }
    // UP LEFT
    potentialMoves = (currentBoard >> (SIZE + 1L)) & RIGHT_MASK & DOWN_MASK & opponentBoard;
    while (potentialMoves != 0L) {
        long tmp = (potentialMoves >> (SIZE + 1L)) & RIGHT_MASK & DOWN_MASK;
        legal |= tmp & emptyBoard;
        potentialMoves = tmp & opponentBoard;
    }
    // UP RIGHT
    potentialMoves = (currentBoard >> (SIZE - 1L)) & LEFT_MASK & DOWN_MASK & opponentBoard;
    while (potentialMoves != 0L) {
        long tmp = (potentialMoves >> (SIZE - 1L)) & LEFT_MASK & DOWN_MASK;
        legal |= tmp & emptyBoard;
        potentialMoves = tmp & opponentBoard;
    }
    // DOWN LEFT
    potentialMoves = (currentBoard << (SIZE - 1L)) & RIGHT_MASK & UP_MASK & opponentBoard;
    while (potentialMoves != 0L) {
        long tmp = (potentialMoves << (SIZE - 1L)) & RIGHT_MASK & UP_MASK;
        legal |= tmp & emptyBoard;
        potentialMoves = tmp & opponentBoard;
    }
    // DOWN RIGHT
    potentialMoves = (currentBoard << (SIZE + 1L)) & LEFT_MASK & UP_MASK & opponentBoard;
    while (potentialMoves != 0L) {
        long tmp = (potentialMoves << (SIZE + 1L)) & LEFT_MASK & UP_MASK;
        legal |= tmp & emptyBoard;
        potentialMoves = tmp & opponentBoard;
    }
    moves.clear();
}

然后我试着用这种方式清理一下代码:

private MoveFinder[] finders = new MoveFinder[] {new UpFinder(), new DownFinder(), new LeftFinder(),
        new RightFinder(), new UpLeftFinder(), new UpRightFinder(), new DownLeftFinder(), new DownRightFinder()};

private void calculateMoves() {
    legal = 0L;
    long potentialMoves;
    long currentBoard = getCurrentBoard();
    long opponentBoard = getOpponentBoard();
    long emptyBoard = emptyBoard();
    for (MoveFinder finder : finders) {
        potentialMoves = finder.next(currentBoard) & opponentBoard;
        while (potentialMoves != 0L) {
            long tmp = finder.next(potentialMoves);
            legal |= tmp & emptyBoard;
            potentialMoves = tmp & opponentBoard;
        }
    }
    moves.clear();
}

private interface MoveFinder {
    long next(long next);
}

private class UpFinder implements MoveFinder {
    @Override
    public long next(long n) {
        return (n >> SIZE) & DOWN_MASK;
    }
}

private class DownFinder implements MoveFinder {
    @Override
    public long next(long n) {
        return (n << SIZE) & UP_MASK;
    }
}

// and so on for the rest of the moves (LeftFinder, RightFinder, etc).

出于某种原因,使用这种方法我每秒只能运行 15k 游戏!为什么这段代码慢得多? Java 方法调用成本高吗?是因为从数组中调用和对象吗?是因为我为每个方法传递了 long n 的副本吗?

任何想法都会很棒,因为我并不擅长优化代码。

谢谢!

【问题讨论】:

  • 我使用了 JProfiler,50% 的时间都花在了移动计算上,但我不知道为什么调用方法更慢,我在分析器中看不到这一点。有什么想法吗?
  • 您应该对结果持保留态度。你见过How do I write a correct micro-benchmark in Java?
  • 不,我没看过那个,我会看的,谢谢!

标签: java optimization


【解决方案1】:

Java 字节码与本机 asm 代码完全不同。当您使用接口和多态性时,它的执行方式与在代码中的执行方式相同。方法调用也是如此。

Java 字节码生成过程中没有进行任何优化,因此速度较慢。函数调用比重复的内联代码慢,后期绑定比静态绑定慢。

但是,我比第一个更喜欢您的第二个代码,我相信它应该保持这样。

【讨论】:

  • 是的,我也喜欢第二种方法。但问题是,对于我需要做的事情(模拟),我需要它尽可能快。 :S
【解决方案2】:

使用数组不应该增加那么多开销。

我认为方法调用可能会增加最大的开销,尤其是如果它们没有得到优化的话。

您可以尝试类似原始代码的方法并使用方法调用,而不用封装它们的类,看看性能是否更好;我似乎记得 Java 优化 getter 和 setter,所以可能将调用的方法放在一个类中会阻止它进行类似的优化。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2021-08-06
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2020-10-21
    • 2020-09-15
    • 1970-01-01
    相关资源
    最近更新 更多