我在这里遇到一点资源问题。似乎.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
}
}
我的STL文件很大(但可以变得更大);它包含10533050个三角形,因此在磁盘上大小约为520 MB。添加到cSTLTriangle
的课程triangleList
如下:
public class cSTLTriangle
{
public cVector normalVector { get; private set; }
public cVector[] vertices { get; private set; }
public UInt16 attributeByteCount { get; private set; }
public bool triangleFilledWithExternalValues { get; private set; }
public cSTLTriangle(byte[] in_bytes)
{
Initialize();
normalVector = new cVector(BitConverter.ToSingle(cHelpers.HandleLSBFirst(in_bytes.SubArray(0, 4)), 0),
BitConverter.ToSingle(cHelpers.HandleLSBFirst(in_bytes.SubArray(4, 4)), 0),
BitConverter.ToSingle(cHelpers.HandleLSBFirst(in_bytes.SubArray(8, 4)), 0));
vertices[0] = new cVector(BitConverter.ToSingle(cHelpers.HandleLSBFirst(in_bytes.SubArray(12, 4)), 0),
BitConverter.ToSingle(cHelpers.HandleLSBFirst(in_bytes.SubArray(16, 4)), 0),
BitConverter.ToSingle(cHelpers.HandleLSBFirst(in_bytes.SubArray(20, 4)), 0));
vertices[1] = new cVector(BitConverter.ToSingle(cHelpers.HandleLSBFirst(in_bytes.SubArray(24, 4)), 0),
BitConverter.ToSingle(cHelpers.HandleLSBFirst(in_bytes.SubArray(28, 4)), 0),
BitConverter.ToSingle(cHelpers.HandleLSBFirst(in_bytes.SubArray(32, 4)), 0));
vertices[2] = new cVector(BitConverter.ToSingle(cHelpers.HandleLSBFirst(in_bytes.SubArray(36, 4)), 0),
BitConverter.ToSingle(cHelpers.HandleLSBFirst(in_bytes.SubArray(40, 4)), 0),
BitConverter.ToSingle(cHelpers.HandleLSBFirst(in_bytes.SubArray(44, 4)), 0));
attributeByteCount = BitConverter.ToUInt16(cHelpers.HandleLSBFirst(in_bytes.SubArray(48, 2)), 0);
triangleFilledWithExternalValues = true;
}
public cSTLTriangle(cVector in_vertex1, cVector in_vertex2, cVector in_vertex3)
{
Initialize();
vertices[0] = in_vertex1;
vertices[1] = in_vertex2;
vertices[2] = in_vertex3;
normalVector = cVectorOperations.CrossProduct(cVectorOperations.GetDirectionVector(vertices[0], vertices[1]), cVectorOperations.GetDirectionVector(vertices[0], vertices[2]));
}
/// <summary>
/// Resets object to a defined state
/// </summary>
private void Initialize()
{
vertices = new cVector[3];
//from here on not strictly necessary, but it helps with resetting the object after an error
normalVector = new cVector(0, 0, 0);
vertices[0] = new cVector(0, 0, 0);
vertices[1] = new cVector(0, 0, 0);
vertices[2] = new cVector(0, 0, 0);
attributeByteCount = 0;
triangleFilledWithExternalValues = false;
}
}
课程cVector
是:(抱歉这么多代码)
public class cVector:ICloneable
{
public float component1 { get; set; }
public float component2 { get; set; }
public float component3 { get; set; }
public double Length { get { return Math.Sqrt(Math.Pow(component1, 2) + Math.Pow(component2, 2) + Math.Pow(component3, 2)); } }
public cVector(float in_value1, float in_value2, float in_value3)
{
component1 = in_value1;
component2 = in_value2;
component3 = in_value3;
}
public object Clone()
{
return new cVector(component1, component2, component3);
}
}
如果我计算我的类中使用的类型的大小,则cSTLTriangle
的一个实例的数量为51个字节。我知道必须有一个开销来容纳功能等。但是,如果我将此大小乘以三角形的数量,我最终会达到512,3 MB,这与实际文件大小非常一致。我会想象triangleList
占用大致相同的内存量(再次允许轻微的开销,但仍然是List<T>
),但不是! (使用GC.GetTotalMemory(false)来评估内存)
从Logpoint 1到Logpoint 2,增加了526660800字节,这非常准确地是加载到字节数组中的STL文件的大小。
在Logpoint 3和Logpoint 2之间增加了大致相同的数量,这是可以理解的,因为我将一个子数组传递给ReadTriangles
方法。 SubArray
是我在SO上找到的代码(这可能是伪装的恶魔吗?):
public static T[] SubArray<T>(this T[] data, int index, int length)
{
T[] result = new T[length];
Array.Copy(data, index, result, 0, length);
return result;
}
下一个Logpoint的事情变得荒谬可笑。在Logpoint 4和Logpoint 3之间,内存使用量大约是原始STL文件大小的4.73倍(正如您所看到的,我在解析每个三角形时大量使用.SubArray
。)
当我让程序继续运行时,内存使用量没有显着增加:好,但也没有减少:糟糕。我希望保持文件的byte[]
释放内存,因为它超出范围,我传递给ReadTriangles(byte[] ...)
的子数组也是如此,但不知何故他们没有。 我的最终结果是原始STL数据大小的5.7倍。
这是通常的行为吗? .NET运行时是否保持内存分配(即使它已经扩展到磁盘),就像Photoshop一样,一旦它获得了一些jucy RAM?如何减少这种类组合的内存占用?
编辑:
GC.Collect()
(所以在对象本身之外)并没有发生任何事情。只有在将对象引用设置为null
后才能恢复内存答案 0 :(得分:3)
内存开销
您的cVector
类添加很多的内存开销。在32位系统上,任何参考对象的内存开销 12字节(尽管如果可能,其中4个可以由字段自由使用),如果我没记错的话。让我们用8字节的开销。因此,在您的情况下,10,000,000个三角形,每个包含4个向量,将upp添加到:
10,000,000 * 4 * 8 = 305 MB的开销
如果你在64位系统上运行,那就是它的两倍:
10,000,000 * 4 * 16 = 610 MB的开销
除此之外,你还有四个引用的开销,每个cSTLTriangle
将对向量进行引用,为您提供:
10,000,000 * 4 * 4 = 152 MB(32位)
10,000,000 * 4 * 8 = 305 MB(64位)
正如你所看到的,所有这些都会产生相当大的开销。
因此,在这种情况下,我建议您cVector
为struct
。正如评论中所讨论的,结构可以实现接口(以及属性和方法)。请注意@Jcl提到的caveats。
你的cSTLTriangle
类有同样的问题(32位和64位分别约为76/152 MB开销),虽然它的大小我不确定我是否想要推荐跟那个结构一起去。这里的其他人可能对这个问题有有用的见解。
此外,由于填充和对象布局,开销可能实际上更大,但我还没有考虑到这一点。
列出容量
将List<T>
类与该数量的对象一起使用可能会导致内存浪费。正如@Matthew Watson所提到的,当列表的内部数组没有更多空间时,它将被扩展。事实上,每次发生这种情况时,它的容量都会增加一倍。在使用您的10533050个条目的测试中,列表的容量最终为16777216个条目,产生了以下开销:
(16777216 - 10533050)* 4字节参考= 23 MB(32位)
(16777216 - 10533050)* 8字节参考= 47 MB(64位)
因为你事先知道了三角形的数量,所以我建议你选择一个简单的数组。手动设置列表的Capacity
也可以。
其他问题
评论中讨论的其他问题不应该给你任何内存开销,但他们肯定会给GC带来很多不必要的压力。尤其是SubArray
方法,虽然非常实用,但会为GC创建数百万个垃圾数组来处理。我建议跳过它并手动索引到数组中,即使它的工作量更大。
另一个问题是立刻读取整个文件。这将比较慢并且使用更多的内存而不是逐个读取它。由于您需要处理的字节序问题,可能无法直接使用其他人提出的BinaryReader
。一个复杂的选项可以使用内存映射文件,这样可以让您访问数据而无需关心它是否被读取,将详细信息留给操作系统。
(男人,我希望我得到所有这些数字)
答案 1 :(得分:2)
您可以尝试减少内存使用量。
首先,如果可能,您应该重写文件加载代码,以便它只加载所需的数据,而不是一次加载整个文件。
例如,您可以将标题读取为单个块,然后将每个三角形的数据作为单个块读取(在循环中)。
其次,您的大型对象堆可能存在碎片 - 垃圾收集器不会移动大型对象,因此无法对其进行碎片整理。 (如果为.Net 4.51修复了此问题,但您必须显式启用大对象堆碎片整理,并明确地发起它。)
您可以通过预先调整triangleList
。
目前,您依次将每个三角形添加到triangleList
,从容量为零的列表开始。这意味着每隔一段时间就会超出列表的容量,导致它被扩展。
当列表在容量时通过向其添加项目来扩展列表时,它:
问题出在哪里:
由于您事先知道三角形列表的最大大小,您可以通过在向其添加项目之前设置列表的容量来解决此问题:
triangleList.Capacity = numberOfTriangles;
答案 2 :(得分:0)
在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
}