我有一个大文件,实质上包含如下数据:
Netherlands,Noord-holland,Amsterdam,FooStreet,1,...,...
Netherlands,Noord-holland,Amsterdam,FooStreet,2,...,...
Netherlands,Noord-holland,Amsterdam,FooStreet,3,...,...
Netherlands,Noord-holland,Amsterdam,FooStreet,4,...,...
Netherlands,Noord-holland,Amsterdam,FooStreet,5,...,...
Netherlands,Noord-holland,Amsterdam,BarRoad,1,...,...
Netherlands,Noord-holland,Amsterdam,BarRoad,2,...,...
Netherlands,Noord-holland,Amsterdam,BarRoad,3,...,...
Netherlands,Noord-holland,Amsterdam,BarRoad,4,...,...
Netherlands,Noord-holland,Amstelveen,BazDrive,1,...,...
Netherlands,Noord-holland,Amstelveen,BazDrive,2,...,...
Netherlands,Noord-holland,Amstelveen,BazDrive,3,...,...
Netherlands,Zuid-holland,Rotterdam,LoremAve,1,...,...
Netherlands,Zuid-holland,Rotterdam,LoremAve,2,...,...
Netherlands,Zuid-holland,Rotterdam,LoremAve,3,...,...
...
这是一个数千兆字节的文件。我有一个类读取此文件并将这些行(记录)公开为IEnumerable<MyObject>
。此MyObject
有多个属性(Country
,Province
,City
,...)等。
正如您所看到的,有很多重复的数据。我希望将基础数据公开为IEnumerable<MyObject>
。但是,其他一些类可能(并且可能会)对这些数据进行分层视图/结构,如:
Netherlands
Noord-holland
Amsterdam
FooStreet [1, 2, 3, 4, 5]
BarRoad [1, 2, 3, 4]
...
Amstelveen
BazDrive [1, 2, 3]
...
...
Zuid-holland
Rotterdam
LoremAve [1, 2, 3]
...
...
...
...
在阅读此文件时,基本上我会这样做:
foreach (line in myfile) {
fields = line.split(",");
yield return new MyObject {
Country = fields[0],
Province = fields[1],
City = fields[2],
Street = fields[3],
//...other fields
};
}
现在,针对手头的实际问题:我可以使用string.Intern()
实习国家,省,城市和街道字符串(这些是主要&#39; vilains&# 39;,MyObject
有几个与问题无关的其他属性)。
foreach (line in myfile) {
fields = line.split(",");
yield return new MyObject {
Country = string.Intern(fields[0]),
Province = string.Intern(fields[1]),
City = string.Intern(fields[2]),
Street = string.Intern(fields[3]),
//...other fields
};
}
当将整个数据集保存在内存中时,这将节省大约42%的内存(经过测试和测量),因为所有重复的字符串都将是对同一字符串的引用。此外,当使用许多LINQ的.ToDictionary()
方法创建分层结构时,可以使用resp的密钥(Country,Province等)。字典会更有效率。
然而,使用string.Intern()
的一个缺点(除了轻微的性能损失,这不是问题)是字符串won't be garbage collected anymore。但是,当我完成我的数据后,我做希望收集所有垃圾(最终)。
I could use a Dictionary<string, string>
to 'intern' this data但我不喜欢&#34;开销&#34;我实际上只有key
和value
才对key
感兴趣。我可以将value
设置为null
或使用与值相同的字符串(这将在key
和value
中生成相同的引用)。它只需支付几个字节的小价格,但它仍然是一个价格。
像HashSet<string>
之类的东西对我来说更有意义。但是,我无法在HashSet中获取对字符串的引用;我可以看到HashSet 是否包含特定的字符串,但是没有获得对HashSet中所定位字符串的特定实例的引用。 I could implement my own HashSet
for this,但我想知道StackOverflowers可能提出的其他解决方案。
要求:
IEnumerable<MyObject>
string.Intern()
)来优化内存使用MyObject
类无法更改;我不会创建City
课程,Country
课程等,并MyObject
将这些内容公开为属性,而不是简单的string
属性Country
,Province
,City
等中的大多数重复字符串,目标是提高(更多)内存效率。如何实现这一点(例如字符串实习,内部哈希集/集合/某事物的结构)并不重要。但是:这更像是一个理论上的&#39;题;纯粹是出于好奇/兴趣,我一直在问。没有&#34; 真正的&#34;问题,但我可以看到在类似的情况下,可能对某人来说是一个问题。
例如:我可以这样做:
public class StringInterningObject
{
private HashSet<string> _items;
public StringInterningObject()
{
_items = new HashSet<string>();
}
public string Add(string value)
{
if (_items.Add(value))
return value; //New item added; return value since it wasn't in the HashSet
//MEH... this will quickly go O(n)
return _items.First(i => i.Equals(value)); //Find (and return) actual item from the HashSet and return it
}
}
但是有一大堆(要重复删除)字符串会很快陷入困境。我可以看一下reference source for HashSet或Dictionary或...并构建一个类似的类,它不会为Add()
方法返回bool但是在内部/铲斗。
我能想到的最好的东西是:
public class StringInterningObject
{
private ConcurrentDictionary<string, string> _items;
public StringInterningObject()
{
_items = new ConcurrentDictionary<string, string>();
}
public string Add(string value)
{
return _items.AddOrUpdate(value, value, (v, i) => i);
}
}
其中有#34;惩罚&#34;有一个Key 和一个值,我实际上只对Key感兴趣。只需几个字节,支付的价格很小。顺便说一句,这也减少了42%的内存使用量;与使用string.Intern()
时产生的结果相同。
tolanj came up with System.Xml.NameTable:
public class StringInterningObject
{
private System.Xml.NameTable nt = new System.Xml.NameTable();
public string Add(string value)
{
return nt.Add(value);
}
}
(我删除了lock and string.Empty check(后者,因为NameTable already does that))
xanatos came up with a CachingEqualityComparer:
public class StringInterningObject
{
private class CachingEqualityComparer<T> : IEqualityComparer<T> where T : class
{
public System.WeakReference X { get; private set; }
public System.WeakReference Y { get; private set; }
private readonly IEqualityComparer<T> Comparer;
public CachingEqualityComparer()
{
Comparer = EqualityComparer<T>.Default;
}
public CachingEqualityComparer(IEqualityComparer<T> comparer)
{
Comparer = comparer;
}
public bool Equals(T x, T y)
{
bool result = Comparer.Equals(x, y);
if (result)
{
X = new System.WeakReference(x);
Y = new System.WeakReference(y);
}
return result;
}
public int GetHashCode(T obj)
{
return Comparer.GetHashCode(obj);
}
public T Other(T one)
{
if (object.ReferenceEquals(one, null))
{
return null;
}
object x = X.Target;
object y = Y.Target;
if (x != null && y != null)
{
if (object.ReferenceEquals(one, x))
{
return (T)y;
}
else if (object.ReferenceEquals(one, y))
{
return (T)x;
}
}
return one;
}
}
private CachingEqualityComparer<string> _cmp;
private HashSet<string> _hs;
public StringInterningObject()
{
_cmp = new CachingEqualityComparer<string>();
_hs = new HashSet<string>(_cmp);
}
public string Add(string item)
{
if (!_hs.Add(item))
item = _cmp.Other(item);
return item;
}
}
(稍微修改为&#34;适合&#34;我的&#34;添加()界面&#34;)
public class StringInterningObject
{
private Dictionary<string, string> _items;
public StringInterningObject()
{
_items = new Dictionary<string, string>();
}
public string Add(string value)
{
string result;
if (!_items.TryGetValue(value, out result))
{
_items.Add(value, value);
return value;
}
return result;
}
}
我只是想知道是否有一种更整洁/更好/更酷的方式来解决问题&#39;我(实际上不是那么多)问题。现在我有足够的选择我猜
以下是我想出的一些简单,简短的初步测试数据:
StringInterningObject (见上文,ConcurrentDictionary
变体)
内存:~2,6Gb
加载时间:~49s
string.Intern()
内存:~2,3Gb
加载时间:~45s
System.Xml.NameTable
内存:~2,3Gb
加载时间:~41s
CachingEqualityComparer
内存:~2,3Gb
加载时间:~58s
StringInterningObject (见上文,(非并发)Dictionary
变体)按Henk Holterman's request:
内存:~2,3Gb >加载时间:~39秒
尽管这些数字并不是非常明确的,但似乎非优化版本的许多内存分配实际上比使用string.Intern()
或上述StringInterningObject
更慢。导致(稍微)更长的加载时间。 此外,&lt;&lt;查看更新。 string.Intern()
似乎“赢了”。来自StringInterningObject
,但不是很大;
答案 0 :(得分:3)
如有疑问,请作弊! : - )
public class CachingEqualityComparer<T> : IEqualityComparer<T> where T : class
{
public T X { get; private set; }
public T Y { get; private set; }
public IEqualityComparer<T> DefaultComparer = EqualityComparer<T>.Default;
public bool Equals(T x, T y)
{
bool result = DefaultComparer.Equals(x, y);
if (result)
{
X = x;
Y = y;
}
return result;
}
public int GetHashCode(T obj)
{
return DefaultComparer.GetHashCode(obj);
}
public T Other(T one)
{
if (object.ReferenceEquals(one, X))
{
return Y;
}
if (object.ReferenceEquals(one, Y))
{
return X;
}
throw new ArgumentException("one");
}
public void Reset()
{
X = default(T);
Y = default(T);
}
}
使用示例:
var comparer = new CachingEqualityComparer<string>();
var hs = new HashSet<string>(comparer);
string str = "Hello";
string st1 = str.Substring(2);
hs.Add(st1);
string st2 = str.Substring(2);
// st1 and st2 are distinct strings!
if (object.ReferenceEquals(st1, st2))
{
throw new Exception();
}
comparer.Reset();
if (hs.Contains(st2))
{
string cached = comparer.Other(st2);
Console.WriteLine("Found!");
// cached is st1
if (!object.ReferenceEquals(cached, st1))
{
throw new Exception();
}
}
我创建了一个“缓存”其分析的最后Equal
个术语的相等比较器: - )
然后可以将所有内容封装在HashSet<T>
/// <summary>
/// An HashSet<T;gt; that, thorough a clever use of an internal
/// comparer, can have a AddOrGet and a TryGet
/// </summary>
/// <typeparam name="T"></typeparam>
public class HashSetEx<T> : HashSet<T> where T : class
{
public HashSetEx()
: base(new CachingEqualityComparer<T>())
{
}
public HashSetEx(IEqualityComparer<T> comparer)
: base(new CachingEqualityComparer<T>(comparer))
{
}
public T AddOrGet(T item)
{
if (!Add(item))
{
var comparer = (CachingEqualityComparer<T>)Comparer;
item = comparer.Other(item);
}
return item;
}
public bool TryGet(T item, out T item2)
{
if (Contains(item))
{
var comparer = (CachingEqualityComparer<T>)Comparer;
item2 = comparer.Other(item);
return true;
}
item2 = default(T);
return false;
}
private class CachingEqualityComparer<T> : IEqualityComparer<T> where T : class
{
public WeakReference X { get; private set; }
public WeakReference Y { get; private set; }
private readonly IEqualityComparer<T> Comparer;
public CachingEqualityComparer()
{
Comparer = EqualityComparer<T>.Default;
}
public CachingEqualityComparer(IEqualityComparer<T> comparer)
{
Comparer = comparer;
}
public bool Equals(T x, T y)
{
bool result = Comparer.Equals(x, y);
if (result)
{
X = new WeakReference(x);
Y = new WeakReference(y);
}
return result;
}
public int GetHashCode(T obj)
{
return Comparer.GetHashCode(obj);
}
public T Other(T one)
{
if (object.ReferenceEquals(one, null))
{
return null;
}
object x = X.Target;
object y = Y.Target;
if (x != null && y != null)
{
if (object.ReferenceEquals(one, x))
{
return (T)y;
}
else if (object.ReferenceEquals(one, y))
{
return (T)x;
}
}
return one;
}
}
}
请注意WeakReference
的使用,以便对可能阻止垃圾收集的对象没有无用的引用。
使用示例:
var hs = new HashSetEx<string>();
string str = "Hello";
string st1 = str.Substring(2);
hs.Add(st1);
string st2 = str.Substring(2);
// st1 and st2 are distinct strings!
if (object.ReferenceEquals(st1, st2))
{
throw new Exception();
}
string stFinal = hs.AddOrGet(st2);
if (!object.ReferenceEquals(stFinal, st1))
{
throw new Exception();
}
string stFinal2;
bool result = hs.TryGet(st1, out stFinal2);
if (!object.ReferenceEquals(stFinal2, st1))
{
throw new Exception();
}
if (!result)
{
throw new Exception();
}
答案 1 :(得分:3)
我确实已经满足了这个要求并且确实在SO上询问过,但是没有就像问题的详细信息一样,没有有用的回复。一个用构建的选项是一个(System.Xml).NameTable,它基本上是一个字符串雾化对象,这是你正在寻找的,我们有(我们实际上转移到实习生,因为我们为App-life保留了这些字符串。
if (name == null) return null;
if (name == "") return string.Empty;
lock (m_nameTable)
{
return m_nameTable.Add(name);
}
在私人NameTable上
http://referencesource.microsoft.com/#System.Xml/System/Xml/NameTable.cs,c71b9d3a7bc2d2af显示它实现为Simple哈希表,即每个字符串只存储一个引用。
下行?是完全字符串特定的。如果你对内存/速度进行交叉测试,我有兴趣看到结果。我们已经在大量使用System.Xml,如果你不在的话,当然可能不那么自然。
答案 2 :(得分:0)
TLDR :从当前结果来看,我使用string.intern, 基于字典的实习生没有多大作用
edit2:
我发布我的制作结果
public class TProduct
{
public int ProductId { get; set; }
public int BrandId { get; set; }
public string ProductName { get; set; }
public string WordName { get; set; }
public string BrandName { get; set; }
public string ProductNameClean { get; set; } //interned field
public string BrandNameClean { get; set; } //interned field
}
该领域的2/7被拘禁了,也许这就是为什么结果不如我预期的那么好。 (如果增加复杂性,我希望至少可以减少2-3倍)
| reduction |
| | ram mb | mb | % | |
|---------------------|--------|------|------|------------------|
| string | 680 | | | |
| string.intern | 513 | 167 | 25 | |
| string.intern(dict) | 500 | 154 | 26 | |
| byte[] | 486 | 194 | 29 | hard to maintain |
| byte[].CustomIntern | 447 | 233 | 34 | hard to maintain |
我还必须将byteArrayEqualityComparer添加到byte []。customIntern中。 否则等于gethashcode无法正常工作。
编辑1:
如果ascii(256个字符的英语-英语字符)适合您。
将所有字符串转换为byte []。并使用byte [] intern。
但这可能会带来很多无法预料的问题。
还可能需要获取char数组,并检查其数值是否大于256。请确保抛出异常。如果您的记录列表字母组合很好,那么
从数据库到byteClass
var AllDbResults_2mRec = new List<MyByteClass>();
foreach (var fields in DbRowProvider)
AllDbResults_2mRec.Add(
new MyClass {
Country = byteArrayInterningObject.Intern(fields[0].ASCII_bytes() ),
Province = byteArrayInterningObject.Intern(fields[1].ASCII_bytes() ),
City = byteArrayInterningObject.Intern(fields[2].ASCII_bytes() ),
} );
当您搜索200万个MyByteClass记录时。
您过滤掉了20条记录(例如)
MyByteClass[] results_asByte = AllDbResults_2mRec .Search("tokyo");
MyClass[] results = results_asByte
.Select(x=> MyClass.From_Byte(x) )
.ToArray();
必修课程
class MyClass
{
string[] Country ;
string[] Province;
string[] City ;
public static From_Byte(MyByteClass mbc)
{
return new MyClass {
Country = mbc.Country.ASCII_string() ),
Province = mbc.Province.ASCII_string() ),
City = mbc.City.ASCII_string() ),
};
}
}
class MyByteClass
{
byte[] Country ;
byte[] Province;
byte[] City ;
}
public static class AsciiExt
{
// guarantee single byte per char. other one return multi byte for single char like € æ ß im not sure . but i chnaged it in my production code
public static byte[] ASCII_Bytes(this string str)
{
if (str == null)
return new byte[0];
var byteArr = new byte[str.Length];
for (int i = 0; i < str.Length; i++)
{
byteArr[i] = (byte)str[i]; //utf16 - already ascii compliant??
}
return byteArr;
}
public static byte[] ASCII_bytes(this string str)
{
return str == null ?
new byte[0] : Encoding.ASCII.GetBytes(str);
}
public static string ASCII_String(this byte[] byteArr)
{
return byteArr == null ?
string.Empty :
Encoding.ASCII.GetString(byteArr);
}
}
public class byteArrayInterningObject
{
private Dictionary<byte[], byte[]> _items;
public byteArrayInterningObject()
{
_items = new Dictionary<byte[], byte[]>();
}
public string Add(byte[] value)
{
string result;
if (!_items.TryGetValue(value, out result))
{
_items.Add(value, value);
return value;
}
return result;
}
}