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

Ale*_*ink 8 .net c# collections

我正在构建一个使用相对较大的表来完成其工作的应用程序(确切地说是LR表).因为我正在生成代码并且表格不是那么大,所以我决定通过生成使用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,(...)
    },
    (...)
Run Code Online (Sandbox Code Playgroud)

奇怪的是,当我生成一个只有几十万个条目的表时,我生成的应用程序在启动时因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);
Run Code Online (Sandbox Code Playgroud)

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

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

使用文字ints或strings代替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)
Run Code Online (Sandbox Code Playgroud)

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

Jon*_*eet 10

为什么会发生这种情况

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

我该如何解决呢?

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


Mar*_*ell 9

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

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

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

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

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