黑白棋(位棋盘)中的落子计算优化

3

我一直在使用Java实现黑白棋游戏,这是为了进行研究,并且需要一个快速的实现来运行尽可能多的游戏。所以这是我所做的:

Board[][]实现

我的第一个方法是使用board [][ ] 多维数组来表示棋盘,只是为了让它工作,并确保它正常工作。我完成了它,感到很高兴。但是,大多数人都知道,那不太快,因为我只能玩1500个游戏每秒(随机移动,仅供测试)。

Bitboard

然后,我将棋盘实现为BitBoard,这大大加快了移动计算速度,与先前的实现相比,速度非常快。使用此实现,它可以每秒播放多达20,000个游戏。这是我用于移动计算的代码,它非常好用,但重复:

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方法调用很昂贵吗?是因为从数组中调用对象吗?还是因为我为每个方法传递了长整型n的副本?如果您有任何想法,请告诉我,因为我不太擅长优化代码。谢谢!

我使用了JProfiler并发现50%的时间都花费在移动计算上,但是我无法弄清楚为什么调用一个方法会更慢,我在分析器中没有看到这个。你有什么想法吗? - David Robles
1
你应该对自己的结果保持高度警惕。你看过《如何在Java中编写正确的微基准测试?》吗?(https://dev59.com/hHRB5IYBdhLWcg3wz6UK) - Matt Ball
不,我还没有看过那个,我会阅读的,谢谢! - David Robles
2个回答

2

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

Java字节码生成过程中没有进行任何优化,所以它比较慢。函数调用比复制内联代码要慢,而晚期绑定比静态绑定要慢。

然而,我更喜欢您的第二段代码,认为应该保留。


是的,我也喜欢第二种方法。但问题是,对于我需要做的事情(模拟),我需要它尽可能快。:S - David Robles

1

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

我认为方法调用可能会添加最多的开销,特别是如果它们没有被优化掉。

您可以尝试像原始代码一样使用方法调用,而不使用包装它们的类,并查看性能是否有所改善;我记得Java优化getter和setter函数,因此将调用的方法放在类中可能会防止它进行类似的优化。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接