lambda 延迟执行_Java Lambdas和低延迟

lambda 延迟执行

总览

有关在Java和低延迟中使用Lambda的主要问题是: 他们会产生垃圾吗,您能做些什么吗?

背景

我正在开发一个支持不同有线协议的库。 这个想法是,您可以描述要写入/读取的数据,并且有线协议确定它是否使用带有JSon或YAML等字段的文本,带有FIX的字段编号的文本,带有BSON的字段名称的二进制或YAML的二进制形式,具有字段名称,字段编号或完全没有字段meta的二进制。 值可以是固定长度,变量长度和/或自描述数据类型。

其想法是它可以处理各种架构更改,或者如果您可以确定架构是相同的(例如,通过TCP会话),则可以跳过所有内容而仅发送数据。

另一个大想法是使用lambda支持这一点。

Lambdas有什么问题

主要问题是需要在低延迟应用程序中避免大量垃圾。 名义上,每次您看到lambda代码时,这都是一个新对象。

幸运的是,Java 8大大改进了Escape Analysis 。 Escape Analysis使JVM通过将新对象解包到堆栈中来替换它们,从而有效地为您分配了堆栈。 Java 7中提供了此功能,但是很少消除对象。 注意:当您使用探查器时,它往往会阻止Escape Analysis正常工作,因此您不能信任使用代码注入的探查器,因为探查器可能会说正在创建对象,而没有探查器则不会创建对象。 Flight Recorder似乎确实与Escape Analysis混为一谈。

Escape Analysis一直都有古怪之处,而且看来仍然如此。 例如,如果您具有IntConsumer或任何其他原始使用者,则可以在Java 8 update 20 – update 40中消除lambda的分配。但是,在没有发生这种情况的情况下,布尔值是一个例外。 希望在将来的版本中可以解决此问题。

另一个怪癖是发生对象消除的方法的大小(内联后)很重要,在相对适度的方法中,转义分析可以放弃。

具体情况

就我而言,我有一个读取方法,如下所示:

public void readMarshallable(Wire wire) throws StreamCorruptedException {
    wire.read(Fields.I).int32(this::i)
            .read(Fields.J).int32(this::j)
            .read(Fields.K).int32(this::k)
            .read(Fields.L).int32(this::l)
            .read(Fields.M).int32(this::m)
            .read(Fields.N).int32(this::n)
            .read(Fields.O).int32(this::o)
            .read(Fields.P).int32(this::p)
            .read(Fields.Q).int32(this::q)
            .read(Fields.R).int32(this::r)
            .read(Fields.S).int32(this::s)
            .read(Fields.T).int32(this::t)
            .read(Fields.U).int32(this::u)
            .read(Fields.V).int32(this::v)
            .read(Fields.W).int32(this::w)
            .read(Fields.X).int32(this::x)
    ;
}

我正在使用lambda设置框架可以处理可选,缺失或乱序字段的字段。 在最佳情况下,可以按提供的顺序使用字段。 在模式更改的情况下,顺序可以不同或具有不同的字段集。 使用lambda可使框架以不同方式处理顺序字段和乱序字段。

使用此代码,我进行了测试,对对象进行了1000万次序列化和反序列化。 我将JVM配置为具有-Xmn14m -XX:SurvivorRatio=5的eden大小为10 MB的伊甸园空间-Xmn14m -XX:SurvivorRatio=5空间是比率为5:2的两个幸存者空间的5倍。 Eden空间是年轻一代总数的5/7,即10 MB。

通过具有10 MB的Eden大小和1000万次测试,我可以通过计算-verbose:gc打印的GC的数量来估计产生的垃圾。对于我得到的每个GC,每个测试平均要创建一个字节。 当我改变序列化和反序列化的字段数时,我在Intel i7-3970X上获得了以下结果。

Lambda相关垃圾

在此图表中,您可以看到,对于以相同方法反序列化的1到8个字段(即最多8个lambda),几乎不会创建垃圾,即最多只有一个GC。 但是,在9个或更多字段或lambda上,转义分析失败,并且您将创建垃圾,垃圾随文件数的增加而线性增加。

我不希望您相信8是一个神奇的数字。 尽管我找不到这样的命令行设置,但是它更可能是方法大小(以字节为单位)的限制。 当方法增长到170字节时,会发生差异。

有什么可以做的吗? 最简单的“修复”方法是将一种方法中的一半字段反序列化,将另一种字段中的一半字段反序列化,从而将代码分为两种方法(如果需要,可以将更多方法拆分),从而能够在不产生垃圾的情况下反序列化9到16个字段。 这是“ bytes(2)”和“ ns(2)”的结果。 通过消除垃圾,代码的平均运行速度也更快。

注意:使用14 x 32位整数对对象进行序列化和反序列化的时间不到100 ns。

其他说明:

当使用事件探查器YourKit(在这种情况下)时,由于Escape Analysis失败,没有产生垃圾的代码开始产生垃圾。

打印了方法内联 ,发现某些关键方法中的assert语句阻止了它们的内联,因为这使方法变大了。 我通过在启用断言的情况下通过工厂方法创建断言的方式来创建by main类的子类来解决此问题。 默认类没有断言,也没有性能影响。

在我提出这些断言之前,我只能反序列化7个字段而不会触发垃圾回收。

当我用匿名内部类替换lambda时,我看到了类似的对象消除,尽管在大多数情况下,如果可以使用首选的lambda。

结论

Java 8在清除寿命很短的对象产生的垃圾方面似乎更聪明。 这意味着在低延迟应用程序中可以选择诸如传递lambda之类的技术。

编辑

尽管我不确定为什么,但我找到了在这种情况下有用的选项。

如果我使用选项-XX:InlineSmallCode=1000 (默认值)并将其更改为-XX:InlineSmallCode=5000则上面的“固定”示例将开始产生垃圾,但是如果将其减少为-XX:InlineSmallCode=500甚至是代码我最初给出的示例不产生垃圾。

翻译自: https://www.javacodegeeks.com/2015/01/java-lambdas-and-low-latency.html

lambda 延迟执行

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值