我正在为我的益智游戏编写AI,我面临以下情况:
目前,我有一个类Move
,它代表我的游戏中的一个动作,它具有与国际象棋相似的逻辑。
在Move
课程中,我存储了以下数据:
此外,我有一些描述amove的方法,例如IsResigned
,Undo
等。
这个移动实例正在我的AI中传递,它基于Alpha Beta算法。因此,移动实例被多次传递,并且我在AI实现中构建了许多Move
类实例。因此,我发现它可能会对性能产生重大影响。
为了降低inpact的性能,我考虑了以下解决方案:
我不是使用Move
类的实例,而是将我的整个移动数据存储在一个长数字中(使用按位运算),然后根据需要提取信息。
例如: - 玩家颜色将来自位1 - 2(1位)。 - Oirign位置将来自位2 - 12(10位)。 等等。
见这个例子:
public long GenerateMove(PlayerColor color, int origin, int destination) {
return ((int)color) | (origin << 10) | (destination << 20);
}
public PlayerColor GetColor(long move) {
return move & 0x1;
}
public int GetOrigin(long move) {
return (int)((move >> 10) & 0x3f);
}
public int GetDestination(long move) {
return (int)((move >> 20) & 0x3f);
}
使用这种方法,我可以传递AI只是长数,而不是类实例。 但是,我有一些奇迹:抛开程序增加的复杂性,类实例通过引用在C#中传递(即通过发送指向该地址的指针)。那么我的替代方法是否有意义呢?情况更糟,因为我在这里使用长数字(64bis),但指针地址可能表示为整数(32位) - 因此它甚至可能比我当前的实现具有最差的性能。
您对此替代方法有何看法?
答案 0 :(得分:3)
这里有几点要说:
struct
而不是class
)。但是你必须要注意值类型语义(你总是在拷贝上工作)。struct
,它由两个System.Collections.Specialized.BitVector32
个实例组成。这是一个内置的.NET类型,它将为您执行掩码和移位操作。在该结构中,您还可以封装访问属性中的值,因此您可以保持这种相当不寻常的方式将值存储在其他代码之外。<强>更新强>
我建议您使用分析器查看性能问题的位置。使用猜测来进行性能优化几乎是不可能的(而且肯定不能很好地利用你的时间)。一旦您看到了分析器结果,您可能会对您遇到问题的原因感到惊讶。我敢打赌,内存使用或内存分配不是它。
如果你真的得出结论,Move
实例的内存消耗是原因,使用小值类型可以解决问题(我很惊讶),不要使用Int64
,使用自定义结构(如6.中所述),如下所示,其大小与Int64
相同:
[System.Runtime.InteropServices.StructLayout( System.Runtime.InteropServices.LayoutKind.Sequential, Pack = 4 )]
public struct Move {
private static readonly BitVector32.Section SEC_COLOR = BitVector32.CreateSection( 1 );
private static readonly BitVector32.Section SEC_ORIGIN = BitVector32.CreateSection( 63, SEC_COLOR );
private static readonly BitVector32.Section SEC_DESTINATION = BitVector32.CreateSection( 63, SEC_ORIGIN );
private BitVector32 low;
private BitVector32 high;
public PlayerColor Color {
get {
return (PlayerColor)low[ SEC_COLOR ];
}
set {
low[ SEC_COLOR ] = (int)value;
}
}
public int Origin {
get {
return low[ SEC_ORIGIN ];
}
set {
low[ SEC_ORIGIN ] = value;
}
}
public int Destination {
get {
return low[ SEC_DESTINATION ];
}
set {
low[ SEC_DESTINATION ] = value;
}
}
}
但请注意,您现在正在使用值类型,因此必须相应地使用它。这意味着分配创建原始副本(即更改目标值将保持源不变),如果您想要保持子程序所做的更改并避免不惜任何代价进行装箱以防止性能更差(某些操作可能意味着装箱),则使用ref参数即使你没有立刻注意到,例如传递实现接口的struct
作为接口类型的参数)。使用结构体(以及使用Int64
)只有在创建大量临时值时才会有价值,而这些临时值很快就会丢失。然后,您仍然需要通过个人资料确认您的表现确实得到了提升。