Warning: file_get_contents(/data/phpspider/zhask/data//catemap/2/.net/23.json): failed to open stream: No such file or directory in /data/phpspider/zhask/libs/function.php on line 167

Warning: Invalid argument supplied for foreach() in /data/phpspider/zhask/libs/tag.function.php on line 1116

Notice: Undefined index: in /data/phpspider/zhask/libs/function.php on line 180

Warning: array_chunk() expects parameter 1 to be array, null given in /data/phpspider/zhask/libs/function.php on line 181
C# NET阵列的开销?_C#_.net_Arrays_Overhead - Fatal编程技术网

C# NET阵列的开销?

C# NET阵列的开销?,c#,.net,arrays,overhead,C#,.net,Arrays,Overhead,我试图使用以下代码确定.NET阵列(在32位进程中)上的头的开销: long bytes1 = GC.GetTotalMemory(false); object[] array = new object[10000]; for (int i = 0; i < 10000; i++) array[i] = new int[1]; long bytes2 = GC.GetTotalMemory(false); array[0] = null; // ensure no

我试图使用以下代码确定.NET阵列(在32位进程中)上的头的开销:

long bytes1 = GC.GetTotalMemory(false);
object[] array = new object[10000];
    for (int i = 0; i < 10000; i++)
        array[i] = new int[1];
long bytes2 = GC.GetTotalMemory(false);
array[0] = null; // ensure no garbage collection before this point

Console.WriteLine(bytes2 - bytes1);
// Calculate array overhead in bytes by subtracting the size of 
// the array elements (40000 for object[10000] and 4 for each 
// array), and dividing by the number of arrays (10001)
Console.WriteLine("Array overhead: {0:0.000}", 
                  ((double)(bytes2 - bytes1) - 40000) / 10001 - 4);
Console.Write("Press any key to continue...");
Console.ReadKey();
在32位进程中,对象[1]的大小应该与int[1]的大小相同,但实际上开销增加了3.28个字节,达到

    237568
    Array overhead: 15.755
有人知道为什么吗


(顺便说一句,如果有人好奇的话,非数组对象的开销,例如上面循环中的(object)i,大约是8字节(8.384)。我听说64位进程的开销是16字节。)

因为堆管理(因为处理getTotalMemy)只能分配相当大的块,后者由较小的块分配,用于CLR的程序员目的

我认为您在测量时做出了一些错误的假设,因为循环期间的内存分配(通过GetTotalMemory)可能与仅阵列的实际所需内存不同-内存可能分配在更大的块中,内存中可能有其他对象在循环期间回收,等等

以下是有关阵列开销的一些信息:


数组是一种引用类型。所有引用类型都带有两个额外的单词字段。类型引用和SyncBlock索引字段,除其他外,用于在CLR中实现锁。因此,引用类型的类型开销是32位上的8字节。除此之外,数组本身还存储另一个4字节的长度。这使总开销达到12字节

我刚刚从Jon Skeet的回答中了解到,引用类型的数组有额外的4字节开销。这可以使用WinDbg进行确认。结果表明,附加字是存储在数组中的类型的另一个类型引用。引用类型的所有数组在内部存储为
object[]
,并附加对实际类型的type对象的引用。因此
string[]
实际上只是一个
对象[]
,带有对类型
string
的附加类型引用。详情请参阅下文

数组中存储的值:引用类型的数组保存对对象的引用,因此数组中的每个条目都是引用的大小(即32位上的4个字节)。值类型数组内联存储值,因此每个元素将占用所讨论类型的大小

这一问题也可能令人感兴趣:

血淋淋的细节

考虑以下代码

var strings = new string[1];
var ints = new int[1];

strings[0] = "hello world";
ints[0] = 42;
连接WinDbg显示以下内容:

首先,让我们看一下值类型数组。

0:000> !dumparray -details 017e2acc 
Name: System.Int32[]
MethodTable: 63b9aa40
EEClass: 6395b4d4
Size: 16(0x10) bytes
Array: Rank 1, Number of elements 1, Type Int32
Element Methodtable: 63b9aaf0
[0] 017e2ad4
    Name: System.Int32
    MethodTable 63b9aaf0
    EEClass: 6395b548
    Size: 12(0xc) bytes
     (C:\Windows\assembly\GAC_32\mscorlib\2.0.0.0__b77a5c561934e089\mscorlib.dll)
    Fields:
          MT    Field   Offset                 Type VT     Attr    Value Name
    63b9aaf0  40003f0        0         System.Int32  1 instance       42 m_value <=== Our value

0:000> !objsize 017e2acc 
sizeof(017e2acc) =           16 (        0x10) bytes (System.Int32[])

0:000> dd 017e2acc -0x4
017e2ac8  00000000 63b9aa40 00000001 0000002a <=== That's the value
首先,我们转储数组和字符串。接下来,我们转储
字符串[]
的大小。请注意,WinDbg在此处将类型列为
System.Object[]
。本例中的对象大小包括字符串本身,因此总大小是数组中的20加上字符串的40

通过转储实例的原始字节,我们可以看到以下内容:首先是SyncBlock,然后是
object[]
的方法表,然后是数组的长度。之后,我们找到了额外的4个字节,并引用了字符串的方法表。如上图所示,这可以通过dumpmt命令进行验证。最后,我们找到对实际字符串实例的单个引用

总之

阵列的开销可以分解如下(即在32位上)

  • 4字节同步块
  • 数组本身的方法表(类型引用)为4字节
  • 4字节表示数组的长度
  • 引用类型数组增加了另外4个字节来保存实际元素类型的方法表(引用类型数组是
    object[]
    在引擎盖下)

即,对于值类型数组,开销为12字节;对于引用类型数组,开销为16字节

很抱歉,今天早上我发现了关于内存过热的有趣信息

我们有一个处理大量数据(高达2GB)的项目。作为主要存储,我们使用
字典
。事实上,成千上万的字典被创造出来了。将键更改为
List
,将值更改为
List
(我们自己实现了
IDictionary
),内存使用率降低了约30-40%

为什么?

这里有一个稍微简洁(IMO)的简短但完整的程序来演示同样的事情:

using System;

class Test
{
    const int Size = 100000;

    static void Main()
    {
        object[] array = new object[Size];
        long initialMemory = GC.GetTotalMemory(true);
        for (int i = 0; i < Size; i++)
        {
            array[i] = new string[0];
        }
        long finalMemory = GC.GetTotalMemory(true);
        GC.KeepAlive(array);

        long total = finalMemory - initialMemory;

        Console.WriteLine("Size of each element: {0:0.000} bytes",
                          ((double)total) / Size);
    }
}
我们的结局如下:

Variables:
x=(0x1f228c8) <System.Object[]>
y=(0x1f228dc) <System.String[]>
z=(0x1f228f0) <System.Int32[]>

Memory:
0x1f228c4: 00000000 003284dc 00000001 00326d54 00000000 // Data for x
0x1f228d8: 00000000 003284dc 00000001 00329134 00000000 // Data for y
0x1f228ec: 00000000 00d443fc 00000001 12345678 // Data for z
这就是我前面提到的协方差。现在考虑到每一个任务都会发生这种情况,减少间接操作的数量是有意义的。特别是,我怀疑您不是真的想通过为每个assignment转到type对象来获取元素类型来破坏缓存。我怀疑(我的x86程序集不够好,无法验证这一点)测试类似于:

  • 要复制的值是否为空引用?如果是的话,没关系。(完成)
  • 获取引用点所在对象的类型指针
  • 该类型指针与元素类型指针(简单二进制相等检查)相同吗?如果是的话,没关系。(完成)
  • 该类型指针赋值是否与元素类型指针兼容?(检查要复杂得多,涉及继承和接口。)如果是这样,没关系——否则,抛出异常
如果我们可以在前三个步骤中终止搜索,那么就不会有太多的间接寻址——这对于像数组赋值一样频繁发生的事情是有好处的。对于值类型分配,这些都不需要发生,因为这是静态可验证的

所以,这就是为什么我认为引用类型数组比值类型数组稍大


好问题-深入研究它真的很有趣:)

这是在调试还是发布版本中?嗯,我实际上不知道,我在使用SnippetCompiler。当我切换到VisualStudio时,结果略有变化:int[1]为11.92,object[1]为15.94,无论
using System;

class Test
{
    const int Size = 100000;

    static void Main()
    {
        object[] array = new object[Size];
        long initialMemory = GC.GetTotalMemory(true);
        for (int i = 0; i < Size; i++)
        {
            array[i] = new string[0];
        }
        long finalMemory = GC.GetTotalMemory(true);
        GC.KeepAlive(array);

        long total = finalMemory - initialMemory;

        Console.WriteLine("Size of each element: {0:0.000} bytes",
                          ((double)total) / Size);
    }
}
object[] x = new object[1];
string[] y = new string[1];
int[] z = new int[1];
z[0] = 0x12345678;
lock(z) {}
Variables:
x=(0x1f228c8) <System.Object[]>
y=(0x1f228dc) <System.String[]>
z=(0x1f228f0) <System.Int32[]>

Memory:
0x1f228c4: 00000000 003284dc 00000001 00326d54 00000000 // Data for x
0x1f228d8: 00000000 003284dc 00000001 00329134 00000000 // Data for y
0x1f228ec: 00000000 00d443fc 00000001 12345678 // Data for z
object[] x = new object[1];
object[] y = new string[1];
x[0] = new object(); // Valid
y[0] = new object(); // Invalid - will throw an exception