gpt4 book ai didi

c# - 初始化大型锯齿状数组会占用超过 1 GB 的 RAM 并因 StackOverflowException 而崩溃

转载 作者:太空狗 更新时间:2023-10-29 18:34:43 27 4
gpt4 key购买 nike

当我编译这段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) .NET Framework 4.5 使用 Microsoft (R) Visual C# 编译器版本 4.0.30319.33440。我只是在命令行上调用 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# 中编译这样的代码时,数组初始化花费了很长的时间(~5 秒),甚至在使用 test() 的 for 循环开始之前执行。

我的实际代码已经缩减为上面的 MVCE,其中 struct Point 不包含任何字段,而 Main() 仅包含数组初始化,没有任何有用的工作。

最佳答案

好的,我开始编译你的类文件的调试/发布版本。使用 14.0 版工具中的 VS 2015 编译器,IL 的输出是相同的。这涵盖了人们没有注意到问题的原因。

在 VS 2013 中使用的先前编译器中的调试与发布非常直接。 Debug模式下的可执行文件输出为 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

此代码与 Release模式代码之间的主要区别是额外的 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 编译器中, Debug模式进行了大量的堆栈分配,可能是为了让您可以在编辑期间智能感知整个锯齿状数组结构并继续,或者可能这样您可能进入每个单独的对象构造。我对此完全不确定。

我不是 IL 方面的专家,但在我看来,它正在为每个点分配,然后为每个数组再次分配,然后再次为锯齿状数组分配,导致分配过多。

关于c# - 初始化大型锯齿状数组会占用超过 1 GB 的 RAM 并因 StackOverflowException 而崩溃,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/39583023/

27 4 0
Copyright 2021 - 2024 cfsdn All Rights Reserved 蜀ICP备2022000587号
广告合作:1813099741@qq.com 6ren.com