当我编译这段C#代码(full text)并运行ArrayTest.exe时,该进程会挂起几秒钟,消耗1 GB的RAM,并因StackOverflowException而崩溃。为什么呢?
public struct Point { }
public class ArrayTest {
public static void Main(string[] args) {
Point[][] array = {
new Point[]{new Point(), new Point(), /* ... 296 omitted ... */, new Point(), new Point()},
new Point[]{new Point(), new Point(), /* ... 296 omitted ... */, new Point(), new Point()},
/* ... 296 omitted ... */
new Point[]{new Point(), new Point(), /* ... 296 omitted ... */, new Point(), new Point()},
new Point[]{new Point(), new Point(), /* ... 296 omitted ... */, new Point(), new Point()},
};
/* Do nothing and return */
}
}
我正在使用Microsoft(R)Visual C#编译器版本4.0.30319.33440 for Microsoft(R).NET Framework 4.5。我只是在命令行上调用csc.exe并执行编译的EXE。添加csc /optimize
标志时问题消失了。上面的代码片段确实是我正在测试的整个代码 - 在初始化数组后,Main()中没有有用的工作。
问题上下文:我试图将一组数字测试用例硬编码到程序中。在Java,JavaScript或Python中,代码将无辜地看起来像这样并正常工作:
class Point { int x; int y; }
Point[][] data = { // About 1000 entries
{new Point(1, 2)},
{new Point(5, 3), new Point(0, 6), new Point(1, 8)}, // Different length
... et cetera ...
};
for (Point[] thing : data):
test(thing);
但是当我尝试在C#中编译这样的代码时,即使在test()
的for循环开始执行之前,数组初始化也需要相当长的时间(~5秒)。
我的实际代码已经减少到上面的MVCE,其中struct Point
不包含任何字段,Main()
只包含数组初始化,没有任何有用的工作。
答案 0 :(得分:7)
好的,我开始编译类文件的调试/发布版本。使用14.0版工具中的VS 2015编译器,IL的输出是相同的。这涵盖了人们没有注意到问题的原因。
在VS 2013中使用的先前编译器中的调试与发布非常相当。在调试模式下输出可执行文件是2,091 kb。发行版中的IL表明它只是忽略了实际的对象,因为它从未被使用过。好的。我将VS 2015 Debug IL与VS 2013 Debug IL进行比较。
为简洁起见,我已将数组大小更改为3x3。
以下是2015 IL的输出:
.method public hidebysig static void Main() cil managed
{
.entrypoint
// Code size 45 (0x2d)
.maxstack 4
.locals init (valuetype Point[][] V_0)
IL_0000: nop
IL_0001: ldc.i4.4
IL_0002: newarr valuetype Point[]
IL_0007: dup
IL_0008: ldc.i4.0
IL_0009: ldc.i4.3
IL_000a: newarr Point
IL_000f: stelem.ref
IL_0010: dup
IL_0011: ldc.i4.1
IL_0012: ldc.i4.3
IL_0013: newarr Point
IL_0018: stelem.ref
IL_0019: dup
IL_001a: ldc.i4.2
IL_001b: ldc.i4.3
IL_001c: newarr Point
IL_0021: stelem.ref
IL_0022: dup
IL_0023: ldc.i4.3
IL_0024: ldc.i4.3
IL_0025: newarr Point
IL_002a: stelem.ref
IL_002b: stloc.0
IL_002c: ret
} // end of method ArrayTest::Main
这与发布模式代码的主要区别在于附加的nop指令。
以下是2012/2013版本编译器的输出:
.method public hidebysig static void Main() cil managed
{
.entrypoint
// Code size 307 (0x133)
.maxstack 4
.locals init (valuetype Point[][] V_0,
valuetype Point[][] V_1,
valuetype Point[] V_2,
valuetype Point V_3)
IL_0000: nop
IL_0001: ldc.i4.4
IL_0002: newarr valuetype Point[]
IL_0007: stloc.1
IL_0008: ldloc.1
IL_0009: ldc.i4.0
IL_000a: ldc.i4.3
IL_000b: newarr Point
IL_0010: stloc.2
IL_0011: ldloc.2
IL_0012: ldc.i4.0
IL_0013: ldelema Point
IL_0018: ldloca.s V_3
IL_001a: initobj Point
IL_0020: ldloc.3
IL_0021: stobj Point
IL_0026: ldloc.2
IL_0027: ldc.i4.1
IL_0028: ldelema Point
IL_002d: ldloca.s V_3
IL_002f: initobj Point
IL_0035: ldloc.3
IL_0036: stobj Point
IL_003b: ldloc.2
IL_003c: ldc.i4.2
IL_003d: ldelema Point
IL_0042: ldloca.s V_3
IL_0044: initobj Point
IL_004a: ldloc.3
IL_004b: stobj Point
IL_0050: ldloc.2
IL_0051: stelem.ref
IL_0052: ldloc.1
IL_0053: ldc.i4.1
IL_0054: ldc.i4.3
IL_0055: newarr Point
IL_005a: stloc.2
IL_005b: ldloc.2
IL_005c: ldc.i4.0
IL_005d: ldelema Point
IL_0062: ldloca.s V_3
IL_0064: initobj Point
IL_006a: ldloc.3
IL_006b: stobj Point
IL_0070: ldloc.2
IL_0071: ldc.i4.1
IL_0072: ldelema Point
IL_0077: ldloca.s V_3
IL_0079: initobj Point
IL_007f: ldloc.3
IL_0080: stobj Point
IL_0085: ldloc.2
IL_0086: ldc.i4.2
IL_0087: ldelema Point
IL_008c: ldloca.s V_3
IL_008e: initobj Point
IL_0094: ldloc.3
IL_0095: stobj Point
IL_009a: ldloc.2
IL_009b: stelem.ref
IL_009c: ldloc.1
IL_009d: ldc.i4.2
IL_009e: ldc.i4.3
IL_009f: newarr Point
IL_00a4: stloc.2
IL_00a5: ldloc.2
IL_00a6: ldc.i4.0
IL_00a7: ldelema Point
IL_00ac: ldloca.s V_3
IL_00ae: initobj Point
IL_00b4: ldloc.3
IL_00b5: stobj Point
IL_00ba: ldloc.2
IL_00bb: ldc.i4.1
IL_00bc: ldelema Point
IL_00c1: ldloca.s V_3
IL_00c3: initobj Point
IL_00c9: ldloc.3
IL_00ca: stobj Point
IL_00cf: ldloc.2
IL_00d0: ldc.i4.2
IL_00d1: ldelema Point
IL_00d6: ldloca.s V_3
IL_00d8: initobj Point
IL_00de: ldloc.3
IL_00df: stobj Point
IL_00e4: ldloc.2
IL_00e5: stelem.ref
IL_00e6: ldloc.1
IL_00e7: ldc.i4.3
IL_00e8: ldc.i4.3
IL_00e9: newarr Point
IL_00ee: stloc.2
IL_00ef: ldloc.2
IL_00f0: ldc.i4.0
IL_00f1: ldelema Point
IL_00f6: ldloca.s V_3
IL_00f8: initobj Point
IL_00fe: ldloc.3
IL_00ff: stobj Point
IL_0104: ldloc.2
IL_0105: ldc.i4.1
IL_0106: ldelema Point
IL_010b: ldloca.s V_3
IL_010d: initobj Point
IL_0113: ldloc.3
IL_0114: stobj Point
IL_0119: ldloc.2
IL_011a: ldc.i4.2
IL_011b: ldelema Point
IL_0120: ldloca.s V_3
IL_0122: initobj Point
IL_0128: ldloc.3
IL_0129: stobj Point
IL_012e: ldloc.2
IL_012f: stelem.ref
IL_0130: ldloc.1
IL_0131: stloc.0
IL_0132: ret
} // end of method ArrayTest::Main
因此,在您正在使用的2012/2013编译器中,调试模式正在进行大量的堆栈分配,这可能使您可以在编辑期间智能感知整个锯齿状阵列结构并继续,或者可能使您可能步入每个单独的对象构造。我根本不确定这一点。
我不是IL的专家,但在我看来,它为每个Point分配,然后再为每个Array分配,然后再为锯齿状阵列分配,导致分配太多。