显然,泛型HashSet<T>
类的搜索性能高于泛型List<T>
类。只需将基于哈希的键与线性方法进行比较即可List<T>
。
但是,计算哈希键本身可能会花费一些CPU周期,因此对于少量项,线性搜索可以真正替代HashSet<T>
。
我的问题:收支平衡在哪里?
为了简化场景(公平地说),我们假定List<T>
类使用元素的Equals()
方法来标识项目。
显然,泛型HashSet<T>
类的搜索性能高于泛型List<T>
类。只需将基于哈希的键与线性方法进行比较即可List<T>
。
但是,计算哈希键本身可能会花费一些CPU周期,因此对于少量项,线性搜索可以真正替代HashSet<T>
。
我的问题:收支平衡在哪里?
为了简化场景(公平地说),我们假定List<T>
类使用元素的Equals()
方法来标识项目。
Answers:
许多人说,一旦达到实际速度,速度就是一个HashSet<T>
永远无法克服的问题List<T>
,但这取决于您的工作。
假设您的List<T>
商品中平均只有5件商品。在大量周期中,如果每个周期添加或删除一个项目,则使用可能会更好List<T>
。
我在我的机器上对此进行了测试,并且,它必须很小,才能从中获得好处List<T>
。对于短字符串列表,对于5号之后的对象,优点在5号之后消失了。
1 item LIST strs time: 617ms
1 item HASHSET strs time: 1332ms
2 item LIST strs time: 781ms
2 item HASHSET strs time: 1354ms
3 item LIST strs time: 950ms
3 item HASHSET strs time: 1405ms
4 item LIST strs time: 1126ms
4 item HASHSET strs time: 1441ms
5 item LIST strs time: 1370ms
5 item HASHSET strs time: 1452ms
6 item LIST strs time: 1481ms
6 item HASHSET strs time: 1418ms
7 item LIST strs time: 1581ms
7 item HASHSET strs time: 1464ms
8 item LIST strs time: 1726ms
8 item HASHSET strs time: 1398ms
9 item LIST strs time: 1901ms
9 item HASHSET strs time: 1433ms
1 item LIST objs time: 614ms
1 item HASHSET objs time: 1993ms
4 item LIST objs time: 837ms
4 item HASHSET objs time: 1914ms
7 item LIST objs time: 1070ms
7 item HASHSET objs time: 1900ms
10 item LIST objs time: 1267ms
10 item HASHSET objs time: 1904ms
13 item LIST objs time: 1494ms
13 item HASHSET objs time: 1893ms
16 item LIST objs time: 1695ms
16 item HASHSET objs time: 1879ms
19 item LIST objs time: 1902ms
19 item HASHSET objs time: 1950ms
22 item LIST objs time: 2136ms
22 item HASHSET objs time: 1893ms
25 item LIST objs time: 2357ms
25 item HASHSET objs time: 1826ms
28 item LIST objs time: 2555ms
28 item HASHSET objs time: 1865ms
31 item LIST objs time: 2755ms
31 item HASHSET objs time: 1963ms
34 item LIST objs time: 3025ms
34 item HASHSET objs time: 1874ms
37 item LIST objs time: 3195ms
37 item HASHSET objs time: 1958ms
40 item LIST objs time: 3401ms
40 item HASHSET objs time: 1855ms
43 item LIST objs time: 3618ms
43 item HASHSET objs time: 1869ms
46 item LIST objs time: 3883ms
46 item HASHSET objs time: 2046ms
49 item LIST objs time: 4218ms
49 item HASHSET objs time: 1873ms
这是显示为图形的数据:
这是代码:
static void Main(string[] args)
{
int times = 10000000;
for (int listSize = 1; listSize < 10; listSize++)
{
List<string> list = new List<string>();
HashSet<string> hashset = new HashSet<string>();
for (int i = 0; i < listSize; i++)
{
list.Add("string" + i.ToString());
hashset.Add("string" + i.ToString());
}
Stopwatch timer = new Stopwatch();
timer.Start();
for (int i = 0; i < times; i++)
{
list.Remove("string0");
list.Add("string0");
}
timer.Stop();
Console.WriteLine(listSize.ToString() + " item LIST strs time: " + timer.ElapsedMilliseconds.ToString() + "ms");
timer = new Stopwatch();
timer.Start();
for (int i = 0; i < times; i++)
{
hashset.Remove("string0");
hashset.Add("string0");
}
timer.Stop();
Console.WriteLine(listSize.ToString() + " item HASHSET strs time: " + timer.ElapsedMilliseconds.ToString() + "ms");
Console.WriteLine();
}
for (int listSize = 1; listSize < 50; listSize+=3)
{
List<object> list = new List<object>();
HashSet<object> hashset = new HashSet<object>();
for (int i = 0; i < listSize; i++)
{
list.Add(new object());
hashset.Add(new object());
}
object objToAddRem = list[0];
Stopwatch timer = new Stopwatch();
timer.Start();
for (int i = 0; i < times; i++)
{
list.Remove(objToAddRem);
list.Add(objToAddRem);
}
timer.Stop();
Console.WriteLine(listSize.ToString() + " item LIST objs time: " + timer.ElapsedMilliseconds.ToString() + "ms");
timer = new Stopwatch();
timer.Start();
for (int i = 0; i < times; i++)
{
hashset.Remove(objToAddRem);
hashset.Add(objToAddRem);
}
timer.Stop();
Console.WriteLine(listSize.ToString() + " item HASHSET objs time: " + timer.ElapsedMilliseconds.ToString() + "ms");
Console.WriteLine();
}
Console.ReadLine();
}
List<T>
游戏引擎更快地添加和删除的东西,并且由于我通常会携带大量对象,因此这种收藏非常理想。
您看错了。是的,对列表的线性搜索将击败HashSet的少量项目。但是性能差异通常对于那么小的集合无关紧要。通常,您需要担心的是大型集合,这就是Big-O的想法。但是,如果您已经衡量了HashSet性能的真正瓶颈,则可以尝试创建混合的List / HashSet,但是您将通过进行大量的经验性能测试来做到这一点-不问SO方面的问题。
when small collection becomes large enough to worry about HashSet vs List?
成千上万的元素重新定义这个问题吗?
HashSet<T>
。在数量较少的情况下,List<T>
可能更快,两者之间的差异可忽略不计。”
它本质上是没有意义的两个结构比较的性能不同行为。使用传达意图的结构。即使您说List<T>
不会重复,并且迭代顺序也可以使其与a相提并论,但这并不是HashSet<T>
一个好选择,List<T>
因为它的容错性相对较低。
也就是说,我将检查性能的其他方面,
+------------+--------+-------------+-----------+----------+----------+-----------+
| Collection | Random | Containment | Insertion | Addition | Removal | Memory |
| | access | | | | | |
+------------+--------+-------------+-----------+----------+----------+-----------+
| List<T> | O(1) | O(n) | O(n) | O(1)* | O(n) | Lesser |
| HashSet<T> | O(n) | O(1) | n/a | O(1) | O(1) | Greater** |
+------------+--------+-------------+-----------+----------+----------+-----------+
即使在这两种情况下加法均为O(1),在HashSet中它的速度都相对较慢,因为在存储哈希码之前会涉及到预先计算哈希码的成本。
HashSet出色的可扩展性具有内存成本。每个条目及其哈希码都存储为一个新对象。本文可能会给您一个想法。
是否使用HashSet <>或List <>取决于您需要如何访问集合。如果您需要保证项目的顺序,请使用列表。如果不这样做,请使用HashSet。让Microsoft担心其哈希算法和对象的实现。
HashSet将访问项目,而不必枚举集合(O(1)或其附近的复杂性),并且由于List保证顺序,与HashSet不同,某些项目将必须枚举(O(n)的复杂性)。
List
首选a,因为您可以记住索引-就是这种情况正在描述。
只是想我会参考一些针对不同情况的基准来说明先前的答案:
对于每种情况,查找出现的值:
在每种情况下,我生成随机大小的随机字符串列表,然后将每个列表馈入哈希集。每个方案都运行了10,000次,基本上是:
(测试伪代码)
stopwatch.start
for X times
exists = list.Contains(lookup);
stopwatch.stop
stopwatch.start
for X times
exists = hashset.Contains(lookup);
stopwatch.stop
在Windows 7、12GB Ram,64位,Xeon 2.8GHz上进行了测试
---------- Testing few small strings ------------
Sample items: (16 total)
vgnwaloqf diwfpxbv tdcdc grfch icsjwk
...
Benchmarks:
1: hashset: late -- 100.00 % -- [Elapsed: 0.0018398 sec]
2: hashset: middle -- 104.19 % -- [Elapsed: 0.0019169 sec]
3: hashset: end -- 108.21 % -- [Elapsed: 0.0019908 sec]
4: list: early -- 144.62 % -- [Elapsed: 0.0026607 sec]
5: hashset: start -- 174.32 % -- [Elapsed: 0.0032071 sec]
6: list: middle -- 187.72 % -- [Elapsed: 0.0034536 sec]
7: list: late -- 192.66 % -- [Elapsed: 0.0035446 sec]
8: list: end -- 215.42 % -- [Elapsed: 0.0039633 sec]
9: hashset: early -- 217.95 % -- [Elapsed: 0.0040098 sec]
10: list: start -- 576.55 % -- [Elapsed: 0.0106073 sec]
---------- Testing many small strings ------------
Sample items: (10346 total)
dmnowa yshtrxorj vthjk okrxegip vwpoltck
...
Benchmarks:
1: hashset: end -- 100.00 % -- [Elapsed: 0.0017443 sec]
2: hashset: late -- 102.91 % -- [Elapsed: 0.0017951 sec]
3: hashset: middle -- 106.23 % -- [Elapsed: 0.0018529 sec]
4: list: early -- 107.49 % -- [Elapsed: 0.0018749 sec]
5: list: start -- 126.23 % -- [Elapsed: 0.0022018 sec]
6: hashset: early -- 134.11 % -- [Elapsed: 0.0023393 sec]
7: hashset: start -- 372.09 % -- [Elapsed: 0.0064903 sec]
8: list: middle -- 48,593.79 % -- [Elapsed: 0.8476214 sec]
9: list: end -- 99,020.73 % -- [Elapsed: 1.7272186 sec]
10: list: late -- 99,089.36 % -- [Elapsed: 1.7284155 sec]
---------- Testing few long strings ------------
Sample items: (19 total)
hidfymjyjtffcjmlcaoivbylakmqgoiowbgxpyhnrreodxyleehkhsofjqenyrrtlphbcnvdrbqdvji...
...
Benchmarks:
1: list: early -- 100.00 % -- [Elapsed: 0.0018266 sec]
2: list: start -- 115.76 % -- [Elapsed: 0.0021144 sec]
3: list: middle -- 143.44 % -- [Elapsed: 0.0026201 sec]
4: list: late -- 190.05 % -- [Elapsed: 0.0034715 sec]
5: list: end -- 193.78 % -- [Elapsed: 0.0035395 sec]
6: hashset: early -- 215.00 % -- [Elapsed: 0.0039271 sec]
7: hashset: end -- 248.47 % -- [Elapsed: 0.0045386 sec]
8: hashset: start -- 298.04 % -- [Elapsed: 0.005444 sec]
9: hashset: middle -- 325.63 % -- [Elapsed: 0.005948 sec]
10: hashset: late -- 431.62 % -- [Elapsed: 0.0078839 sec]
---------- Testing many long strings ------------
Sample items: (5000 total)
yrpjccgxjbketcpmnvyqvghhlnjblhgimybdygumtijtrwaromwrajlsjhxoselbucqualmhbmwnvnpnm
...
Benchmarks:
1: list: early -- 100.00 % -- [Elapsed: 0.0016211 sec]
2: list: start -- 132.73 % -- [Elapsed: 0.0021517 sec]
3: hashset: start -- 231.26 % -- [Elapsed: 0.003749 sec]
4: hashset: end -- 368.74 % -- [Elapsed: 0.0059776 sec]
5: hashset: middle -- 385.50 % -- [Elapsed: 0.0062493 sec]
6: hashset: late -- 406.23 % -- [Elapsed: 0.0065854 sec]
7: hashset: early -- 421.34 % -- [Elapsed: 0.0068304 sec]
8: list: middle -- 18,619.12 % -- [Elapsed: 0.3018345 sec]
9: list: end -- 40,942.82 % -- [Elapsed: 0.663724 sec]
10: list: late -- 41,188.19 % -- [Elapsed: 0.6677017 sec]
---------- Testing few ints ------------
Sample items: (16 total)
7266092 60668895 159021363 216428460 28007724
...
Benchmarks:
1: hashset: early -- 100.00 % -- [Elapsed: 0.0016211 sec]
2: hashset: end -- 100.45 % -- [Elapsed: 0.0016284 sec]
3: list: early -- 101.83 % -- [Elapsed: 0.0016507 sec]
4: hashset: late -- 108.95 % -- [Elapsed: 0.0017662 sec]
5: hashset: middle -- 112.29 % -- [Elapsed: 0.0018204 sec]
6: hashset: start -- 120.33 % -- [Elapsed: 0.0019506 sec]
7: list: late -- 134.45 % -- [Elapsed: 0.0021795 sec]
8: list: start -- 136.43 % -- [Elapsed: 0.0022117 sec]
9: list: end -- 169.77 % -- [Elapsed: 0.0027522 sec]
10: list: middle -- 237.94 % -- [Elapsed: 0.0038573 sec]
---------- Testing many ints ------------
Sample items: (10357 total)
370826556 569127161 101235820 792075135 270823009
...
Benchmarks:
1: list: early -- 100.00 % -- [Elapsed: 0.0015132 sec]
2: hashset: end -- 101.79 % -- [Elapsed: 0.0015403 sec]
3: hashset: early -- 102.08 % -- [Elapsed: 0.0015446 sec]
4: hashset: middle -- 103.21 % -- [Elapsed: 0.0015618 sec]
5: hashset: late -- 104.26 % -- [Elapsed: 0.0015776 sec]
6: list: start -- 126.78 % -- [Elapsed: 0.0019184 sec]
7: hashset: start -- 130.91 % -- [Elapsed: 0.0019809 sec]
8: list: middle -- 16,497.89 % -- [Elapsed: 0.2496461 sec]
9: list: end -- 32,715.52 % -- [Elapsed: 0.4950512 sec]
10: list: late -- 33,698.87 % -- [Elapsed: 0.5099313 sec]
List
仅需0.17 毫秒即可执行一次查找,并且HashSet
在查找频率达到荒谬的水平之前,不太可能需要替换它。届时,通常使用List的问题最少。
与往常一样,答案是“ 取决于 ”。我以您正在谈论的C#标签为基础。
最好的选择是确定
并编写一些测试用例。
它还取决于您对列表进行排序的方式(如果对列表进行了排序),需要进行哪种比较,对列表中的特定对象执行“比较”操作需要多长时间,甚至取决于您打算如何使用列表。采集。
通常,最佳选择不是基于您正在使用的数据大小,而是您打算如何访问它。您是否具有与特定字符串或其他数据相关联的每条数据?基于散列的集合可能是最好的。您存储的数据顺序是否很重要,还是需要同时访问所有数据?定期列出可能会更好。
额外:
当然,我的上述评论假设“性能”意味着数据访问。还有其他需要考虑的问题:当您说“表现”时,您正在寻找什么?表现个人价值在找吗?它是大型(10000、100000或更多)值集的管理吗?它是用数据填充数据结构的性能吗?删除数据?访问单个数据位?替换值?遍历值?内存使用情况?数据复制速度?例如,如果您通过字符串值访问数据,但是您的主要性能要求是最小的内存使用量,那么您可能会遇到冲突的设计问题。
您可以使用HybridDictionary,它可以自动检测断点并接受空值,使其本质上与HashSet相同。