将数据存储在长号或类实例中以获得更好的性能

时间:2016-11-15 10:11:25

标签: c# performance artificial-intelligence

我正在为我的益智游戏编写AI,我面临以下情况: 目前,我有一个类Move,它代表我的游戏中的一个动作,它具有与国际象棋相似的逻辑。 在Move课程中,我存储了以下数据:

  • 移动玩家的颜色。
  • 动人的作品。
  • 棋盘上的原点位置。
  • 主板上的目的地位置。
  • 通过执行此移动而被杀死的棋子(如果有的话)。
  • 移动得分。

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

这个移动实例正在我的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位) - 因此它甚至可能比我当前的实现具有最差的性能。

您对此替代方法有何看法?

1 个答案:

答案 0 :(得分:3)

这里有几点要说:

  1. 您确实遇到了性能问题(并且您确定内存使用是原因)吗?新实例的内存分配在.net中非常便宜,通常,您不会注意垃圾收集。所以你可能会在这里咆哮错误的树。
  2. 当您传递引用类型的实例时,您只是传递引用;当您存储引用类型(例如,在数组中)时,您将只存储引用。因此,除非您创建许多不同的实例或将数据复制到新实例中,否则传递引用不会增加堆大小。所以传递引用可能是最有效的方法。
  3. 如果您创建了大量副本并快速丢弃它们并且您害怕内存影响(同样,您是否面临实际问题?),您可以创建值类型(struct而不是class )。但是你必须要注意值类型语义(你总是在拷贝上工作)。
  4. 您不能依赖32位的引用。在64位系统上,它将是64位。
  5. 我强烈建议不要将数据存储在整数变量中。它使您的代码不易维护,并且在大多数情况下不值得性能权衡。除非您遇到严重麻烦,否则不要这样做。
  6. 如果您不想放弃使用数值的想法,请至少使用struct,它由两个System.Collections.Specialized.BitVector32个实例组成。这是一个内置的.NET类型,它将为您执行掩码和移位操作。在该结构中,您还可以封装访问属性中的值,因此您可以保持这种相当不寻常的方式将值存储在其他代码之外。
  7. <强>更新

    我建议您使用分析器查看性能问题的位置。使用猜测来进行性能优化几乎是不可能的(而且肯定不能很好地利用你的时间)。一旦您看到了分析器结果,您可能会对您遇到问题的原因感到惊讶。我敢打赌,内存使用或内存分配不是它。

    如果你真的得出结论,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)只有在创建大量临时值时才会有价值,而这些临时值很快就会丢失。然后,您仍然需要通过个人资料确认您的表现确实得到了提升。