lambdas 排序_Java8 Lambdas:解释性能缺陷的排序

lambdas 排序

与Peter Lawrey合作撰写 。

几天前,我对使用新的Java8声明式的排序性能提出了一个严重的问题。 在这里查看博客文章。 在那篇文章中,我仅指出了问题,但在这篇文章中,我将更深入地了解和解释问题的原因。 这将通过使用声明式样式重现问题,然后一点一点地修改代码来完成,直到我们消除了性能问题并保留了使用旧样式比较所期望的性能。

回顾一下,我们对此类的实例进行排序:

private static class MyComparableInt{private int a,b,c,d;public MyComparableInt(int i) {a = i%2;b = i%10;c = i%1000;d = i;}public int getA() return a;public int getB() return b;public int getC() return c;public int getD() return d;
}

使用声明性的Java 8样式(如下),大约需要6秒钟才能排序10m个实例:

List mySortedList = myComparableList.stream().sorted(Comparator.comparing(MyComparableInt::getA).thenComparing(MyComparableInt::getB).thenComparing(MyComparableInt::getC).thenComparing(MyComparableInt::getD)).collect(Collectors.toList());

使用自定义排序器(如下)需要约1.6秒的时间来排序10m个实例。

这是排序的代码调用:

List mySortedList = myComparableList.stream().sorted(MyComparableIntSorter.INSTANCE).collect(Collectors.toList());

使用此自定义比较器:

public enum MyComparableIntSorter implements Comparator<MyComparableInt>{INSTANCE;@Overridepublic int compare(MyComparableInt o1, MyComparableInt o2) {int comp = Integer.compare(o1.getA(), o2.getA());if(comp==0){comp = Integer.compare(o1.getB(), o2.getB());if(comp==0){comp = Integer.compare(o1.getC(), o2.getC());if(comp==0){comp = Integer.compare(o1.getD(), o2.getD());}}}return comp;}}

让我们在类中创建一个comparing方法,以便我们可以更紧密地分析代码。 comparing方法的原因是允许我们轻松交换实现,但调用代码保持不变。

在所有情况下,这都是comparing方法的调用方式:

List mySortedList = myComparableList.stream().sorted(comparing(MyComparableInt::getA,MyComparableInt::getB,MyComparableInt::getC,MyComparableInt::getD)).collect(Collectors.toList());

比较的第一个实现几乎是jdk中的一个副本。

public static <T, U extends Comparable<? super U>> Comparator<T> comparing(Function<? super T, ? extends U> ke1,Function<? super T, ? extends U> ke2,Function<? super T, ? extends U> ke3,Function<? super T, ? extends U> ke4){return  Comparator.comparing(ke1).thenComparing(ke2).thenComparing(ke3).thenComparing(ke4);}

毫不奇怪,这花了大约6秒钟才能完成测试-但是至少我们重现了该问题,并为进一步进行奠定了基础。

让我们看一下该测试的飞行记录:

屏幕截图2015年1月22日的11.56.20

可以看出有两个大问题:

  1. lambda$comparing方法中的性能问题
  2. 反复调用Integer.valueOf (自动装箱)

让我们尝试处理比较方法中的第一个方法。 乍一看,这似乎很奇怪,因为当您查看代码时,该方法中没有发生太多事情。 然而,随着代码找到该函数的正确实现,虚拟表查找将在这里广泛进行。 当从一行代码中调用多种方法时,将使用虚拟表查找。 我们可以通过下面的comparing实现消除这种延迟源。 通过扩展Function接口的所有用途,每一行只能调用一个实现,因此可以内联该方法。

public static <T, U extends Comparable<? super U>> Comparator<T> comparing(Function<? super T, ? extends U> ke1,Function<? super T, ? extends U> ke2,Function<? super T, ? extends U> ke3,Function<? super T, ? extends U> ke4){return  (c1, c2) -> {int comp = compare(ke1.apply(c1), ke1.apply(c2));if (comp == 0) {comp = compare(ke2.apply(c1), ke2.apply(c2));if (comp == 0) {comp = compare(ke3.apply(c1), ke3.apply(c2));if (comp == 0) {comp = compare(ke4.apply(c1), ke4.apply(c2));}}}return comp;};}

通过展开方法,JIT应该能够内联方法查找。

确实,时间几乎减半到3.5秒,让我们看一下此运行的飞行记录:

屏幕截图2015年1月22日的11.57.58

当我第一次看到此消息时,我感到非常惊讶,因为到目前为止,我们还没有进行任何更改来减少对Integer.valueOf的调用,但是该百分比已经下降了! 实际上发生的事情是,由于我们进行了允许内联的更改,已对Integer.valueOf进行了内联,并且将Integer.valueOf花费的时间归咎于调用程序( lambda$comparing ),后者已对被调用者( Integer.valueOf )。 这是事件探查器中的一个常见问题,因为他们可能会误解应归咎于哪种方法,尤其是在进行内联时。

但是我们知道在之前的Flight Recording Integer.valueOf
已突出显示,因此让我们通过comparing实现comparing删除,看看是否可以进一步减少时间。

return  (c1, c2) -> {int comp = compare(ke1.applyAsInt(c1), ke1.applyAsInt(c2));if (comp == 0) {comp = compare(ke2.applyAsInt(c1), ke2.applyAsInt(c2));if (comp == 0) {comp = compare(ke3.applyAsInt(c1), ke3.applyAsInt(c2));if (comp == 0) {comp = compare(ke4.applyAsInt(c1), ke4.applyAsInt(c2));}}}return comp;
};

通过这种实现,时间可以缩短到1.6s,这是我们使用自定义比较器可以实现的。

让我们再次查看此运行的飞行记录:

屏幕截图2015年1月22日的12.59.27

现在,所有时间都在使用实际的排序方法,而不是开销。

总之,我们从这次调查中学到了一些有趣的事情:

  1. 由于自动装箱和虚拟表查找的成本,在某些情况下,使用新的Java8声明式排序将比编写自定义比较器慢4倍。
  2. FlightRecorder虽然比其他分析器要好(有关此问题,请参阅我的第一篇博客文章 ),但仍将时间归因于错误的方法,尤其是在进行内联时。

翻译自: https://www.javacodegeeks.com/2015/01/java8-lambdas-sorting-performance-pitfall-explained.html

lambdas 排序

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/338177.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

strncmp函数用法是什么

strncmp函数用法&#xff1a;函数原型int strcmp(char *str1,char * str2&#xff0c;int n)功能比较字符串str1和str2的前n个字符。头文件#include 返回值返回值&#xff1a;返回整数值&#xff1a;当str1<str2时&#xff0c;返回值<0&#xff1b; str1"str2时&…

分享10个适合初学者学习的C开源项目代码

1.WebbenchWebbench 是一个在 linux 下使用的非常简单的网站压测工具。它使用 fork ()模拟多个客户端同时访问我们设定的 URL&#xff0c;测试网站在压力下工作的性能&#xff0c;最多可以模拟 3 万个并发连接去测试网站的负载能力。Webbench 使用C语言编写&#xff0c; 代码实…

tomcat与tomee_Apache TomEE(和Tomcat)的自签名证书

tomcat与tomee可能在大多数Java EE项目中&#xff0c;您将拥有具有SSL支持&#xff08; https &#xff09;的部分或整个系统&#xff0c;因此浏览器和服务器可以通过安全连接进行通信。 这意味着在处理数据之前&#xff0c;已发送的数据已加密&#xff0c;传输并最终解密。 …

C 线程的使用~(上)

C 11 之前&#xff0c;C 语言没有对并发编程提供语言级别的支持&#xff0c;这使得我们在编写可移植的并发程序时&#xff0c;存在诸多的不便。现在 C 11 中增加了线程以及线程相关的类&#xff0c;很方便地支持了并发编程&#xff0c;使得编写的多线程程序的可移植性得到了很大…

k8s中graphite_在Graphite中存储Hystrix的几个月历史指标

k8s中graphiteHystrix的杀手级功能之一是低延迟&#xff0c;数据密集和美观的仪表板 &#xff1a; 即使这只是Hystrix实际操作的副作用&#xff08;断路器&#xff0c;线程池&#xff0c;超时等&#xff09;&#xff0c;它也往往是最令人印象深刻的功能。 为了使其工作&#…

C语言中的“三字母词”坑了工程师

某软件工程师接盘了前同事的项目&#xff0c;进度一拖再拖&#xff0c;最后发现问题出现在如下代码&#xff1a;// 注释语句 ??/2a b c;请注意代码中的“??/”&#xff0c;就是这注释隐藏的很深&#xff0c;让项目一拖再拖。"??/"会被编译器当作 /&#xff0c…

C 线程的使用~(下)

2.3 detach()detach() 函数的作用是进行线程分离&#xff0c;分离主线程和创建出的子线程。在线程分离之后&#xff0c;主线程退出也会一并销毁创建出的所有子线程&#xff0c;在主线程退出之前&#xff0c;它可以脱离主线程继续独立的运行&#xff0c;任务执行完毕之后&#x…

用c语言编写爱心的代码是什么

用c语言编写爱心的代码&#xff1a;输入完整代码如下&#xff1a;#include int main(void){float a,x,y;for(y1.5f; y>-1.5f; y-0.1f){for(x-1.5f; x<1.5f; x 0.05f){a x*x y*y-1;char ch a*a*a-x*x*y*y*y<0.0f?*: ; putchar(ch); }printf("\n");}retur…

c++ lambda 重载_您会后悔对Lambdas应用重载!

c lambda 重载编写好的API很难。 非常辛苦。 如果您希望用户喜欢您的API&#xff0c;则必须考虑很多事情。 您必须在以下两者之间找到适当的平衡&#xff1a; 用处 易用性 向后兼容 前向兼容性 之前&#xff0c;在我们的文章&#xff1a; 如何设计良好的常规API中&#xf…

7个华为关于C语言的经典面试题

1、找错void test1(){ char string[10]; char* str1"0123456789"; strcpy(string, str1);}这里string数组越界&#xff0c;因为字符串长度为10&#xff0c;还有一个结束符’\0’。所以总共有11个字符长度。string数组大小为10&#xff0c;这里越界了。PS&am…

linux数组操作 增删改查,linuxea:go数组与数组增删改查(19)

复合数据类型是集合类&#xff0c;并且可以存储多个单值。在golang中存储的数组是相同的数据类型&#xff0c;并且长度也是其中的一个属性。在go中&#xff0c;数组的长度一旦定义&#xff0c;就不可变。如果声明了长度的变量&#xff0c;只能赋值相同的长度的数组数组是具有相…

C语言的特点与创建的基本步骤是什么

C语言的特点与创建的基本步骤是&#xff1a;C 语言特点&#xff1a;1.C语言是一种成功的系统描述语言&#xff0c;用C语言开发的UNIX操作系统就是一个成功的范例;2.同时C语言又是一种通用的程序设计语言&#xff0c;在国际上广泛流行。世界上很多著名的计算公司都成功的开发了不…

谷歌guava_Google Guava:您永远不会知道的5件事

谷歌guava每个开发人员可以使用哪些鲜为人知的Google Guava功能&#xff1f; 它是那里最受欢迎的库之一&#xff0c;它是开源的&#xff0c;您可能已经知道了&#xff0c;它来自人们玩Quidditch作为一项真正的运动的地方&#xff08;至少在The Internship上 &#xff09;。 它…

C语言strcmp函数用法

C语言strcmp函数用法strcmp函数语法为“int strcmp(char *str1,char *str2)”&#xff0c;其作用是比较字符串str1和str2是否相同&#xff0c;如果相同则返回0&#xff0c;如果不同&#xff0c;前者大于后者则返回1&#xff0c;否则返回-1。简单示例&#xff1a;char a[]"…

Linux C 服务器端这条线怎么走?

在校学生的编程语言和数据结构的基础还不错&#xff0c;我认为应该在《操作系统》和《计算机体系结构》这两门课上下功夫&#xff0c;然后才去读编程方面的 APUE、UNP 等书。下面简单谈谈我对学习这两门课的看法和建议&#xff0c;都是站在服务端程序员的角度&#xff0c;从实用…

tp3 默认模块 默认方法_您需要了解的有关默认方法的所有信息

tp3 默认模块 默认方法因此&#xff0c;默认方法是……昨天的新闻&#xff0c;对不对&#xff1f; 是的&#xff0c;但是使用了一年之后&#xff0c;积累了很多事实&#xff0c;我想将这些事实收集在一个地方&#xff0c;供刚开始使用它们的开发人员使用。 甚至有经验的人都可以…

存储过程 锁定并发_Java并发教程–锁定:显式锁定

存储过程 锁定并发1.简介 在许多情况下&#xff0c;使用隐式锁定就足够了。 有时&#xff0c;我们将需要更复杂的功能。 在这种情况下&#xff0c; java.util.concurrent.locks包为我们提供了锁定对象。 当涉及到内存同步时&#xff0c;这些锁的内部机制与隐式锁相同。 区别在于…

C语言 PK 各大编程语言

今天分享一篇关于C语言为何如此有魅力的文章&#xff0c;如果你还在学习哪门语言的路口抉择&#xff0c;建议可以认真看看~以下为CSDN译文&#xff1a;没有什么技术可以应用长达50年之久&#xff0c;除非它真的比大多数其他东西都要好用——对于一种计算机行业的技术来说尤其如…

在switch语句中,case后的标号只能是什么?

switch语句用于基于不同条件执行不同动作。语法格式&#xff1a;switch (变量表达式){case 常量1: 语句;break;case 常量2: 语句;break;case 常量3: 语句;break;...case 常量n: 语句;break;default: 语句;break;}switch语句是一个条件选择语句&#xff0c;找到相同的…

C语言——结构体链表,附完整示例

引用自身的结构体&#xff0c;一个结构体中有一个或多个成员的基类型就是本结构体类型时&#xff0c;说明这个结构体可以引用自己&#xff0c;所以称作引用自身的结构体。例如下面的结构体&#xff1a;struct link{ char ch; struct link *p} a;p是一个可以指向struct link类型…