数组还是HashSet?

HashSet,数组 · 浏览次数 : 1285

小编点评

**结论:** 在元素很少的情况下,直接使用数组可能比使用 HashSet<T> 快。但当元素数量超过 32 个时,使用 HashSet<T> 的性能将明显优于数组。 **为什么数组比 HashSet<T> 快?** * **SIMD:**在数组元素数量少的情况下,使用数组的 `for` 循环可以利用 SIMD 来优化运算,提高效率。 * **内存布局:**数组元素通常是连续存储的,这可以优化数组的访问性能。 **具体情况:** * 当元素数量少于 16 个时,使用 `for` 循环会比 HashSet<T> 快。 * 当元素数量在 16-32 个之间时,速度最快是 HashSet<T>。 * 当元素数量超过 32 个时,使用 HashSet<T> 的性能最优。

正文

我记得大约在半年前,有个朋友问我一个问题,现在有一个选型:

一个性能敏感场景,有一个集合,需要确定某一个元素在不在这个集合中,我是用数组直接Contains还是使用HashSet<T>.Contains

大家肯定想都不用想,都选使用HashSet<T>,毕竟HashSet<T>的时间复杂度是O(1),但是后面又附加了一个条件:

这个集合的元素很少,就4-5个。

那这时候就有一些动摇了,只有4-5个元素,是不是用数组Contains或者直接遍历会不会更快一些?当时我也觉得可能元素很少,用数组就够了。

而最近在编写代码时,又遇到了同样的场景,我决定来做一下实验,看看元素很少的情况下,是不是使用数组优于HashSet<T>

测试

我构建了一个测试,分别尝试在不同的容量下,查找一个元素,使用数组和HashSet的区别,代码如下所示:

[GcForce(true)]
[MemoryDiagnoser]
[Orderer(SummaryOrderPolicy.FastestToSlowest)]
public class BenchHashSet
{
	private HashSet<string> _hashSet;
	private string[] _strings;

	[Params(1,2,4,64,512,1024)]
	public int Size { get; set; }

	[GlobalSetup]
	public void Setup()
	{
		_strings = Enumerable.Range(0, Size).Select(s => s.ToString()).ToArray();
		_hashSet = new HashSet<string>(_strings);
	}

	[Benchmark(Baseline = true)]
	public bool EnumerableContains() => _strings.Contains("8192");

	[Benchmark]
	public bool HashSetContains() => _hashSet.Contains("8192");
}

大家猜猜结果怎么样,就算Size只为1,那么HashSet也比数组Contains遍历快40%。

那么故事就这么结束了吗?所以无论如何场景我们都直接无脑使用HashSet就行了吗?大家看滑动条就知道,故事没有这么简单。

刚刚我们是引用类型的比较,那值类型怎么样?结论就是一样的结果,就算只有1个元素也比数组的Contains快。

那么问题出在哪里?点进去看一下数组Contains方法的实现就清楚了,这个东西使用的是Enumerable迭代器匹配。

那么我们直接来个原始的,Array.IndexOf匹配和for循环匹配试试,于是有了如下代码:

[GcForce(true)]
[MemoryDiagnoser]
[Orderer(SummaryOrderPolicy.FastestToSlowest)]
public class BenchHashSetValueType
{
	private HashSet<int> _hashSet;
	private int[] _arrays;

	[Params(1,4,16,32,64)]
	public int Size { get; set; }
	

	[GlobalSetup]
	public void Setup()
	{
		_arrays = Enumerable.Range(0, Size).ToArray();
		_hashSet = new HashSet<int>(_arrays);
	}

	[Benchmark(Baseline = true)]
	public bool EnumerableContains() => _arrays.Contains(42);
	
	[Benchmark]
	public bool ArrayContains() => Array.IndexOf(_arrays,42) > -1;

	[Benchmark]
	public bool ForContains()
	{
		for (int i = 0; i < _arrays.Length; i++)
		{
			if (_arrays[i] == 42) return true;
		}

		return false;
	}

	[Benchmark]
	public bool HashSetContains() => _hashSet.Contains(42);
}

接下来结果就和我们预想的差不多了,在数组元素小的时候,使用原始的for循环比较会快,然后HashSet就变为最快的了,在更多元素的场景中Array.IndexOf会比for更快:

至于为什么在元素多的情况Array.IndexOf会比for更快,那是因为Array.IndexOf底层使用了SIMD来优化,在之前的文章中,我们多次提到了SIMD,这里就不赘述了。

既然如此我们再来确认一下,到底多少个元素以内用for会更快,可以看到16个元素以内,for循环会快于HashSet:

总结

所以我们应该选择HashSet<T>还是数组呢?这个就需要分情况简单的总结一下:

  • 在小于16个元素场景,使用for循环匹配会比较快。
  • 16-32个元素的场景,速度最快是HashSet<T>然后是Array.IndexOfforIEnumerable.Contains
  • 大于32个元素的场景,速度最快是HashSet<T>然后是Array.IndexOfIEnumerable.Containsfor

从这个上面来看,大于32个元素就不合适直接用for比较了。不过这些差别都很小,除非是性能非常敏感的场景,可以忽略不计,本文解决了笔者的一些困扰,简单记录一下。

与数组还是HashSet?相似的内容:

数组还是HashSet?

我记得大约在半年前,有个朋友问我一个问题,现在有一个选型: 一个性能敏感场景,有一个集合,需要确定某一个元素在不在这个集合中,我是用数组直接Contains还是使用HashSet.Contains? 大家肯定想都不用想,都选使用HashSet,毕竟HashSet的时间复杂度是O(1

fastadmin的导出到excel功能

正常的excel导出没什么问题,最近一直头疼的是怎么导出数据中包含图片,并且图片还是数组?????by user 悦悦 https://www.cnblogs.com/nuanai 1、导出的excel布局是图片分行显示 2、导出图片路径,并且已另外的文字设置超链接 3、其他行合并(这个还没有想好怎

GO数组解密:从基础到高阶全解

在本文中,我们深入探讨了Go语言中数组的各个方面。从基础概念、常规操作,到高级技巧和特殊操作,我们通过清晰的解释和具体的Go代码示例为读者提供了全面的指南。无论您是初学者还是经验丰富的开发者,这篇文章都将助您更深入地理解和掌握Go数组的实际应用。 关注公众号【TechLeadCloud】,分享互联网

[转帖]redis数据结构详解之Hash(四)

https://www.cnblogs.com/Alex80/p/5320091.html 序言 Hash数据结构累似c#中的dictionary,大家对数组应该比较了解,数组是通过索引快速定位到指定元素的,无论是访问数组的第一个元素还是最后一个元素,所耗费的时间都是一样的,但是数组中的索引却没有实

[转帖]Shell 标准输入和输出

https://my.oschina.net/jiagoushi/blog/5609783 无论是要交给程序处理的数据,还是控制脚本的简单命令,都少不了输入和输出。程序要做的第一件事就是处理如同一阴一阳的 “输入与输出”。 1 、从文件获取输入 当我们希望向文件输出内容时,我们可以通过符号 > 或

iOS APP启动广告实现方式 与 APP唤端调用

APP启动广告功能实现要从2个方面思考 一是UI方案,怎样处理广告页与主页之间的切换方式。 二是广告页展示时机,是使用后台实时广告数据还是使用本地缓存广告数据。后台数据方式获取广告最新但是用户要等待后台返回数据后才能展示,增加用户等待时间。使用本地缓存启动速度快但数据更新不及时。 UI方案实现 双W

【ASP.NET Core】MVC操作方法如何绑定Stream类型的参数

咱们都知道,MVC在输入/输出中都需要模型绑定。因为HTTP请求发送的都是文本,为了使其能变成各种.NET 类型,于是在填充参数值之前需 ModelBinder 的参与,以将文本转换为 .NET 类型。 尽管 ASP.NET Core 已内置基础类型和复杂类型的各种 Binder,但有些数据还是不能

python 如何判断一组数呈上升还是下降趋势

1. python 判断一组数呈上升还是下降趋势的方法 要判断一组数(数列)是呈上升趋势、下降趋势还是无明显趋势,我们可以比较数列中相邻元素的差值。如果大部分差值都是正数,则数列呈上升趋势;如果大部分差值都是负数,则数列呈下降趋势;如果正负差值数量相当或差值接近于零,则数列无明显趋势。 以下是一个使

提高 MySQL查询效率的方法

当涉及到提高MySQL查询效率时,以下是一些重要的策略和技巧,可以帮助你优化数据库性能。无论你是一个Web开发者、数据工程师还是系统管理员,这些方法都可以帮助你确保你的MySQL数据库运行得更快、更有效。 索引优化: 使用索引是提高查询性能的关键。确保在经常用于过滤和排序的列上创建索引。 使用复合索

Llama2-Chinese项目:2.3-预训练使用QA还是Text数据集?

Llama2-Chinese项目给出pretrain的data为QA数据格式,可能会有疑问pretrain不应该是Text数据格式吗?而在Chinese-LLaMA-Alpaca-2和open-llama2预训练使用的LoRA技术,给出pretrain的data为Text数据格式。所以推测应该pre