我以前曾在博客中介绍我们在下一个Camel 3.1版本(第1部分)中所做的优化 。
今天,我想发布大约4周后的最新状态更新。
我们集中在三个方面优化骆驼核心:
- 不必要的对象分配
- 不必要的方法调用
- 提高绩效
换句话说,我们使Camel创建更少的对象,调用更少的方法并提高路由过程中的性能。
为了帮助识别骆驼核心中的这些问题,我们使用了一条简单的骆驼路线:
来自timer:foo
记录:foo
其他时候,我们专注于更长的路线:
来自timer:foo
记录:foo1
记录:foo2
记录:foo3
…
登录:fooN
或关注bean组件:
来自timer:foo
到bean:foo
等等。 我们还为计时器组件添加了一个不包含元数据的选项,因此消息不包含任何正文,标头或交换属性。 这使我们可以专注于纯路由引擎及其开销。
因此,所有这些共同帮助确定了许多较小的改进点,共同取得了巨大的成功。
tl:dr –显示数字
好吧,让我们先发布一些数字,然后再详细说明已完成的工作。
对象分配–(5分钟采样)
骆驼2.25 2.9 M创建对象
骆驼3.0 55 M对象创建
骆驼3.1 1.8 M对象创建
好的,我们必须承认Camel 3.0在路由过程中存在过多的对象分配问题。 没有内存泄漏,但是会创建很多不必要的对象。 我将在下面详细说明原因。
但是,有趣的是骆驼2.25和3.1之间的增益(创建的对象减少40%)。
方法调用–(5分钟采样)
骆驼2.25 139种不同的骆驼使用方法
骆驼3.0 167种不同的骆驼使用方法
Camel 3.1使用84种不同的Camel方法
上表列出了Camel在路由过程中从Camel调用的方法数量。 数据不包括JDK中的所有方法。 由于我们无法优化它们,但是我们可以优化Camel源代码。
从表中可以看出,我们已有改进。 骆驼3.1的使用率不到3.0的一半,比骆驼2.2.5的使用率低40%。
骆驼3.0
好的,所以Camel 3.0在使用过多内存方面存在问题。 一个重要的原因是新的反应式执行器现在可以通过事件循环执行路由中的每个步骤,方法是将任务移交给队列,并让工作人员执行任务。 因此,此切换现在需要创建其他对象并将任务存储在队列中等。
最大的一些成功是避免创建TRACE日志消息,不幸的是,无论是否启用了TRACE日志记录级别,始终都会创建该消息。 另一个大胜利是避免使用子元素创建路由过程的toString表示形式。 取而代之的是,骆驼现在只输出进程的id,这是一个快速的操作,并且不分配新对象。
另一个问题是使用java.util.stream的新代码。 这既是祝福也是诅咒(主要是对快速代码的诅咒)。 因此,通过使用普通的for循环,if结构并在核心路由引擎的关键部分避免使用java.util.stream,我们减少了对象分配。
Camel 3也是高度模块化的,例如,在Camel 2.x中,我们将所有类都放在同一类路径中,并且可以使用instanceof检查。 因此,在Camel 3中,我们有一些代码在执行此类检查时表现很差(再次是Java util流)。
另一个问题是被动执行器,它使用LinkedList作为队列。 因此,如果您有任务进入队列并且工作人员以相同的速度处理它们,因此队列为空/耗尽,那么LinkedList的性能会很差,因为它会不断分配/释放对象。 通过切换到预分配大小为16的ArrayQueue,则队列中始终有空间容纳任务,并且不会发生分配/取消分配。
还有更多优化,但是上面提到的优化可能是最大的问题。 然后,许多较小的优化组合在一起。
许多较小的优化
骆驼的UUID生成器正在使用一些字符串连接,这会花费很多。 我们减少了在消息和工作单元中生成UUID的需求,因此每个交换只生成1个。
骆驼路由引擎中的内部建议(建议= AOP之前/之后)。 这些建议中的一些具有从前到后都需要保留的状态,这意味着需要存储对象。 在我们为所有建议分配一个数组之前,即使对于那些没有状态的建议也是如此,因此存储了null。 现在,我们仅分配具有状态的建议的确切数目的数组。 (非常小的胜利,例如object [6] vs object [2]等,但这是在骆驼路线中的每一步发生的,所以总的来说是合计的。) 另一个胜利是,如果不需要内部路由处理器,则避免在UnitOfWork周围进行AOP。 这避免了额外的方法调用,并为after任务分配了一个回调对象。 由于所有这些都发生在路由的每个步骤中,因此是一个很好的改进。
一些最常用的EIP已经过优化。 例如
允许您使用其他MEP将消息发送到端点(但是很少使用)。 现在,EIP会检测到此情况,并避免创建用于恢复MEP的回调对象。 管道EIP(例如,当您执行->到-> to时)也对使用索引计数器而不是java.util.Iterator进行了一些改进,因为后者分配了一个额外的对象
骆驼还有一个秒表,它使用java.util.Date来存储时间。 已对其进行优化以使用长值。
另一个改进是事件通知。 现在,我们会预先计算其是否正在使用中,并避免在与路由消息相关的事件中一起调用它们。 顺便说一句,在Camel 3.0中,事件通知程序被重构为使用Java 8 Supplier的API和许多精美的API,但是所有这些都会产生大量开销。 在Camel 3.1中,我们已将通知程序恢复为与以前在Camel 2.x中一样,并进行了其他优化。
因此,让我以说……结束这个博客。 太棒了 Camel 3.1将使用更少的内存,通过不调用太多方法来执行得更快(请记住,我们可能不得不移动一些需要调用的代码,但是以不同的方式执行此操作,以避免调用太多的方法)。
在涉及的源代码方面,最大的变化之一是从使用ServiceSupport(Camel中很多东西的基类)中的基于实例的记录器切换为使用静态记录器实例。 这意味着将创建更少的Logger对象,这也是一种更好的做法。
更好的性能
其他改进是,我们将骆驼作为交换属性保留的某些内部状态直接移到了Exchange的字段中。 这样可以避免在属性映射中存储键/值,但是我们可以使用诸如boolean,int等原语。由于通过getter获取布尔值要比通过键在Map中查找值更快,因此这样做的效果也更好。
实际上,在Camel 3.1中,然后在常规路由期间,Camel不会从交换属性中查找任何此类状态,这意味着没有方法调用。 仍然有一些状态存储为交换属性(将来可能会改善其中一些状态,但是这些状态中的大多数仅很少使用)。 我们优化的是在路由过程中始终检查和使用的状态。
交换getProperty(5分钟采样)
骆驼2.25 572598 getPropety(字符串)
骆驼2.25 161502 getPropety(字符串,对象)
骆驼2.25 161502 getPropety(字符串,对象,类)
骆驼2.25 141962 getPropeties()
骆驼3.0 574944 getProperty(字符串)
Camel 3.0 167904 getPropety(字符串,对象)
骆驼3.0 167904 getPropety(字符串,对象,类)
骆驼3.0 91584 getPropeties()
骆驼3.1 0 getProperty(String)
骆驼3.1 0 getPropety(String,Object)
骆驼3.1 0 getPropety(String,Object,Class)
骆驼3.1 0 getPropeties()
如您所见,Camel 2.25和3.0查找了很多这种状态。 在Camel 3.1中,我们对此进行了极大的优化,并且根本没有查找-就像说状态以原始类型存储在Exchange上一样,JDK可以内联并执行得非常快。
以下屏幕截图显示了骆驼2.25和3.1。 (3.1的屏幕截图与昨天相比略有过时,此后我们对Camel进行了优化)。 请参见下面的屏幕截图:
![](https://i-blog.csdnimg.cn/blog_migrate/51e175ee5db96e082c4cd64edd2a4052.png)
好的,还有许多其他较小的优化,在撰写此博客时,我目前正在研究一个优化。 好的,让我结束这个博客,并保存第3部分的详细信息。