【发布时间】:2015-01-16 12:45:05
【问题描述】:
我在这里遇到了一点资源问题。似乎.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。添加到triangleList 的类cSTLTriangle 如下:
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 4 和 Logpoint 3 之间,内存使用量增加了大约是原始 STL 文件大小的 4.73 倍(如您所见,我在解析每个三角形时大量使用了.SubArray)。
当我让程序继续运行时,内存使用量没有显着增加:好,但也完全没有减少:坏。我希望持有文件的byte[] 会释放内存,因为它超出了范围,我传递给ReadTriangles(byte[] ...) 的子数组也是如此,但不知何故他们没有。 我最终得到的“开销”是原始 STL 数据大小的 5.7 倍。
这是通常的行为吗? .NET 运行时是否会像 Photoshop 一样保持分配内存(即使它已扩展到磁盘),一旦它获得了一些 jucy RAM?如何减少这种类组合的内存占用?
编辑:
- 该问题出现在 Win7 Enterprise、x64 上。 .NET 4.5,针对 x64(也包括 AnyCPU)编译
- 我在对象创建完成后调用了
GC.Collect()(所以在对象本身之外),但什么也没发生。只有在将对象引用设置为null之后,我才恢复了内存
【问题讨论】:
-
不看数字,我可以说没有立即减少的一个可能原因是,虽然一个对象 符合 GC,但 GC 没有理由回收它,可能是因为它用于管理对象生成的堆中有足够的空间。您是否尝试过强制所有代的 GC 收集来观察内存使用量的减少?
-
避免一次读取大文件。打开文件的流并根据需要逐个或逐块读取字节,然后关闭流。
-
将你的向量类设为
struct可以为每个向量节省一大堆字节,看看你有多少字节,在这种情况下它实际上可能会有所作为。 -
我建议您使用 BinaryReader 读取文件流,并将读取器传递给子解析器,从而将您必须保存在内存中的“额外”字节量整体降低为零文件被单次读取,没有信息被复制到子数组中进行子解析。
-
@lhiapgpeonk 是的,结构可以实现接口,但是对象的装箱有一些限制和注意事项...推荐阅读:blogs.msdn.com/b/abhinaba/archive/2005/10/05/477238.aspx