为了账号安全,请及时绑定邮箱和手机立即绑定

用Java JMH比较处理大规模字符串集合交集的人工与AI方法性能

最近,在工作中遇到一个案例,需要比较两组用户并找出它们交集的部分作为结果。难点在于每组的元素数量都比较大(两组都可能有10,000多个元素)。为了给用户提供更流畅的体验,速度不能太拖沓。所以我在找更快的方法来搞定这个交集问题。

在这篇文章中,我利用AI(如Gemini和ChatGPT)和传统的Stackoverflow问答方式来寻找几种实现集合交集的操作方法。使用JMH Java微基准测试套件运行所有测试并生成报告。

候选交叉点方法
双子的方式

第一个候选人是双子

双子提供了一种在更大的集合上进行循环的方法(创建一个新的 HashSet 对象),并使用 smallSet.contains() 作为判断条件来决定是否将元素作为结果收集。

我们现在给它起了个名字叫 loopBigSetIsContainedByNewSmallSet

双子方式(修改)

但是这种方式,就像双子座一样,在第一行创建一个新的哈希表(HashMap),这看起来有点多余,所以我们提供了一种更简洁的方法,不创建新的HashMap,并将其命名为 loopBigSetIsContainedBySmallSet(循环判断大集合是否被小集合包含)。

ChatGPT的用法

还有一个竞争者是ChatGPT(GPT4o)。

ChatGPT 编写了一个简单的函数,该函数使用 Java 内置方法 Set.retainAll 来实现功能。我们称其为 smallSetRetainAllBigSet,该名称表示一个小集合保留与大集合相同的元素。

在AI给出解决方案之后,让我们用传统的方法来找找答案。在这里我发现这个问题三年前就已经有人问过了,这个问题中,被采纳的答案也是用了Set.retainAll,但我们仍然可以从其他答案中找到两种不同的方法。我们来看看。

Stack Overflow 方法1: stream().filter()

第一个人的答案是使用Java 8的特性,在较小的集合上使用流(stream),并检查每个元素是否在较大的集合中存在。

我们把这个叫做 streamSmallSetIsContainedByBigSet,另一个版本叫做 streamSmallSetIsContainedByNewBigSet,用来和Gemini方式对比。

Stack Overflow 方法二:Guava

最后一种方法是使用Google提供的工具库,Guava。在Sets工具类中有intersection()这个方法。

如何进行JMH测试(JMH:Java微基准测试框架):

我们都知道,JVM需要一些时间(代码执行)来达到稳定性能所需的时间。此外,我们还需要进行性能计算并生成易于理解的结果。这听起来确实很麻烦。但幸运的是,我们有Java微基准测试工具(JMH)来帮我们搞定这一切。

怎么用

在线有很多关于如何完成这项任务的指南或教程。我按照jmh-gradle-plugin中的README文档来做,一切都很顺利。如果您想了解更多细节或查看代码实现,请参阅我的仓库,里面有一个实际运行的例子。

场景简介

在我的情况中,我需要找出同时出现在这两个集合中的用户ID:一个集合包括那些之前购买过我产品的用户,另一个集合则包括同意接收广告的用户。

为了评估表现,我设计了两个情境。

  1. 两组都有100,000个UUID,其中有1,000个ID是共同的部分。
  2. 一组有100,000个UUID,另一组则有1,100个UUID,其中1,000个ID与上一组相同。

我的文章会免费且无限制,但你可以买我一杯啤酒来支持和激励我写更多文章。买我一杯啤酒🍺

![](https://imgapi.imooc.com/66f383c30964847c10900306.jpg)你的支持让我更有动力创作更多关于编程的内容。

不要忘了分享和点赞这篇文章,同时关注我在 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 生成方法的一半。

参考
点击查看更多内容
TA 点赞

若觉得本文不错,就分享一下吧!

评论

作者其他优质文章

正在加载中
  • 推荐
  • 评论
  • 收藏
  • 共同学习,写下你的评论
感谢您的支持,我会继续努力的~
扫码打赏,你说多少就多少
赞赏金额会直接到老师账户
支付方式
打开微信扫一扫,即可进行扫码打赏哦
今天注册有机会得

100积分直接送

付费专栏免费学

大额优惠券免费领

立即参与 放弃机会
意见反馈 帮助中心 APP下载
官方微信

举报

0/150
提交
取消