最近,在工作中遇到一个案例,需要比较两组用户并找出它们交集的部分作为结果。难点在于每组的元素数量都比较大(两组都可能有10,000多个元素)。为了给用户提供更流畅的体验,速度不能太拖沓。所以我在找更快的方法来搞定这个交集问题。
在这篇文章中,我利用AI(如Gemini和ChatGPT)和传统的Stackoverflow问答方式来寻找几种实现集合交集的操作方法。使用JMH Java微基准测试套件运行所有测试并生成报告。
候选交叉点方法 双子的方式第一个候选人是双子
双子提供了一种在更大的集合上进行循环的方法(创建一个新的 HashSet
对象),并使用 smallSet.contains()
作为判断条件来决定是否将元素作为结果收集。
我们现在给它起了个名字叫 loopBigSetIsContainedByNewSmallSet
。
但是这种方式,就像双子座一样,在第一行创建一个新的哈希表(HashMap),这看起来有点多余,所以我们提供了一种更简洁的方法,不创建新的HashMap,并将其命名为 loopBigSetIsContainedBySmallSet
(循环判断大集合是否被小集合包含)。
还有一个竞争者是ChatGPT(GPT4o)。
ChatGPT 编写了一个简单的函数,该函数使用 Java 内置方法 Set.retainAll
来实现功能。我们称其为 smallSetRetainAllBigSet
,该名称表示一个小集合保留与大集合相同的元素。
在AI给出解决方案之后,让我们用传统的方法来找找答案。在这里我发现这个问题三年前就已经有人问过了,这个问题中,被采纳的答案也是用了Set.retainAll
,但我们仍然可以从其他答案中找到两种不同的方法。我们来看看。
第一个人的答案是使用Java 8的特性,在较小的集合上使用流(stream),并检查每个元素是否在较大的集合中存在。
我们把这个叫做 streamSmallSetIsContainedByBigSet
,另一个版本叫做 streamSmallSetIsContainedByNewBigSet
,用来和Gemini方式对比。
最后一种方法是使用Google提供的工具库,Guava。在Sets
工具类中有intersection()
这个方法。
我们都知道,JVM需要一些时间(代码执行)来达到稳定性能所需的时间。此外,我们还需要进行性能计算并生成易于理解的结果。这听起来确实很麻烦。但幸运的是,我们有Java微基准测试工具(JMH)来帮我们搞定这一切。
怎么用在线有很多关于如何完成这项任务的指南或教程。我按照jmh-gradle-plugin中的README文档来做,一切都很顺利。如果您想了解更多细节或查看代码实现,请参阅我的仓库,里面有一个实际运行的例子。
场景简介在我的情况中,我需要找出同时出现在这两个集合中的用户ID:一个集合包括那些之前购买过我产品的用户,另一个集合则包括同意接收广告的用户。
为了评估表现,我设计了两个情境。
- 两组都有100,000个UUID,其中有1,000个ID是共同的部分。
- 一组有100,000个UUID,另一组则有1,100个UUID,其中1,000个ID与上一组相同。
我的文章会免费且无限制,但你可以买我一杯啤酒来支持和激励我写更多文章。买我一杯啤酒🍺
你的支持让我更有动力创作更多关于编程的内容。
比较出来的结果不要忘了分享和点赞这篇文章,同时关注我在 Medium,未来会有更多科技故事等着你。感谢你的支持,祝你读得开心!
在这里,我们可以开始运行jmh-gradle-plugin的JMH命令。不过,需要注意一些关键点。
注意懒加载。 小心懒加载。在Java中,有些方法使用了延迟加载的机制(例如Guava的Sets.intersection()
只有在调用SetView
的某些方法时才会返回一个SetView
实例)。这些方法执行起来非常迅速,而其他方法需要的时间在0.045毫秒到0.085毫秒之间,大约为0.045毫秒到0.085毫秒,而延迟加载的方式则只需花费约0.00001毫秒。
所以我们需要在后面调用一些常用的方法。例如,从交集集合中赋值有助于SetView真正执行交集操作,最终我们就能得到正确的结果。
JVM 预热很重要接下来,我们看看JVM预热如何影响结果稳定性。如果只进行一次预热迭代,我们将看到较高的误差,这意味着结果不够稳定。
为了比较,如果我们热身三次迭代,可以获得更稳定的结果和更低的误差。
简而言之 摘要检查最终结果,AI生成的方法虽然起作用,但可能不是最优解。我们还有机会优化并调整给出的答案。特别是在编码细节上,比如创建新实例,实际上使时间成本加倍。因此,如果我们能确保参数只在不是HashSet
时才这么做,性能可以得到提升。
另外,在使用JMH时,我们应该始终注意这两个方面:JVM预热阶段以及方法是否返回了一个延迟加载的结果。这两个因素都会影响我们结果的准确性。
最后,在这场人工智能与人类的对决中,最终的赢家仍然是人类。在我的两个案例中,谷歌 guava 库中的 Sets.intersection()
方法表现优于其他方法,所用时间不到原 AI 生成方法的一半。
- https://github.com/melix/jmh-gradle-plugin(一个用于JMH的Gradle插件的GitHub页面)
- https://stackoverflow.com/questions/2851938/efficiently-finding-the-intersection-of-a-variable-number-of-sets-of-strings(关于如何高效查找字符串变量数集交集的问题页面)
- https://gemini.google.com/app(以下为外部链接)
- https://chatgpt.com/(由OpenAI开发的聊天机器人)
共同学习,写下你的评论
评论加载中...
作者其他优质文章