Java纯大型数组的存在会导致大量内存峰值
我目前正在编写一个UCI国际象棋引擎。在我的引擎中,我有一个大的转置表,它将键映射到值 在实现这一点时,我已经考虑到了内存,在表中放置某些内容时不想创建新对象,因此我首先用空对象填充整个表 代码如下所示:Java纯大型数组的存在会导致大量内存峰值,java,arrays,memory,heap,Java,Arrays,Memory,Heap,我目前正在编写一个UCI国际象棋引擎。在我的引擎中,我有一个大的转置表,它将键映射到值 在实现这一点时,我已经考虑到了内存,在表中放置某些内容时不想创建新对象,因此我首先用空对象填充整个表 代码如下所示: private TranspositionEntry[] entries; private int size; private int maxSize; private long hashMask; public TranspositionTable(int keyBits){ t
private TranspositionEntry[] entries;
private int size;
private int maxSize;
private long hashMask;
public TranspositionTable(int keyBits){
this.maxSize = (int)(Math.pow(2, keyBits));
this.hashMask = maxSize - 1;
this.entries = new TranspositionEntry[maxSize];
for(int i = 0; i < maxSize; i++){
this.entries[i] = new TranspositionEntry(0,0,0,0,0, new Move(0, 0, 0, 0));
}
}
private int index(long zobrist){
return (int)(zobrist & hashMask);
}
public boolean contains(long key){
int index = index(key);
return entries[index].getZobrist() != 0;
}
public void clear(){
for(int i = 0; i < maxSize; i++){
this.entries[i].setZobrist(0);
}
}
public int size(){
return size;
}
public TranspositionEntry get(long key){
System.out.println("I was called");
int index = index(key);
if(entries[index].getZobrist() == 0) return null;
return entries[index];
}
public void put(long key, double eval, int depthLeft, int node_tpe, int color, Move move){
int index = index(key);
System.out.println("I was called");
TranspositionEntry en = entries[index];
if(en.getZobrist() == 0) size++;
en.setVal(eval);
en.setDepthLeft(depthLeft);
en.setNode_type(node_tpe);
en.setColor(color);
en.getBestMove().setType(move.getType());
en.getBestMove().setFrom(move.getFrom());
en.getBestMove().setTo(move.getTo());
en.getBestMove().setPieceFrom(move.getPieceFrom());
en.getBestMove().setPieceTo(move.getPieceTo());
}
正如您所看到的,这个表本身确实消耗了大约100MB的内存
现在是有趣的部分: 当我运行代码为给定位置寻找最佳移动时,我 通常首先创建换位表(如果需要,则清除它) 已经存在)。但是我禁用了对转置的所有访问 除了
size()
之外的表。如上所示,表中有get/put
方法
使用print
-语句,它们在任何时候都不会被调用
当搜索位置时,VisualVM会给我这个内存输出
换位表已在开始时创建,但在开始时未使用
随时都可以
正如您所看到的,内存使用率从一开始就很高,现在已经收敛到相当低的水平。
我最初认为搜索本身在一开始就消耗了这么多内存(这没有意义)。所以我运行了完全相同的代码,除了这个代码>
输出如下所示:
private TranspositionEntry[] entries;
private int size;
private int maxSize;
private long hashMask;
public TranspositionTable(int keyBits){
this.maxSize = (int)(Math.pow(2, keyBits));
this.hashMask = maxSize - 1;
this.entries = new TranspositionEntry[maxSize];
for(int i = 0; i < maxSize; i++){
this.entries[i] = new TranspositionEntry(0,0,0,0,0, new Move(0, 0, 0, 0));
}
}
private int index(long zobrist){
return (int)(zobrist & hashMask);
}
public boolean contains(long key){
int index = index(key);
return entries[index].getZobrist() != 0;
}
public void clear(){
for(int i = 0; i < maxSize; i++){
this.entries[i].setZobrist(0);
}
}
public int size(){
return size;
}
public TranspositionEntry get(long key){
System.out.println("I was called");
int index = index(key);
if(entries[index].getZobrist() == 0) return null;
return entries[index];
}
public void put(long key, double eval, int depthLeft, int node_tpe, int color, Move move){
int index = index(key);
System.out.println("I was called");
TranspositionEntry en = entries[index];
if(en.getZobrist() == 0) size++;
en.setVal(eval);
en.setDepthLeft(depthLeft);
en.setNode_type(node_tpe);
en.setColor(color);
en.getBestMove().setType(move.getType());
en.getBestMove().setFrom(move.getFrom());
en.getBestMove().setTo(move.getTo());
en.getBestMove().setPieceFrom(move.getPieceFrom());
en.getBestMove().setPieceTo(move.getPieceTo());
}
正如您所见,搜索本身不会导致这些内存峰值
因此,我的问题是:
private double val;
private long zobrist;
private int depthLeft;
private int node_type;
private int color;
private Move bestMove;
public TranspositionEntry(long zobrist, double val, int depthLeft, int node_type, int color, Move bestMove) {
this.val = val;
this.zobrist = zobrist;
this.depthLeft = depthLeft;
this.node_type = node_type;
this.color = color;
this.bestMove = bestMove;
}
编辑2
我找到了解决这个问题的办法。
如果最大堆空间从开始就受到限制,则似乎不会出现峰值。
我添加了
xmx1024M
,这似乎解决了问题 除非您的表有很大一部分被填满(比如80%),否则您应该根据需要创建(和删除)条目以节省内存。Java中的对象存在,无论它们是否在数组(或其他对象)中引用,直到垃圾收集器清除它们时,在最后一个非弱引用被清除并且最后一个线程失去对该对象的访问之后。即使这样,垃圾收集器也可能需要很长时间来清理对象,这取决于它的设置。最重要的是,JVM不愿意在分配内存后返回内存,因为重新分配内存往往代价高昂。然而,以上所有内容都取决于实施情况
具体建议填写表格,编写一个ensureEntry
方法,根据需要在表格中创建一个对象,这样您就不必在启动时初始化它:
public transsitionEntry(整数索引){
TranspositionEntry=this.entries[index];
if(条目==null){
条目=新换位尝试(0,0,0,0,0,新移动(0,0,0,0));
此.entries[索引]=条目;
}
返回条目;
}
这种方法对于运行时来说是非常轻量级的,因为编译器很可能会将其内联,并且它在解决内存问题方面做了很多工作。只需使用它访问您的表
如果需要再次从表中删除条目,请编写类似的方法写入表,如果“零大小写”为真,则将arrayindex设置为null
。不要害怕null
!如果操作正确,空引用就是您的朋友。别忘了,他们就在那里
以及解决“为什么空数组会占用空间”的问题
对象数组(与基元类型数组相反)只分配足够的vm内存来存储它可能包含的所有引用,而不是实际对象的空间。因此,如果您创建一个数组来容纳100个
字符串
,那么您的数组将采用其自身开销+100个对象引用(无论它们是否存在)的内存大小。但是,引用的大小是OS和VM特定的,并且可能会有所不同,您需要为每个对象引用指定至少32位和(通常)最多64位的属性。因此,一个大小为100的字符串数组分配100*64+的开销位。条目的创建是在表的构造函数中完成的,为什么要使用超过1G的RAM?``公共转置尝试(长zobrist、双val、int depthLeft、int node_type、int color、Move bestMove){this.val=val;this.zobrist=zobrist;this.depthLeft=depthLeft;this.node_type=node_type;this.color=color;this.bestMove=bestMove;}“``它实际上只是复制了那些值,而不是将代码放在注释中,它是不可读的。只需编辑问题即可。谢谢!我这样做的同时还限制了最大堆大小,效果非常好:)
private double val;
private long zobrist;
private int depthLeft;
private int node_type;
private int color;
private Move bestMove;
public TranspositionEntry(long zobrist, double val, int depthLeft, int node_type, int color, Move bestMove) {
this.val = val;
this.zobrist = zobrist;
this.depthLeft = depthLeft;
this.node_type = node_type;
this.color = color;
this.bestMove = bestMove;
}