使用数组字段而不是大量的对象

根据这篇文章 ,我想知道人们使用数组存储大量数据集(比如说> 10,000,000个对象)来存储数据字段而不是实例化数百万个对象并增加内存开销(例如,12-每个对象24个字节,具体取决于您阅读的文章)。 每个属性的数据因项目而异,因此我不能使用严格的Flyweight模式,但会设想类似的东西。

我对这种表示的想法是,有一个’模板对象’……

class Thing { double A; double B; int C; string D; } 

然后是一个容器对象,其中包含一个根据请求创建对象的方法……

 class ContainerOfThings { double[] ContainerA; double[] ContainerB; int[] ContainerC; string[] ContainerD; ContainerOfThings(int total) { //create arrays } IThing GetThingAtPosition(int position) { IThing thing = new Thing(); //probably best done as a factory instead thing.A = ContainerA[position]; thing.B = ContainerB[position]; thing.C = ContainerC[position]; thing.D = ContainerD[position]; return thing; } } 

所以这是一个简单的策略,但不是非常通用,例如,不能创建’Thing’的子集(作为List)而不重复数据并且无法实现arrays字段存储的目的。 我一直无法找到好的例子,所以我很欣赏能够从一个完成它的人那里处理这种情况的更好方法的链接或代码片段……或者更好的想法。

这取决于你的具体情况。 取决于创建对象的频率,您可以:

  1. 如果对象是可序列化的,请将它们保存在MemoryMappedFile中(获得一些中/低性能和低内存消耗的融合)。

  2. 映射不同对象之间的字段:我的意思是如果对象最初具有默认值,将它们全部放在单独的基础中,并且如果该值与默认值不同,则实际分配新空间。 (这自然适用于参考类型)。

  3. 另一个解决方案再次将对象保存到SqlLite基础。 比MemoryMappedFiles更容易管理,因为您可以使用简单的SQL。

选择取决于您,因为它取决于您的具体项目需求。

问候。

根据这篇文章,我想知道人们使用数组存储大量数据集(比如说> 10,000,000个对象)来存储数据字段而不是实例化数百万个对象并增加内存开销的经验是什么…

我想有几种方法可以解决这个问题,实际上你正在寻找一种可能的解决方案来限制内存中的数据。 但是,我不确定你的结构是否会减少到24? bytes会给你带来很多好处。 你的结构大约是79个字节(15个字符串)= 8 + 8 + 4 + 24? + 4 + 1 +(2 *字符长度),因此您的总收益最多为25%。 这似乎没有用,因为你必须处于1000万* 80字节适合内存而1000万* 100字节不适合的位置。 这意味着你要设计一个处于灾难边缘的解决方案,太多的大字符串,或太多的记录,或其他程序占用内存,你的机器内存不足。

如果你需要支持随机访问n个小记录,其中n = 1000万,那么你应该设计至少2n或10n。 也许你已经考虑过你的1000万了? 无论哪种方式,都有许多技术可以支持这种类型的数据被访问。

一种可能性是如果字符串限制在Max Length(ml)中,合理的大小(比如255)那么你可以去一个简单的ISAM商店。 每条记录将是8 + 8 + 4 + 255字节,您可以简单地偏移到平面文件中读取它们。 如果记录大小可变或可能很大,那么您将需要使用不同的存储格式并将偏移存储到文件中。

另一种可能性是,如果您通过某个键查找值,那么我会推荐类似嵌入式数据库或BTree的东西,您可以禁用某些磁盘一致性以获得性能。 碰巧我为大量数据的客户端缓存编写了一个BPlusTree。 有关使用B +树的详细信息,请点击此处 。

实际上,ADO.NET DataTable使用类似的方法来存储数据。 也许你应该看看它是如何实现的。 因此,您需要一个类似DataRow的对象,该对象在内部保存指向Table数据和行数据索引的指针。 这将是我认为最轻量级的解决方案。

在您的情况下:a)如果您在每次调用GetThingAtPosition方法时构建Thing,则在堆中创建对象,这会使表中已有的信息加倍。 加上“对象开销”数据。

b)如果需要访问ContainerOfThings中的每个项目,所需的内存将加倍+ 12bytes *开销对象数量。 在这种情况下,最好有一个简单的事物arrays而不是即时创建它们。

你的问题意味着存在问题。 内存使用量是否有问题?

如果每个项目100个字节,那么它听起来像1GB。 所以我想知道应用程序,如果这是一个问题。 该应用程序是在专用的64位盒子上运行,比如8GB还是内存?

如果有恐惧,你可以通过集成测试来测试恐惧。 实例化说这些项目中的2000万并运行一些性能测试。

但当然它确实都来自app域。 我有专门的应用程序,使用比这更多的RAM,并且工作正常。 硬件成本通常低于软件成本(再次归结为app域)。

拜拜

不幸的是,OO无法抽象出性能问题(带宽饱和度为1)。 这是一个方便的范例,但它有局限性。

我喜欢你的想法,我也使用它…猜猜看,我们不是第一个想到这个;-)。 我发现它确实需要一点思维转变。

我可以向J社区介绍你吗? 看到:

http://www.JSoftware.com

那不是C#(或Java)组。 他们是一群很好的人。 通常,数组需要被视为第一类对象。 在C#中,它不是那么灵活。 使用C#可能是令人沮丧的结构。

对于大型数据集问题有各种OO模式……但是如果你问这样的问题,可能是时候进行更多function了。 或者至少可用于解决问题/原型设计。

我为rapidSTORM项目做了这样的事情,需要缓存数百万个人口稀少的对象(本地化显微镜)。 虽然我无法真正为您提供良好的代码片段(过多的依赖项),但我发现Boost Fusion的实现非常快速和直接。 融合了结构,为每种元素类型构建了一个向量,然后为重建每个元素的向量写了一个非常简单的访问器。

(噢,我只是注意到你标记了这个问题,但也许我的C ++答案也有帮助)

[更新2011-07-19]

现在有一个新版本: http : //www.mediafire.com/file/74fxj7u1n0ppcq9/MemStorageDemo-6639584-2011_07_19-12_47_00.zip

我仍在尝试调试一些令人讨厌的引用计数,但是从一个新的xUnit会话,我能够运行一个创建1000万个对象的测试(它发生在我身上,我现在已经减少了字符串大小用于测试但是我让它运行了1000万个可变长度的字符串,从3到15个字节,我还没有机会尝试大于那个。在我的系统上,我从大约1.95G负载到〜2.35G与10除了使用实际托管字符串的非常简单的支持类之外,我仍然没有对字符串做任何事情。

无论如何,所以,我认为它工作得相当好,虽然在后备存储上肯定会有优化,我还认为必要时可以在迭代器上完成一些工作,具体取决于你一次处理多少数据。 不幸的是,直到明天或明天晚些时候才能再次看到它。

无论如何,这是基本的想法:

  1. MemoryArray类:使用Marhsal.AllocHGlobal()分配的非托管内存来存储结构。 我正在使用一个大的MemoryArray进行测试,但是成员方面只有几个字段,我认为只要你保持数组大小相当大,分解它就不会有太大的内存消耗差异。 MemoryArray实现IEnumerable ,这是我在测试中用来填充数组的方法。

MemoryArray用于保存来自任何被支持对象的常规大小的数据块。 你可以使用我尚未实现的指针数学的枚举器做一些事情。 我目前每次都会返回新物品,所以这是我相信的一大块。 在基于这个原型的原型类中,我能够使用非常规的指针数学进行遍历,但是我认为我这样做的方式主要用于非常快速的遍历,并且可能不适用于互操作性。

MemoryArray只有一个标准索引器,它使用Int Ptr上的指针数学来获取所请求的元素,该数据表示在构造时分配的非托管数据的头部。 我还实现了一个名义上的2D索引器,您可以在其中将表示维度的int数组传递给Array,然后可以对其执行A [x,y]。 它只是它如何运作的一个简单的例子。

我尚未实现的一件事是任何类型的分段,但我确实认为一个小节适合于该项目,所以当我有机会时我可能会实现它。

  1. MemoryArrayEnumerator类:我选择实现实际的枚举器而不是枚举器函数。 枚举器类基本上只需要一个MemoryArray ,然后为枚举器提供stock函数以返回实际的MemoryArrayItem对象。

  2. MemoryArrayItem类:除了根据数组中的开始和位置保存适当的指针信息之外,这个类没有什么用处。 它是在这个对象上实现的特定类,这个对象实际上是用来获取数据的指针。

然后还有一些支持类, MemoryStringArray是可变大小的内存支持块,暂时只执行字符串,然后是自动处理类( AutoDisposer )和处理附加和分离的generics类。 (自动AutoReference<> )。

现在,在这个基础之上是特定的类。 三种类型(数组/枚举器/项)中的每一种都是专门为您正在查看的对象实现的。 在我放弃的这个项目的更大规模的版本中,这是一个尖峰,我有更多的通用处理抵消等等,所以你不是那么紧张的具体类,但即使他们是很有用; 我最初的实现是所有具体的类,没有真正的基础。

目前我将它们全部实现为您传递引用的单独类; 所以, TestArray类在其构造函数中传递了一个MemoryArray类。 与枚举器和项目相同。 我知道那里会有一些处理能力,而且我认为节省空间的可能性很大,如果我能找到一种很好的方法来实现它们作为后代而不仅仅是拥有底层类的副本。 我想先得到它的基本感觉,这似乎是最直接的方式。 问题是它是另一层间接。

除了传递MemoryArrayMemoryArrayEnumerator的function之外, TestArrayTestArrayEnumerator结果不会做太多。 这些类中的主要问题是将指针划分并传递到正在使用它的项目中。

但是,TestArrayItem是指针实际切换到实际数据的地方; 这是文件。 我剪了一大段评论,通过一些选项来更好地处理可变长度的后备存储(仍然在上面给出的链接中的实际文件中),请原谅大量的评论,我留下自己的笔记在我工作的时候我在想什么:)

TestArrayItem.cs

 // ---------------------------------------------- // rights lavished upon all with love // see 'license/unlicense.txt' // ♥ 2011, shelley butterfly - public domain // ---------------------------------------------- using System; using System.Collections.Generic; using System.Linq; using System.Text; namespace MemStorageDemo { public unsafe class TestArrayItem { public static MemoryStringArray s_MemoryArrayStore = new MemoryStringArray(); public static int TestArrayItemSize_bytes = sizeof(double) * 2 + sizeof(int) + sizeof(int); // hard-coding here; this is another place that things could be a little more generic if you wanted and if // performance permitted; for instance, creating a dictionary of offsets based on index. also, perhaps // a dictionary with key strings to allow indexing using field names of the object. private enum EFieldOffset { DoubleTheFirstOffset = 0, DoubleTheSecondOffset = 8, IntTheFirstOffset = 16, StringTheFirstHandleOffset = 20 } private MemoryArrayItem myMemoryArrayItem; private MemoryStringArray myStringStore; // constructor that uses the static string array store public TestArrayItem(MemoryArrayItem parMemoryArrayItem) : this(parMemoryArrayItem, s_MemoryArrayStore) { } // constructor for getting the item at its memory block without any initialization (eg existing item) public TestArrayItem(MemoryArrayItem parMemoryArrayItem, MemoryStringArray parStringStore) { myMemoryArrayItem = parMemoryArrayItem; myStringStore = parStringStore; } // constructor for geting the item at its memory block and initializing it (eg adding new items) public TestArrayItem(MemoryArrayItem parMemoryArrayItem, double parDoubleTheFirst, double parDoubleTheSecond, int parIntTheFirst, string parStringTheFirst) { myMemoryArrayItem = parMemoryArrayItem; DoubleTheFirst = parDoubleTheFirst; DoubleTheSecond = parDoubleTheSecond; IntTheFirst = parIntTheFirst; StringTheFirst = parStringTheFirst; } // if you end up in a situation where the compiler isn't giving you equivalent performance to just doing // the array math directly in the properties, you could always just do the math directly in the properties. // // it reads much cleaner the way i have it set up, and there's a lot less code duplication, so without // actually determining empirically that i needed to do so, i would stick with the function calls. private IntPtr GetPointerAtOffset(EFieldOffset parFieldOffset) { return myMemoryArrayItem.ObjectPointer + (int)parFieldOffset; } private double* DoubleTheFirstPtr { get { return (double*)GetPointerAtOffset(EFieldOffset.DoubleTheFirstOffset); } } public double DoubleTheFirst { get { return *DoubleTheFirstPtr; } set { *DoubleTheFirstPtr = value; } } private double* DoubleTheSecondPtr { get { return (double*)GetPointerAtOffset(EFieldOffset.DoubleTheSecondOffset); } } public double DoubleTheSecond { get { return *DoubleTheSecondPtr; } set { *DoubleTheSecondPtr = value; } } // ahh wishing for a preprocessor about now private int* IntTheFirstPtr { get { return (int*)GetPointerAtOffset(EFieldOffset.IntTheFirstOffset); } } public int IntTheFirst { get { return *IntTheFirstPtr; } set { *IntTheFirstPtr = value; } } // okay since we're using the StringArray backing store in the example, we just need to get the // pointer stored in our blocks, and then copy the data from that address private int* StringTheFirstHandlePtr { get { return (int*)GetPointerAtOffset(EFieldOffset.StringTheFirstHandleOffset); } } public string StringTheFirst { get { return myStringStore.GetString(*StringTheFirstHandlePtr); } set { myStringStore.ModifyString(*StringTheFirstHandlePtr, value); } } public void CreateStringTheFirst(string WithValue) { *StringTheFirstHandlePtr = myStringStore.AddString(WithValue); } public override string ToString() { return string.Format("{0:X8}: {{ {1:0.000}, {2:0.000}, {3}, {4} }} {5:X8}", (int)DoubleTheFirstPtr, DoubleTheFirst, DoubleTheSecond, IntTheFirst, StringTheFirst, (int)myMemoryArrayItem.ObjectPointer); } } } 

所以,这才是真正的魔力; 只是基本上实现了根据字段信息找出正确指针的函数。 因为它是我认为它是代码生成的一个非常好的候选人,好吧,假设我得到附加/分离的东西正常工作。 我隐藏了很多必须使用自动类型指针进行手动内存管理,我认为从长远来看这将是值得的调试…

无论如何,这是关于它,我希望今晚或明天回到互联网上,我会尽我所能办理登机手续。不幸的是,我要把电缆调制解调器送回有线电视公司所以不会再开展业务了。除非我们去麦当劳或者其他什么东西:)希望这是一种帮助; 我将调试它的问题,以便至少有一个function基础可以工作; 我知道这不是我第一次想到写这样的图书馆,我想其他人也有。


以前的内容

我使用了这样的东西来使用我创建的COM库来与预编译的win32 / 64 FFTW dll互操作。 对于这个问题,我们真的需要一些比我的更通用的东西,所以我上周开始研究一些适合这些类型用途的通用库,具有可扩展的内存管理,多维度,切片,等等

好吧,我终于在昨天对自己表示赞同:(a)在它准备好之前还有几天;(b)我需要一个尖峰解决方案,无论如何都要找出一些最后的位。 所以,我决定只是在较低的抽象层次上进行第一次削减,试图满足你的问题所解决的需求。 不幸的是我们即将搬家,我必须做一些包装,但我想我可能已经足够解决你的问题了。

我本周将继续研究这个例子,我将在这里用新代码进行更新,我将尝试从中提取足够的信息来发表解释它的post,但是如果你仍然感兴趣,这是最后一个我我可能会发布到本周晚些时候:

http://www.mediafire.com/file/a7yq53ls18q7bvf/EfficientStorage-6639584.zip

它只是一个VS2010解决方案,具有满足您需求的基本必需品。 有很多评论可以通过,但随时可以提出问题,一旦我上网,我会立即回来看看……无论哪种方式,这都是一个有趣的项目。

在完成示例后,我打算完成第一个迭代完整库并将其释放到某个地方; 我会在这里用链接更新。

强制警告:它编译但未经过测试; 我确定有问题,这是一个非常大的话题。

为类型中的每个属性创建一个System.Array数组,其中包含一个元素。 这些子数组的大小等于您拥有的对象数。 物业访问将是:

masterArray [propertyIndex] [objectIndex]

这将允许您使用值类型数组而不是对象数组。