我将拥有一些MyClass类的数千万个实例,并希望最小化其内存大小。在Find out the size of a .net object中讨论了测量对象在内存中占用多少空间的问题 我决定按照Jon Skeet的建议,这是我的代码:
// Edit: This line is "dangerous and foolish" :-)
// (However, commenting it does not change the result)
// [StructLayout(LayoutKind.Sequential, Pack = 1)]
public class MyClass
{
public bool isit;
public MyClass nextRight;
public MyClass nextDown;
}
class Program
{
static void Main(string[] args)
{
var a1 = new MyClass(); //to prevent JIT code mangling the result (Skeet)
var before = GC.GetTotalMemory(true);
MyClass[] arr = new MyClass[10000];
for (int i = 0; i < 10000; i++)
arr[i] = new MyClass();
var after = GC.GetTotalMemory(true);
var per = (after - before) / 10000.0;
Console.WriteLine("Before: {0} After: {1} Per: {2}", before, after, per);
Console.ReadLine();
}
}
我在64位Windows上运行程序,选择“发布”,平台目标:“任何cpu”,然后选择“优化代码”(选项只有在我明确定位x86时才有意义)结果是,遗憾的是,48字节每个实例。
我的计算是每个引用8个字节,加上1个字节的bool加上一些~8byte的开销。到底是怎么回事?这是一个保持RAM价格高和/或让非Microsoft代码膨胀的阴谋吗?好吧,好吧,我想我真正的问题是:我做错了什么,或者我怎样才能最小化MyClass的大小?
编辑:我为我的问题草率道歉,我编辑了几个标识符名称。我具体而直接的关注是建立一个“2-dim链表”作为稀疏布尔矩阵实现,我可以很容易地在给定的行/列中得到设置值的枚举。 [当然这意味着我还必须在课堂上存储x,y坐标,这使我的想法更不可行]
答案 0 :(得分:26)
从另一端处理问题。而不是问自己“我怎样才能使这个数据结构更小,并且仍然分配了数千万个?”问自己“我怎样才能使用完全不同的数据结构来表示这些数据呢?”
看起来你正在构建一个双向链接的bool列表,正如你所说,它使用的内存比它需要的内存多30到50倍。您是不是只使用BitArray
来存储您的bool列表?
更新:
实际上我试图实现一个稀疏的布尔二维矩阵
那你为什么不首先这么说呢?
当我想制作一个庞大的稀疏布尔二维矩阵时,我用一个memoized工厂构建了一个不可变的持久布尔四叉树。如果数组是稀疏的,或者即使它是密集但以某种方式自相似,你也可以实现巨大的压缩。 2 64 x 2 64 布尔运算的方阵可以很容易地表示,即使显然是一个真实的数组,这将是比世界上存在的更多的记忆。
我一直在想着做一系列关于这种技术的博客文章;我可能会在三月下旬这样做。
简而言之,我们的想法是创建一个抽象类Quad,它有两个子类:Single和Multi。 “单一”是一个双重身份 - 就像一个单身人士,但只有两个实例,称为True和False。 Multi是具有四个子四边形的四边形,称为NorthEast,SouthEast,SouthWest和NorthWest。
每个Quad都有一个整数“级别”; Single的级别为零,并且需要多级n级才能使其所有子级为级别为n-1的四元组。
多工厂被记忆;当你要求它制作一个有四个孩子的新Multi时,它会查询缓存以查看它是否已经成功。如果有,它不会构建一个新的;它分发了旧的。由于Quads是不可变的,所以你不必担心有人在你进入缓存后更改Quad。
现在考虑一下有多少个记忆单词(一个单词是4或8个字节,具体取决于体系结构)和“全部错误”等级n的多个消耗。级别1“全部为假”多个消耗四个单词用于指向其子级的链接,一个用于级别计数的单词(如果需要,您不需要保持多级中的级别,尽管它有助于调试)和一些单词用于同步块等。我们称之为八个字。 (加上False Single quad的内存,我们可以假设这是两个或三个单词的常量,因此可以忽略。)
2级“全假”多消耗相同的八个单词,但其四个孩子中的每一个都是相同的1级多。因此,2级“全假”的总消耗量可以说是16个字。
3级,4级......等等。 64级多路复用的总内存消耗在逻辑上是2 64 x 2 64 方形布尔数组,只有64 x 16个内存字!
有意义吗?希望这足以让你前进。如果没有,请在3月下旬看我的博客。
答案 1 :(得分:4)
8(对象引用)+ 8(对象引用)+ 1(bool)+ 16(标题)+ 8(数组本身引用)= 41
即使它在内部未对齐,每个都将在堆上对齐。所以我们至少要看48字节。
我不能为我的生活看到为什么你想要一个链接的bool列表。它们的列表将占用48倍的空间,并且在您获得每位存储bool的优化之前,这将使其小384倍。而且更容易操纵。
答案 2 :(得分:1)
如果该类的数亿个实例大多是类的副本,并且类属性值的变化很小,那么您的系统是使用所谓的 {{3}的主要候选者。 } 模式。这种模式通过反复使用相同的instanes来最小化内存使用,并且只需根据需要更改属性......