【问题标题】:Store data inside long number or class instance for better performance将数据存储在长数字或类实例中以获得更好的性能
【发布时间】:2016-11-15 10:11:25
【问题描述】:

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

  • 移动玩家颜色。
  • 移动部件。
  • 棋盘上的原点位置。
  • 棋盘上的目标位置。
  • 通过执行此动作杀死的棋子(如果有的话)。
  • 移动得分。

另外,我得到了一些描述amove的方法,例如IsResignedUndo等。

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

为了降低性能影响,我考虑了以下解决方案: 我不会使用 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);
}

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

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

【问题讨论】:

  • 你的性能有多糟糕,你真的想要优化它?你甚至会看到差异。这将是我的第一个问题。是否值得考虑...
  • Alpha Beta 搜索在 4 步(5-7 秒~)的深度上挣扎。所以我试图找到让我的算法表现更好的方法,因为用户看不到计算机“思考”(这是一个交互式游戏,而不是经典棋盘)。我从实现 Zobrist Hash 开始,这很有帮助,但现在我想知道这种尝试是否可行。
  • 应用程序的内存占用情况如何?首先观察内存是如何变化的(即使在 Windows 默认任务管理器中)。
  • @Evk 我不是在为内存问题而苦苦挣扎,而是在为性能苦苦挣扎。我想知道使用长数字是否会比类实例更有效。无论如何,我会马上回家并发布它需要多少内存,谢谢!

标签: c# performance artificial-intelligence


【解决方案1】:

这里有几件事要说:

  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 一样)才值得。然后你仍然需要通过个人资料确认你的表现确实得到了改善。

【讨论】:

  • 你的最后一段让我思考是否值得努力将我的 aStar 实现中的节点从类更改为结构。在类形式中,它们在自身中存储父节点、g 和 fCost,这些在构造后应用。如果我删除那些不断变化的变量并将它们作为令牌存储在字典中并使用结构节点。这可能会提高性能吗?有时我会得到多达几百万个需要评估的节点,最初的小型测试表明,结构构造比类构造快 6 倍。
  • 答案是:这通常取决于,但要小心许多结构实例。实例化很快,因为您不需要堆。问题是:您如何存储这些实例?如果将它们存储在数组中,则可能没问题,因为数组存储在堆上。否则,您将在堆栈上拥有数百万个项目。而且您将无法选择在不装箱的情况下存储对父级的引用,这太慢了,您最好首先创建一个类实例。
  • 这是拳击吗? Dictionary childParentmap;它们存储在一个使用一维数组作为基础的堆中。
  • 这不会进行装箱,但最好不要为每个节点创建一个。
  • 呵呵,不,一本字典可以处理所有的关系
猜你喜欢
  • 2022-09-28
  • 2011-01-30
  • 1970-01-01
  • 2012-11-03
  • 2016-08-13
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多