编译时字典或数组 C#

4
在C#中是否有可能在编译时创建一个常量字典或数组? 如果不行,那么在C或C++中是否可以这样做并编译成一个dll,以便将其引入到C#项目中?
我需要创建一个大约有1.3亿成员的字典,而合理快速地生成它会消耗很多时间。我想最快的方法是通过编程方式生成一个巨大的文本文件,并将其编译为一个常量数组一次,然后永远不再支付这个代价。但我在C#中找不到这个功能。我觉得我可以在C中做到这一点。
编辑1:如果我没错,这将占用大约1.5GB的RAM。许多有用的评论揭示了这将占据至少这么多磁盘空间作为exe,这可能无法部署。然而,我仍然对对于规模较小的问题的答案感兴趣。
编辑2:了解应用程序可能会回答很多问题。这是一个蒙特卡罗模拟器。一张牌是一个位,从2clubs =0x1到Aspades = (0x1<<52)。一手牌是一个ulong,其中有7位被设置为指示手中的牌。
这种存储方案使我能够使用比逻辑更为麻烦的位操作。例如,要检测同花,我可以屏蔽所有红桃,然后做popcount > 4,然后重复clubs、diamonds和spades。
然后你必须确定一手牌的牌力值。但如果你正在排列牌,这是微不足道的,因为高牌由排列步骤给出。如果你不在排列中间,那么确定等级就会更麻烦。这就是为什么按需生成值是不可取的原因。你必须在某个地方付出代价,但现场费用比前期费用高得多。
最终目标是将它们放入一个字典中,以便确定2手牌中的更好的一手是通过查找字典实现的。
最快的方法(我想到的)是将所有手/等级存储在tuplet数组中,然后执行ToDictionary()。缺点是数组显然没有重复检查。这意味着,在同花顺示例中,我必须检查是否存在同花顺,然后再推送到数组中,而在字典中,我可以先放直接同花顺,然后尝试推送同花。因此,你要么支付检查同花顺的成本,要么支付每次将1个项目推送到字典中的成本。
字典键是所有可能的7张牌扑克牌手。值是每个手的相对强度,例如,从同花顺到ace的值为零。

2
这个回答解决了你的问题吗?如何声明一个常量数组 - Wyck
1
你是否正在使用允许配置字典capacity构造函数?这可以加快初始化速度。 - Theodor Zoulias
1
你的计算机有足够的RAM来存储130万个词条的字典吗,而不需要使用内存分页技术? - Theodor Zoulias
2
你从哪里获取数据的原始来源?是数据库还是文本文件或其他? - PMF
PMF:如果这个能够工作,它将会是一个由程序生成的源代码文件。 - Major Major
显示剩余5条评论
2个回答

3

我更愿意尝试以下方法:

将字典在内存中创建一次,然后使用此方法序列化并保存到文件中。

这样,当您的应用程序启动时,您可以直接从文件加载字典。 这样一来,您就不会得到一个130MB的exe文件。 如果你真的希望它成为可执行文件的一部分,你仍然可以将其嵌入为二进制资源。


2
实现来看,反序列化字典的速度不应该比从头开始构建快多少。序列化数据不包括每个条目的哈希值,因此每个130万个键都必须在运行时进行哈希计算。如果键是(例如)长字符串,则这可能非常昂贵。 - Theodor Zoulias
1
该帖子还提到了ProtoBuf,据说它运行更快。不过我个人没有尝试过。 - StefanFFM
我对一个130MB的exe文件没有任何问题。但是你仍然是正确的,因为它实际上是一个1.5 GB的exe文件 - 还有一个大约20GB的文本文件。我没有意识到exe文件(显然)会如此之大,以至于a)它将很难部署和b)磁盘读取将成为一个巨大的瓶颈。至于反序列化,从架构上讲,我明白那更有意义。但它非常慢。 - Major Major

1
我进行了一些测量,以评估初始化这个大小的内存数据库的成本。
使用一个 Dictionary<ulong, int>
Dictionary<ulong, int> dict = new(130_000_000);
for (int i = 0; i < 130_000_000; i++) dict.Add((ulong)i, i);

结果:

Duration: 20,030 msec, Allocated: 3,640,002,808 bytes

使用一个SortedList<ulong, int>
SortedList<ulong, int> dict = new(130_000_000);
for (int i = 0; i < 130_000_000; i++) dict.Add((ulong)i, i);

结果:

Duration: 32,979 msec, Allocated: 1,560,003,920 bytes

使用两个数组(ulong[]int[]),将它们填充预先排序的数据,并打算使用Array.BinarySearch<T>方法。
ulong[] keys = new ulong[130_000_000];
int[] values = new int[130_000_000];
for (int i = 0; i < 130_000_000; i++) keys[i] = (ulong)i;
for (int i = 0; i < 130_000_000; i++) values[i] = i;

结果:

Duration: 1,707 msec, Allocated: 1,560,000,840 bytes

我的电脑相当老旧,只有4GB的内存,因此在高端机器上进行这些实验可能会得出明显不同的结果。
我的观察是:
  • 关于所需的内存,Dictionary 的成本更高,初始化的持续时间也很长。然而,它应该是最有效的选项,用于搜索操作。

  • SortedList 具有较低的内存成本,但初始化成本更高。搜索操作需要二进制搜索,因此它应该与下一个选项一样有效。

  • ulong[]/int[] 数组对具有较低的内存成本和较低的初始化成本。在一个包含130,000,000个元素的数组中进行二进制搜索最多需要28个 ulong 比较,因此它应该比 Dictionary 慢,但不会太慢。

我的结论是,你可能需要投入一些时间来研究和实现自定义数据结构,或寻找类似内存数据库的替代方案。内置于.NET标准库中的数据结构可能不足以处理如此重的负载,同时还要求快速初始化。

Theodor,非常感谢您的付出。逐个向字典添加1个条目非常繁琐。建立一个数组并进行todictionary可以快一个数量级。它使用约2倍的内存,但数组最终应该被垃圾回收,对吧? - Major Major
1
@MajorMajor 是的,数组将被垃圾回收,但是根据ToDictionary方法的实现,它不应比逐个添加更快。基本上,它在内部执行相同的操作。 - Theodor Zoulias
不确定为什么,但是将更多或更少的随机ulongs添加到字典中所需的时间比按照生成的顺序添加ulongs长一个数量级。仅添加所有可能的手牌,大约需要1.5秒枚举,就需要很长时间,我从未能让它完成——它的速度是10-30分钟。然而,dict.TryAdd(ulong.GetHashCode(), int)比dict.TryAdd(ulong, int)快10倍,我认为这可能是解决方案。 - Major Major

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接