在大型集合初始化程序启动时的Stackoverflow

时间:2012-04-12 21:48:41

标签: 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也是小的。

重现此代码的代码:

警告 :在发布模式下尝试下面的代码让我崩溃了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。

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

使用文字intstring而不是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)

这表明抖动确实因堆栈溢出而崩溃,试图使用这种方法。尽管如此,它确实很奇怪,特别是我从中得到了一个例外。

2 个答案:

答案 0 :(得分:10)

  

为什么会发生上述情况

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

  

我该如何解决它?

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

答案 1 :(得分:9)

如果它试图将所有这些预先推入堆栈,那么它将需要一个质量的堆栈空间,所以我个人确实希望堆栈溢出在这里,这取决于编译器的方式做到了。

之前已经做过类似事情(因为IL太大了,因为IL太大了,因为IL太大了),我的经验建议是:通过序列化而不是通过c#来做。在我的情况下,我通过protobuf-net,即

做了很多
  • 生成模型(无数据)作为代码
  • 执行它以从数据库填充模型
  • 将其序列化为文件
  • 使用我的部署
  • 发送文件
  • 在初始化期间反序列化

但是 - 我似乎记得最近有这个讨论;如果它与你自己,那么我完全支持我以前的言论。您尝试这样做的方式是仍然有问题。上述方法(来自直接经验)非常有效。作为IL?没那么多。

注意:如果你绝对想要在没有执行步骤的情况下编写文件,那也是可能的 - 只是比较棘手。