快速随机访问集合
Fast random access to a collection
我正在使用半随机标记流。对于每个令牌,我都在维护大量数据(包括一些子集合)。
唯一令牌的数量没有限制,但实际上往往在 100,000-300,000 的数量级。
我从一个列表开始,并使用 Linq 查询确定要更新的适当标记对象。
public class Model {
public List<State> States { get; set; }
...
}
var match = model.States.Where(x => x.Condition == stateText).SingleOrDefault();
在前 ~30k 个唯一令牌中,我能够找到并更新~1,100 tokens/sec。
性能分析表明,总 Cpu 周期中的 85% 花在了 Where(...).SingleOrDefault()
上(这是有道理的,列表是一种低效的搜索方式)。
因此,我将列表切换到 HashSet 并再次进行分析,确信 HashSet 能够更快地随机查找。这次,我只处理了 ~900 tokens/sec。在 Linq 上花费的时间几乎相同 (89%)。
所以...首先,我是否滥用了 HashSet
? (使用 Linq 是否强制转换为 IEnumerable,然后是枚举/类似的东西?)
如果不是,我自己实现的最佳模式是什么?我的印象是 HashSet 已经进行了二进制搜索,所以我假设我需要构建某种树结构并具有更小的子集?
从评论中回答一些问题...条件是唯一的(如果我两次获得相同的标记,我想更新相同的条目),HashSet 是股票 .Net 实现(System.Collections.Generic.HashSet<T>
).
更广泛的代码视图是...
var state = new RollingList(model.StateDepth); // Tracks last n items and drops older ones. (Basically an array and an index that wraps around
var tokens = tokeniser.Tokenise(contents); // Iterator
foreach (var token in tokens) {
var stateText = StateToString(ref state);
var match = model.States.Where(x => x.Condition == stateText).FirstOrDefault();
// ... update the match as appropriate for the token
}
var match = model.States.Where(x => x.Condition == stateText).SingleOrDefault();
如果您使用散列集做完全相同的事情,那就没有节省。哈希集针对快速回答问题进行了优化 "is this member in the set?" 而不是 "is there a member that makes this predicate true in the set?" 后者无论是哈希集还是列表都是线性时间。
可能满足您需求的数据结构:
创建一个从文本到状态的字典映射,然后在字典中对文本键进行搜索以获得结果状态。理论上搜索和插入的时间复杂度为 O(1);实际上,它取决于散列的质量。
制作一个从文本到状态的排序字典映射。再次,搜索文本。已排序的字典使键在平衡树中排序,因此搜索和插入的时间复杂度为 O(log n)。
30k 不是那么多,所以如果状态是唯一的,你可以这样做。
词典访问速度更快。
var statesDic = model.States.ToDictionary(x => x.Condition, x => x);
var match = statesDic.ConstainsKey(stateText) ? statesDic[stateText] : default(State);
引用 MSDN:
The Dictionary generic class provides a mapping from a set of keys to a set of values. Each addition to the dictionary consists of a value and its associated key. Retrieving a value by using its key is very fast, close to O(1), because the Dictionary class is implemented as a hash table.
您可以找到有关词典的更多信息 here。
还要注意字典使用内存 space 来提高性能,您可以对 300k 项进行快速测试,看看我说的是哪种 space:
var memoryBeforeDic = GC.GetTotalMemory(true);
var dic = new Dictionary<string,object>(300000);
var memoryAfterDic = GC.GetTotalMemory(true);
Console.WriteLine("Memory: {0}", memoryAfterDic - memoryBeforeDic);
我正在使用半随机标记流。对于每个令牌,我都在维护大量数据(包括一些子集合)。
唯一令牌的数量没有限制,但实际上往往在 100,000-300,000 的数量级。
我从一个列表开始,并使用 Linq 查询确定要更新的适当标记对象。
public class Model {
public List<State> States { get; set; }
...
}
var match = model.States.Where(x => x.Condition == stateText).SingleOrDefault();
在前 ~30k 个唯一令牌中,我能够找到并更新~1,100 tokens/sec。
性能分析表明,总 Cpu 周期中的 85% 花在了 Where(...).SingleOrDefault()
上(这是有道理的,列表是一种低效的搜索方式)。
因此,我将列表切换到 HashSet 并再次进行分析,确信 HashSet 能够更快地随机查找。这次,我只处理了 ~900 tokens/sec。在 Linq 上花费的时间几乎相同 (89%)。
所以...首先,我是否滥用了 HashSet
? (使用 Linq 是否强制转换为 IEnumerable,然后是枚举/类似的东西?)
如果不是,我自己实现的最佳模式是什么?我的印象是 HashSet 已经进行了二进制搜索,所以我假设我需要构建某种树结构并具有更小的子集?
从评论中回答一些问题...条件是唯一的(如果我两次获得相同的标记,我想更新相同的条目),HashSet 是股票 .Net 实现(System.Collections.Generic.HashSet<T>
).
更广泛的代码视图是...
var state = new RollingList(model.StateDepth); // Tracks last n items and drops older ones. (Basically an array and an index that wraps around
var tokens = tokeniser.Tokenise(contents); // Iterator
foreach (var token in tokens) {
var stateText = StateToString(ref state);
var match = model.States.Where(x => x.Condition == stateText).FirstOrDefault();
// ... update the match as appropriate for the token
}
var match = model.States.Where(x => x.Condition == stateText).SingleOrDefault();
如果您使用散列集做完全相同的事情,那就没有节省。哈希集针对快速回答问题进行了优化 "is this member in the set?" 而不是 "is there a member that makes this predicate true in the set?" 后者无论是哈希集还是列表都是线性时间。
可能满足您需求的数据结构:
创建一个从文本到状态的字典映射,然后在字典中对文本键进行搜索以获得结果状态。理论上搜索和插入的时间复杂度为 O(1);实际上,它取决于散列的质量。
制作一个从文本到状态的排序字典映射。再次,搜索文本。已排序的字典使键在平衡树中排序,因此搜索和插入的时间复杂度为 O(log n)。
30k 不是那么多,所以如果状态是唯一的,你可以这样做。 词典访问速度更快。
var statesDic = model.States.ToDictionary(x => x.Condition, x => x);
var match = statesDic.ConstainsKey(stateText) ? statesDic[stateText] : default(State);
引用 MSDN:
The Dictionary generic class provides a mapping from a set of keys to a set of values. Each addition to the dictionary consists of a value and its associated key. Retrieving a value by using its key is very fast, close to O(1), because the Dictionary class is implemented as a hash table.
您可以找到有关词典的更多信息 here。 还要注意字典使用内存 space 来提高性能,您可以对 300k 项进行快速测试,看看我说的是哪种 space:
var memoryBeforeDic = GC.GetTotalMemory(true);
var dic = new Dictionary<string,object>(300000);
var memoryAfterDic = GC.GetTotalMemory(true);
Console.WriteLine("Memory: {0}", memoryAfterDic - memoryBeforeDic);