.netstring类的替代

因为我正在计划一个应用程序,它将在内存中保存许多数据,所以我希望有一些'compact'string类,至less一个string的格式不能大于string的零终止ASCII版本。

你知道任何这样的string类实现 – 它应该有一些像原始string类的实用function。

编辑:

我需要对string进行sorting,并且能够扫描它们,只是提到我将要使用的一些操作。

理想情况下,它将与System.String的源代码兼容,所以基本的search和replace操作可以优化应用程序内存占用。

号码:

每条logging最多可以有10万条logging,每条logging最多10个string,长度为30-60个字符。 所以:

100000x10x60 = 60000000 = 57mega字符。 为什么不用60公分的公羊而不是120公分的公羊呢? 行动会更快,一切都会更紧。

树将被用于search,但是对于我计划使用的正则expression式扫描没有帮助。

我实际上有一个类似的问题,但有一些不同的问题参数。 我的应用程序处理两种types的string – 相对较短的字符数量为60-100字节,较长的字符数为100-1000字节(平均值大约为300)。

我的用例也必须支持unicode文本,但是相对较小比例的string实际上具有非英文字符。

在我的用例中,我将每个String属性公开为一个本地String,但底层的数据结构是一个保存unicode字节的byte []。

我的用例还需要search和sorting这些string,获取子string和其他常见的string操作。 我的数据集以百万计。

基本的实现看起来像这样:

byte[] _myProperty; public String MyProperty { get { if (_myProperty== null) return null; return Encoding.UTF8.GetString(value); } set { _myProperty = Encoding.UTF8.GetBytes(value); } } 

即使在search和sorting时,这些转换的性能也相对较低(大约是10-15%)。

这很好,但我想进一步降低开销。 下一步是为给定对象中的所有string创build一个合并数组(一个对象可以包含1个short和1个longstring,或者4个short和1个longstring)。 所以每个对象将有一个字节[],每个string只需要1个字节(保存长度始终小于256)。 即使你的string可能比256更长,并且int仍然更便宜,那么byte []的12-16字节开销。

这大大减less了byte []开销,并且增加了一点复杂性,但是对性能没有额外的影响(与所涉及的数组拷贝相比,编码通道相对昂贵)。

这个实现看起来像这样:

 byte _property1; byte _property2; byte _proeprty3; private byte[] _data; byte[] data; //i actually used an Enum to indicate which property, but i am sure you get the idea private int GetStartIndex(int propertyIndex) { int result = 0; switch(propertyIndex) { //the fallthrough is on purpose case 2: result+=property2; case 1: result+=property1; } return result; } private int GetLength(int propertyIndex) { switch (propertyIndex) { case 0: return _property1; case 1: return _property2; case 2: return _property3; } return -1; } private String GetString(int propertyIndex) { int startIndex = GetStartIndex(propertyIndex); int length = GetLength(propertyIndex); byte[] result = new byte[length]; Array.Copy(data,startIndex,result,0,length); return Encoding.UTF8.GetString(result); } 

所以吸气剂看起来像这样:

 public String Property1 { get{ return GetString(0);} } 

setter是在同一个精神 – 将原始数据复制到两个数组(从0开始到startIndex之间,和startIndex +长度之间),并创build一个新的数组与3个数组(dataAtStart + NewData + EndData),并设置数组的长度为适当的局部variables。

我仍然不满意保存的内存和每个属性的手动执行的非常辛苦的工作,所以我build立了一个使用令人惊讶的快速QuickLZ压缩整页的内存中压缩分页系统。 这给了我很多时间内存权衡的控制(这实质上是页面的大小)。

我的用例的压缩率(与更高效的byte [] store相比)接近50%(!)。 我使用了每页大约10个string的页面大小,并将相似的属性分组在一起(它们倾向于具有相似的数据)。 这增加了10-20%的额外开销(在仍然需要的编码/解码过程之上)。 分页机制将最近访问的页面caching到可configuration的大小。 即使没有压缩,这个实现允许你为每个页面的开销设置一个固定的因子。 我目前实现页面caching的主要缺点是压缩不是线程安全的(没有它没有这样的问题)。

如果你对压缩分页机制感兴趣,让我知道(我一直在寻找一个借口来开源它)。

编辑:我现在有一个关于这个话题的博客文章,进入一个相当详细的数额。


按照你的号码

每个logging最多可以有10万条logging,每条logging最多10个字符,有30-60个字符。

首先,通过添加对象开销 – 由于不可避免的对象开销和长度,string占用了大约20个字节(IIRC – 可能更多地在64位CLR上) 加上实际数据。 让我们再次做math:

使用string:20 + 120字节的一百万个对象= 140MB

使用新的类:20 + 60字节= 80MB的100万个对象

当然,仍然有60MB的差异,但比例要比你期望的要小。 你只能节省42%的空间,而不是50%。

现在,你们谈论的是速度更快的事情:由于CLR本身意识到了string ,我怀疑一个第三方类的速度对于某些操作来说是无法匹配的,所以你必须付出很多的工作,以获得许多其他人一样的速度。 无可否认,你有更好的caching一致性,如果你可以忽略文化问题,那么通过进行所有的比较序数,也应该节省一些时间。

为了60MB,我不打扰。 现在这个差别是很小的 – 考虑为了弥补使用两种不同types的string所花费的额外成本,通过实现这一小节省,您将需要获得更多的客户。

说完所有这些,我很想自己实现,像Edulinq这样的博客项目。 不要指望几周或几个月的结果:)

编辑:我刚刚想到另一个问题。 我们上面得到的数字实际上并不正确,因为string类是特殊的。 它将数据直接embedded到对象中 – 与数组之外的任何其他数据types不同, string实例的大小不是固定的; 它会根据其中的数据而变化。

编写你自己的AsciiString类,你将无法做到这一点 – 你必须在类中embedded数组引用:

 public class AsciiString { private readonly byte[] data; } 

这意味着您需要额外的4或8个字节作为参考(32位或64位CLR)以及每个string的数组对象(16字节,IIRC)的额外开销。

如果你把它devise成像Java那样,一个子string可以重用现有的字节数组(两个string可以共享),但是在AsciiString需要额外的长度和偏移量。 你也会失去一些caching一致性的好处。

可以只使用原始字节数组作为数据结构,并写一堆扩展方法来对它们进行操作……但是那样会很糟糕,因为你不能区分正常的字节数组和一个意义上的数组代表一个ASCIIstring。

另一种可能性是创build一个像这样的结构:

 struct AsciiString { private readonly byte[] data; ... } 

这将有效地给你强有力的打字,但你需要考虑的东西,如:

 AsciiString x = new AsciiString(); 

这将以空data引用结束。 你可以有效地对待这个,就好像x是一个空值,但是这非常不习惯。

备用数据结构

我build议,考虑到你也希望search存储的“string”值,你应该考虑一个Trie结构,如Patricia Trie,还是为了更好的记忆分期付款,一个有向非循环的词图(参考affctionalty作为DAWG)会更好。

构build它们需要更长的时间(尽pipe通常它们被用于底层存储本身代表这种forms的合理良好的前提下快速构build的情况),并且即使它们上的某些操作在algorithm上更为优越,您也可能发现在现实世界中使用实际上它们只要有一个合理的重复次数,就可以显着减less数据的内存占用。

这些可以看作是在.net(以及java和许多其他pipe理语言)中提供的(内置的)重复数据的string实习的概括。

如果你特别希望用一些词典方式来保留string的sorting(所以你只需要考虑一个字符或代码点),那么Patricia Trie很可能是更好的select,在DAWG会有问题。

如果您有一个特定的string域,可以使用更深奥的解决scheme,其中包括:

运行长度编码和其他forms的压缩。

以随机访问string为代价,如果input结果不如预期,则实际使用更多内存的风险。 霍夫曼编码在英文文本中可以很好地工作,而且实现起来相当简单,只要字母的频率分布是可比较的,它的优点是它的字典可以在整个集合中进行分片。 sorting会再次成为问题。

固定长度的string

如果你知道string很小,并且所有几乎相同(或者完全相同)的大小,你可以以固定大小的值存储(如果需要的话,如果字符数在16或更小的范围内,在这里使用限制将取决于你的确切用法,并可能严重依赖于你是否愿意调整你的代码,以与这个devise打好)

你可以创build一个新的数据结构来保存这些数据结构,但我认为这是过度的。

但是,如果您有每个单词或常用短语的数组,则将索引存储为每个单词的数组。

然后你为每个单词支付4个字节,但是如果每个单词平均为3.6个字符,则平均每个单词节省3.2个字节,因为你支付2个字节/字母惩罚一次/单词。

但是,为了进行search或sorting,您至less需要在短时间内重buildstring才能获得大的性能。

您可能需要重新考虑如何devise您的程序,因为有许多程序使用大量的数据,并且可以在相对有限的内存中运行。

那么,有UTF8Encoding类

 //Example from MSDN using System; using System.Text; public class Example { public static void Main() { Encoding enc = new UTF8Encoding(true, true); string value = "\u00C4 \uD802\u0033 \u00AE"; try { byte[] bytes= enc.GetBytes(value); foreach (var byt in bytes) Console.Write("{0:X2} ", byt); Console.WriteLine(); string value2 = enc.GetString(bytes); Console.WriteLine(value2); } catch (EncoderFallbackException e) { //Encoding error } } } 

然而,像Jon说的,任何时候你想用任何需要一个string(大部分.Net库)的方法,你必须把它转换回正常的unicodestring反正…如果你给我们更多关于你想要做什么的信息,也许我们可以帮助你想出一个更好的解决scheme?

或者,如果您真的需要低级别的字节数组非空格终止的string,那么最好使用C ++编写。

你有多less重复的东西? 如果数组中有大量重复项,则可能需要考虑实现caching特定string实例的stringcaching(包含Dictionary<string, string>包装),并为每个caching的重复string返回该实例的引用它。

你可以把它和检查string结合起来,所以如果你在整个程序中共享了很多string的话,你总是会使用这个string。

根据您的数据,这可能会比尝试优化每个string的存储更好。

我认为这个关键是每个logging有很多string字段

通过将每个logging的所有string字段存储在单个字符数组中,然后使用具有该偏移量的int字段,可以极大地减less对象的数量。 (即使在你放入任何数据之前,每个对象的开销都是2个字左右)。

然后,您的属性可以转换为/从标准string。 垃圾收集器非常擅长处理大量的短暂垃圾,因此在访问属性时创build大量的“tmp”string不应该成为问题。

(现在,如果很多string字段从不改变它们的值,事情变得更容易)

你可以通过一个很大的字节[]来保存每个对象的开销,这个字节存储字符,然后把一个int偏移量作为你的“string”存储到这个数组中。

也许一个很好的旧时尚字符arrays将适合您的需求。

所有这些string是不同的?

在大多数现实世界的数据集中,我将不同的string的实际数量可能不会太高,如果考虑到string的实际内存,那么实际消耗的内存量可能会比您想象的要less得多。