c# - 大型集合初始值设定项启动时的 Stackoverflow

标签 c# .net collections

我正在构建一个应用程序,它使用相对较大的表来完成其工作(准确地说是 LR tables)。因为无论如何我都在生成代码并且表不是那么大,所以我决定通过生成代码来序列化我的表,该代码使用 C# 集合初始化程序语法在我生成的程序启动时初始化表:

public static readonly int[,] gotoTable = new int[,]
{
    {
        0,1,0,0,0,0,0,0,0,0,0,0,0,0,(...)
    },
    {
        0,0,4,0,5,6,0,0,0,0,0,7,0,0,(...)
    },
    (...)

奇怪的是,当我生成一个只有几十万个条目的表时,我生成的应用程序在启动时崩溃并出现 StackOverflowException。 C# 编译器可以很好地编译它;表格生成应用程序也运行良好。事实上,当我切换到 Release 模式时,应用程序确实启动了。 OutOfMemoryException 可能有一定意义,但即便如此,我使用的表对于 OutOfMemoryException 来说还是太小了。

重现代码:

警告:在 Release模式下尝试以下代码会使我的 Visual Studio 2010 崩溃;当心丢失未保存的工作。此外,如果您生成的代码编译器会生成大量错误,Visual Studio 也会挂起。

//Generation Project, main.cs:
using (StreamWriter writer = new StreamWriter("../../../VictimProject/Tables.cs"))
{
    writer.WriteLine("using System;");
    writer.WriteLine("public static class Tables");
    writer.WriteLine("{");
    writer.WriteLine("    public static readonly Tuple<int>[] bigArray = new Tuple<int>[]");
    writer.WriteLine("    {");
    for (int i = 0; i < 300000; i++)
        writer.WriteLine("        new Tuple<int>(" + i + "),");
    writer.WriteLine("    };");
    writer.WriteLine("}");
}
//Victim Project, main.cs:
for (int i = 0; i < 1234; i++)
{
    // Preventing the jitter from removing Tables.bigArray
    if (Tables.bigArray[i].Item1 == 10)
        Console.WriteLine("Found it!");
}
Console.ReadKey(true);

为 Tables.cs 文件运行第一个项目,然后运行第二个程序以获取 StackOverflowException。请注意,以上内容在我的计算机上崩溃:它可能不会在不同的平台等上发生;如果没有,请尝试增加 300000。

使用 Release模式而不是 Debug模式似乎会稍微增加限制,因为我的项目不会在 Release模式下崩溃。但是,上面的代码对我来说在两种模式下都会崩溃。

使用文字 int s 或 string s 而不是 Tuple<int> s 不会导致崩溃,"new int()"也不会(但它可能会转换为文字 0)。使用具有单个 int 的结构字段确实导致了崩溃。这似乎与使用构造函数作为初始值设定项有关。

我的猜测是集合初始值设定项以某种方式递归实现,这可以解释堆栈溢出。然而,这是一件非常奇怪的事情,因为迭代解决方案似乎更简单、更有效。 C# 编译器本身对程序没有任何问题并且编译速度非常快(它可以很好地处理更大的集合,但正如预期的那样,它确实会在非常大的集合上崩溃)。

我想可能有某种方法可以将我的表直接写入二进制文件,然后链接该文件,但我还没有看过。

我想我有两个问题:为什么会发生上述情况,我该如何解决?

编辑:反汇编 .exe 后的一些有趣细节:

.maxstack  4
.locals init ([0] class [mscorlib]System.Tuple`1<int32>[] CS$0$0000)
IL_0000:  ldc.i4     0x493e0
IL_0005:  newarr     class [mscorlib]System.Tuple`1<int32>
IL_000a:  stloc.0
IL_000b:  ldloc.0
IL_000c:  ldc.i4.0
IL_000d:  ldc.i4.0
IL_000e:  newobj     instance void class [mscorlib]System.Tuple`1<int32>::.ctor(!0)
IL_0013:  stelem.ref
IL_0014:  ldloc.0
IL_0015:  ldc.i4.1
IL_0016:  ldc.i4.1
IL_0017:  newobj     instance void class [mscorlib]System.Tuple`1<int32>::.ctor(!0)
IL_001c:  stelem.ref
(goes on and on)

这表明 jitter 确实因试图 jit 这个方法的堆栈溢出而崩溃。尽管如此,它确实很奇怪,特别是我从中得到了一个异常(exception)。

最佳答案

why does the above happen

我怀疑可能是 JIT 崩溃了。您将生成一个巨大 类型的初始值设定项(IL 中的 .cctor 成员)。每个值将是 5 条 IL 指令。对于拥有 150 万条指令的成员会导致问题,我并不感到惊讶...

and how do I work around it?

改为将数据包含到嵌入式资源文件中,并在需要时将其加载到类型初始值设定项中。我假设这是生成的数据 - 所以将数据放在它所属的位置,放在二进制文件中而不是文字代码中。

关于c# - 大型集合初始值设定项启动时的 Stackoverflow,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/10132374/

相关文章:

.net - 具有纹理的 CUDA 中的 GPU 性能

c# - C#.NET内存泄漏:GC阶段1和阶段2运行时的锯齿状内存使用情况

java - 实现我自己的锻炼助手应用程序 - Collection 注意事项

c# - '在使用 C# ASP.NET 时 WebClient 请求期间发生异常

c# - 将复杂文本作为字符串获取

c# - .NET Garbage Collector的疑惑

.net - 是否有可用的 IL 反汇编器图标的描述

vector - 检查 Vec 是否包含来自另一个 Vec 的所有元素

c# - 将通用 Action<T> 委托(delegate)添加到列表

c# - 异步读取图像文件