Java:如何以极高的性能实现基于位置(x,y)的索引?

Java:如何以极高的性能实现基于位置(x,y)的索引?,java,arrays,performance,data-structures,hashmap,Java,Arrays,Performance,Data Structures,Hashmap,我有一个游戏板,它是一个正方形的二维空间,最多有64×64个瓷砖。每个磁贴都可以,但并不总是包含游戏块。作为生成未来可能状态以进行分析的AI算法的一部分,我需要能够: 在坐标x,y(每秒1m+次)插入和移除游戏碎片 使用坐标x,y(每秒1m+次)检查是否有游戏棋子 访问这些游戏(每秒超过1百万次) 克隆一块电路板(每秒超过100k次) 显而易见的解决方案是使用一个简单的二维数组来保存对对象的引用。虽然这在访问/删除数据块时易于使用且性能良好,但电路板克隆/生成却是一个主要的性能瓶颈。通过生成

我有一个游戏板,它是一个正方形的二维空间,最多有64×64个瓷砖。每个磁贴都可以,但并不总是包含游戏块。作为生成未来可能状态以进行分析的AI算法的一部分,我需要能够:

  • 在坐标x,y(每秒1m+次)插入和移除游戏碎片

  • 使用坐标x,y(每秒1m+次)检查是否有游戏棋子

  • 访问这些游戏(每秒超过1百万次)

  • 克隆一块电路板(每秒超过100k次)

显而易见的解决方案是使用一个简单的二维数组来保存对对象的引用。虽然这在访问/删除数据块时易于使用且性能良好,但电路板克隆/生成却是一个主要的性能瓶颈。通过生成所有这些板,我基本上达到了内存写入带宽上限

我需要克隆电路板,不能重复使用电路板,因为电路板中的更改只能发生在我正在使用的电路板上。因此,我需要找到一种方法来生成足够多的对象,以允许位置索引,同时确保它足够快。 从理论上讲,一个不制造新板,但使用和清洁过时板的解决方案可能会奏效,但它需要一种清洁成本低,同时提供廉价的O(1)访问、插入和移除的容器

我尝试过的事情:

  • 2d阵列方法(电路板创建达到内存带宽限制)

  • 长宽*高的1d阵列(电路板创建达到内存带宽限制)

  • 使用点包装类(插入和访问时间太长)索引片段的HashMap

  • 使用游戏对象列表而不是位置索引(访问时间太长,因为它是O(N))

  • 将地图分割成块,仅初始化包含对象的电路板部分(总体性能一般,整体不够好)

有没有其他解决方案我可以试试

等级制度 无论您选择什么解决方案,克隆所有64*64=4096个元素都太多了。由于更改很小,您可以使用分层不可变表示法,例如64乘以64数组,并始终仅克隆更改的部分:

Piece[][] getModifiedClone(Piece[][] original, int x, int y, Piece newPiece) {
    Piece[][] result = original.clone(); // clones 64 pointers only
    result[x] = result[x].clone(); // also clones 64 pointers only
    result[x][y] = newPiece;
    return result;
}
这将开销从64*64减少到64+64。这里不必遵循行/列结构。以某些索引位操作为代价,可以使用不同的结构。您不必将内部层次结构组织为2D;您可以使用3或4个级别,并将开销减少到16+16+16或8+8+8+8

微观优化
对象只有一种类型,但它有三个重要变量。这三个都是整数,经过更多的修改后总是,我得出的效果很好的解决方案是:

在启动时,创建一个由64x64游戏机阵列组成的预制空64x64板的巨大存储库

当我需要一块木板的时候。我使用一个静态递增int遍历存储库,该int在到达末尾时循环回零。 我选择我发现的第一块尚未使用的板,并将其设置为执行模拟的计划对象的工作板。我将此板标记为已使用(一个简单的布尔数组),以便其他对象不会尝试使用它。如果我找不到一个清晰的板,我就从头开始创建一个新的(非常罕见)

我反复浏览从我要克隆的板上获得的片段列表,并将它们放在我的板上

然后我做我的模拟,移动碎片等等

在我完成这个特定的模拟/电路板之后,我调用一个cleanup函数,该函数遍历我的片段列表,并将这些片段的对应值设置回null。然后,我将我的板标记为可供其他人再次使用

这最终效果很好。除了开始,我基本上不需要生成新的容器或任何我还没有使用过的对象,所以避免创建垃圾。克隆和清理电路板都是O(N),因为我只需要迭代各个部分,而不是整个电路板。按位置访问/检查和移动工件都是O(1),而且非常便宜


这种方法唯一真正的问题是,我不得不使用一个相当大的空电路板存储库,因为我的一些电路板比其他电路板寿命更长。这会造成持续的大内存占用。

如果我错了,有人可以纠正我,但64x64板x 64位引用x 100k/sec=每秒3.2 gig的对象分配,使用可用的最基本数据结构,垃圾收集器需要做大量工作。这只是董事会,没有考虑其中包含的任何内容。在我看来,对于纯Java应用程序来说,您的“需求”根本不现实。你需要像OpenCL这样的东西。每个游戏块对于棋盘上的每个点都是不同的实例,还是只有几种类型可以重用?一个棋盘可以容纳多少个游戏块?如果这个数字与瓷砖的数量相比是低的,你不能只存储碎片和它们的坐标吗?(例如,对于一个国际象棋游戏,你最多只能存储2*16个棋子,而不是64个)。@Michael这正是我需要比简单数据结构更好的东西的原因。我负担不起每个单板克隆的64x64对象分配。不过,我可以负担得起的分配只是瓷砖上有游戏件,因为这是一个小比例的瓷砖(始终在0-10%之间)。所以我正在寻找一个容器,它允许我这样做。@samabcde只有一种类型的对象,但它有三个重要的变量。这三个都是整数,始终是@tanyehzheng,而不是通过
x
y
索引,您可以通过
x
的三个最高有效位(使用
x>>3
)索引,然后通过其三个最低有效位(使用
x&7
)索引,
y
++
int pack(int a, int b, int c) {
    return (a << 22) + (b << 11) + (c << 0);
}

int unpackA(int packed) {
    return (packed >> 22) & 0x7FF;
}

int unpackB(int packed) {
    return (packed >> 11) & 0x7FF;
}

int unpackC(int packed) {
    return (packed >> 0) & 0x7FF;
}
int getPackedPieceAt(int[][][][] board, int x, int y) {
   return board[x >> 3][x & 7][y >> 3][y & 7];
}
int[][][][] getModifiedClone(int[][][][] original, int x, int y, int newPiece) {
    ... just like above