将数据存储在长数字或类实例中以提高性能

我在为我的拼图游戏编写AI时遇到了以下情况:目前,我有一个Move类,用于表示游戏中的一个移动,其逻辑类似于国际象棋。在Move类中,我存储了以下数据:

  • 移动玩家的颜色。
  • 移动的棋子。
  • 棋盘上的起始位置。
  • 棋盘上的目标位置。
  • 通过此移动被击杀的棋子(如果有)。
  • 移动的分数。

此外,我还有一些描述移动的方法,例如IsResignedUndo等。

这个移动实例在我的AI中被传递,AI基于Alpha Beta算法。因此,移动实例被多次传递,并且在AI实现过程中构造了许多Move类实例。因此,我担心这可能会对性能产生重大影响。

为了减少性能影响,我考虑了以下解决方案:不使用Move类的实例,而是将整个移动数据存储在一个长数字中(使用位操作),然后根据需要提取信息。

例如:- 玩家颜色将从第1位到第2位(1位)。- 起始位置将从第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);}

使用这种方法,我可以只传递长数字,而不是类实例。然而,我有一些疑问:撇开程序增加的复杂性不谈,类实例在C#中是按引用传递的(即发送到该地址的指针)。那么我的替代方法是否有意义?情况甚至更糟,因为我在这里使用的是长数字(64位),但指针地址可能表示为整数(32位) – 所以它甚至可能比我当前的实现性能更差。

您对这种替代方法有何看法?


回答:

这里有几点需要说明:

  1. 您是否真的遇到了性能问题(并且确定内存使用是原因)?在.net中,为新实例分配内存非常便宜,通常您不会注意到垃圾回收。因此,您可能是在错误的方向上努力。
  2. 当您传递引用类型的实例时,您只是传递一个引用;当您存储一个引用类型(例如在数组中)时,您只会存储引用。因此,除非您创建了许多不同的实例或将数据复制到新实例中,否则传递引用不会增加堆大小。因此,传递引用可能是最有效的方法。
  3. 如果您创建了许多副本并很快丢弃它们,并且您担心内存影响(再次,您是否面临实际问题?),您可以创建值类型(使用struct而不是class)。但您必须注意值类型语义(您总是在处理副本)。
  4. 您不能依赖引用是32位的。在64位系统上,它将是64位的。
  5. 我强烈建议不要将数据存储在整数变量中。这会使您的代码难以维护,在大多数情况下,这不值得性能上的权衡。除非您遇到了严重的问题,否则不要这样做。
  6. 如果您不想放弃使用数值的想法,至少使用一个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一样)只有在您创建了许多临时值并很快丢弃它们时才值得。然后,您仍然需要通过性能分析器确认您的性能实际上得到了改善。

Related Posts

L1-L2正则化的不同系数

我想对网络的权重同时应用L1和L2正则化。然而,我找不…

使用scikit-learn的无监督方法将列表分类成不同组别,有没有办法?

我有一系列实例,每个实例都有一份列表,代表它所遵循的不…

f1_score metric in lightgbm

我想使用自定义指标f1_score来训练一个lgb模型…

通过相关系数矩阵进行特征选择

我在测试不同的算法时,如逻辑回归、高斯朴素贝叶斯、随机…

可以将机器学习库用于流式输入和输出吗?

已关闭。此问题需要更加聚焦。目前不接受回答。 想要改进…

在TensorFlow中,queue.dequeue_up_to()方法的用途是什么?

我对这个方法感到非常困惑,特别是当我发现这个令人费解的…

发表回复

您的邮箱地址不会被公开。 必填项已用 * 标注