C#内存开销从何而来

C#内存开销从何而来,c#,memory,C#,Memory,我这里有点资源问题。似乎.NET正在产生大量的内存开销,并且/或者没有释放出它本不需要的内存。但问题在于: 我有一个对象,它读取以下类的STL文件: public class cSTLBinaryDataModel { public byte[] header { get; private set; } public UInt32 triangleCount { get { return Convert.ToUInt32(triangleList.Count); } }

我这里有点资源问题。似乎.NET正在产生大量的内存开销,并且/或者没有释放出它本不需要的内存。但问题在于:

我有一个对象,它读取以下类的STL文件:

public class cSTLBinaryDataModel
{
    public byte[] header { get; private set; }
    public UInt32 triangleCount { get { return Convert.ToUInt32(triangleList.Count); } }
    public List<cSTLTriangle> triangleList { get; private set; }

    public  cSTLBinaryDataModel()
    {
        header = new byte[80];
        triangleList = new List<cSTLTriangle>();
    }

    public void ReadFromFile(string in_filePath)
    {
        byte[] stlBytes;
//Memory logpoint 1
        stlBytes = File.ReadAllBytes(in_filePath);
//Memory logpoint 2
        ReadHeader(stlBytes.SubArray(0, cConstants.BYTES_IN_HEADER));
        ReadTriangles(stlBytes.SubArray(cConstants.BYTES_IN_HEADER, stlBytes.Length - cConstants.BYTES_IN_HEADER));
//Evaluate memory logpoints here
    }

    private void ReadHeader(byte[] in_header)
    {
        header = in_header;
    }

    private void ReadTriangles(byte[] in_triangles)
    {
        UInt32 numberOfTriangles = BitConverter.ToUInt32(cHelpers.HandleLSBFirst(in_triangles.SubArray(0, 4)), 0);
//Memory logpoint 3
        for (UInt32 i = 0; i < numberOfTriangles; i++)
        {
            triangleList.Add(new cSTLTriangle(in_triangles.SubArray(Convert.ToInt32(i * cConstants.BYTES_PER_TRIANGLE + 4), Convert.ToInt32(cConstants.BYTES_PER_TRIANGLE))));
        }
//Memory logpoint 4
    }
}
cVector
为:(为这么多代码感到抱歉)

如果我计算我的类中使用的类型的大小,那么对于
cSTLTriangle
的一个实例,它总共是51个字节。我知道必须有一个开销来容纳功能等。但是,如果我将这个大小乘以三角形的数量,结果是512,3MB,这与实际的文件大小非常一致。我可以想象
三角形列表
占用的内存量大致相同(同样考虑到轻微的开销,它是一个
列表
无),但不是!(使用GC.GetTotalMemory(false)计算内存)

从Logpoint 1到Logpoint 2,增加了526660800字节,这相当准确地表示加载到字节数组中的STL文件的大小。 Logpoint 3和Logpoint 2之间的增量大致相同,这是可以理解的,因为我将子数组传递给
ReadTriangles
方法。
子数组
是我在这里找到的代码,所以(这可能是伪装中的魔鬼吗?)

公共静态T[]子数组(此T[]数据,int索引,int长度)
{
T[]结果=新的T[长度];
复制(数据、索引、结果、0、长度);
返回结果;
}
事情在下一个关键时刻变得荒谬可笑。在Logpoint 4和Logpoint 3之间,内存使用量大约增加了原始STL文件大小的4.73倍(如您所见,在解析每个三角形时,我大量使用
.SubArray

当我让程序继续运行时,内存使用没有显著增加:好,但也没有减少:坏。我希望保存文件的
字节[]
会释放内存,因为它超出了范围,我传递给
ReadTriangles(字节[]…)
的子数组也超出了范围,但不知怎的,它们没有。我最终得到的“开销”是原始STL数据大小的5.7倍

这是正常的行为吗?NET运行时是否会像Photoshop一样,在获得一些jucy RAM后保持内存分配(即使已扩展到磁盘)?如何减少此类组合的内存占用?

编辑:

  • 问题发生在Win7 Enterprise x64上。NET 4.5,为x64编译(也为AnyCPU编译)
  • 在对象创建完成后(在对象本身之外),我调用了
    GC.Collect()
    ,但什么也没有发生。只有在将对象引用设置为
    null
    之后,我才恢复了内存

在logpoint 2之后,也许您可以尝试将代码拆分一点,这样您就有了一个

byte[] header
byte[] triangles
在分割完原始字节数组后,将其设置为null,然后可以使用
System.GC.Collect()
强制垃圾收集器运行。这将为您节省一点内存

 public void ReadFromFile(string in_filePath)
    {
        byte[] stlBytes;
//Memory logpoint 1
        stlBytes = File.ReadAllBytes(in_filePath);
//Memory logpoint 2
        byte[] header = stlBytes.SubArray(0, cConstants.BYTES_IN_HEADER);
        byte[] triangles = stlBytes.SubArray(cConstants.BYTES_IN_HEADER, stlBytes.Length - cConstants.BYTES_IN_HEADER);
        ReadHeader(header);
        ReadTriangles(triangles);
        stlBytes = null;
        System.GC.Collect();
//Evaluate memory logpoints here
    }

有几件事可以尝试减少内存使用

首先,如果可能的话,您应该重写文件加载代码,以便它只加载所需的数据,而不是一次加载整个文件

例如,可以将标题作为单个块读取,然后将每个三角形的数据作为单个块(在循环中)读取

第二,您的大对象堆可能正在经历碎片化,垃圾收集器不会移动大对象,因此无法对其进行碎片化。(如果.Net 4.51修复了此问题,则必须显式启用大型对象堆碎片整理,并显式启动它。)

您可以通过预先调整
三角形列表的大小来缓解此问题

此时,您依次将每个三角形添加到
三角形列表
,从一个容量为零的列表开始。这意味着每隔一段时间列表的容量就会被超过,导致列表被扩展

当列表处于满负荷状态时,通过向列表中添加项目来展开列表时,它将:

  • 创建两倍于当前缓冲区大小的新内部缓冲区
  • 将旧缓冲区复制到新缓冲区
  • 删除旧的缓冲区
  • 将新项复制到新缓冲区
问题有两个方面:

  • 大量冗余复制正在进行
  • 如果内部缓冲区超过将对象放入大型对象堆的阈值,则可能会得到堆碎片
  • 由于您事先知道三角形列表的最大大小,因此可以通过在添加项目之前设置列表的容量来解决此问题:

    triangleList.Capacity = numberOfTriangles;
    

    内存开销

    您的
    cVector
    类增加了大量内存开销。在32位系统上,如果我没有记错的话,任何引用对象都有12个字节的内存开销(尽管其中4个字节可以由字段自由使用)。让我们使用8字节的开销。因此,对于10000000个三角形,每个三角形包含4个向量,这将upp添加到:

    10000000*4*8=305 MB的开销

    如果您在64位系统上运行,则该值是以下值的两倍:

    10000000*4*16=610 MB的开销

    除此之外,您还有四个参考的开销,每个
    cSTLTriangle
    将有四个参考向量,为您提供:

    10000000*4*4=152 MB(32位)

    10000000*4*8=305 MB(64位)

    正如您所看到的,所有这些都会增加相当大的开销

    因此,在本例中,我建议您将
    cVector
    a
    struct
    。如注释中所述,结构可以实现接口(以及属性和方法)。请注意@Jcl提到的那个一点

    您的
    cSTLTriangle
    类也有同样的问题(对于32-b,大约有76/152 MB的开销)
    byte[] header
    byte[] triangles
    
     public void ReadFromFile(string in_filePath)
        {
            byte[] stlBytes;
    //Memory logpoint 1
            stlBytes = File.ReadAllBytes(in_filePath);
    //Memory logpoint 2
            byte[] header = stlBytes.SubArray(0, cConstants.BYTES_IN_HEADER);
            byte[] triangles = stlBytes.SubArray(cConstants.BYTES_IN_HEADER, stlBytes.Length - cConstants.BYTES_IN_HEADER);
            ReadHeader(header);
            ReadTriangles(triangles);
            stlBytes = null;
            System.GC.Collect();
    //Evaluate memory logpoints here
        }
    
    triangleList.Capacity = numberOfTriangles;