Lukas Larsson,核心的VM开发者,最近很活跃,在Erlang内存体系上做了不少工作,包括recon项目的贡献。
他最近在erlang factory会议上分享了“Memory Allocators in the VM, Memory Management: Battle Storie”, 参见这里。
Erlang内存体系架构是个复杂的体系,一般的开发人员能难一眼就能搞清楚:
所以我们需要专家的经验把我们迅速带入门,他的PPT不再提供下载,我拉了一份,在 这里,原理、方法以及案例分析,很不错。
祝玩得开心!
Erlang公平调度是它的哲学(或者说坚持)之一,从第一个版本的beam代码的时间片分配和抢占开始,到最近版本的bif对公平性的坚持(比如R17版binary_to_term就大幅做了修改,代码复杂很多,执行效率也有下降,但是在碰到大的binary的情况下,通过Trap机制会让出执行权,排队后再回来断点续作), nif(加入扣除时间片的接口),这些努力保证了erlang系统是个公平的系统。
很多终端系统和业务会受益于这个哲学,如云计算。不管用户大小和业务的负载情况如何,系统性的公平性可以保证每个用户有机会被服务,对用户有很好的体验。 而公平性一定是要贯穿于整个系统里面,特别是有设计哲学方面来保证,每个模块和系统的设计者都能心有默契来遵守。如果有一个人打破了,整个系统其他的公平也就失去意义。这也是为什么公平性这么难做的核心原因。
我们在云业务里面会经常碰到一个系统在服务内部用户的时候效果很好,针对公众服务的时候就遇到各种抱怨。我举个简单的例子如RDS(mysql云),如果不考虑公平性,来一个SQL服务一个,而不管SQL的大小和复杂度,就会马上面临一个问题,强势的几个用户会把这个系统的资源全部占光,其他用户的SQL根本没有机会得到执行。这个问题当然可以通过mysql后端的资源隔离,如cgroup限制CPU,内存,IO,网络的消耗,还需要通过精确的每条SQL的消耗来在适当的时候让出控制权来达到目的。 这个链条上的公平非常难做,所以说大部分的系统都是蹩脚的,无法做到真正的公平,只是程度的差别而已。
虽然Erlang骨子里面就强调公平,而且在身体力行,但是实现上的细节决定了,下面几件事情上做不到公平,留下了坑。我们知道erlang代码核心的思路是根据规约数(函数执行一个,算一个规约),每个进程预先分配比如2000个时间片。时间片用完或者条件不满足(比如需要等消息)时候让出控制权,而且系统是消息驱动的。
Read more…
Erlang R17.0 发布的release note 里面花了挺多笔墨讲了内存carrier迁移的特性:
Support for migration of memory carriers between memory allocator instances has been introduced.
By default this feature is not enabled and do not effect the characteristics of the system. When enabled it has the following impact on the characteristics of the system:
* Reduced memory footprint when the memory load is unevenly distributed between scheduler specific allocator instances.
* Depending on the default allocaton strategy used on a specific allocator there might or might not be a slight performance loss.
* When enabled on the fix_alloc allocator, a different strategy for management of fix blocks will be used.
* The information returned from erlang:system_info({allocator, A}), and erlang:system_info({allocator_sizes, A}) will be slightly different when this feature has been enabled. An mbcs_pool tuple will be present giving information about abandoned carriers, and in the fix_alloc case no fix_types tuple will be present.
For more information, see the documentation of the +M acul command line argument.
那么什么是”migration of memory carriers between memory allocator instances“,解决什么问题呢?
官方的文档 erts/emulator/internal_doc/CarrierMigration.md, 见这里, 已经描述的非常清楚了。
我来简单的说下复述下:
Erlang的内存分配器为了提高性能,每个调度器一个都有自己的内存池,在申请/释放内存的可以避免大量的锁争用,提高了性能。但也带来内存浪费的问题,首先调度器默认使用策略是“full load or not”, 也就是说低ID的调度器如果没饱和的话,不会用下一个调度器。在高负载的情况下,更多的调度器被启用,该调度器上的内存被缓冲,留在池子里。当工作负载下去的的话,因为压力没到,高ID的调度器没机会被使用,也就是说这个时候,这个调度器上的内存就浪费掉了,从整个VM的角度来看,内存的碎片率就很高。Erlang的VM是以稳定性著名的,但是它也有Crash的时候,十有八九是因为内存爆了。我们在设计系统的时候,通常从数据量去反推需要的内存,但是如果有碎片或者浪费存在严重的话,我们就无法准确,就可能导致灾难。 为了解决这个问题,最直接的反应就是当每个调度器池子里面的内存使用率低于一定程度的时候,就把该块内存出让出来,让有需要的调度器能够利用起来。这就是内存carriers迁移要解决的核心问题。
这个特性在R16加入的默认不启用,R17默认启用了,也就是说+M acul 默认是“de”. 带来的影响有下面几个:
1. 内存申请的效率,现在的测试是先在自己的池子里面分配,不满足了就找cpool,再不满足才去找mseg或者sys分配器申请。效率和利用率是鱼和熊掌不可兼得。
2. 内存分配器的内存除了在池子里面还有在cpool里,统计内存的时候要小心。
3. 内存浪费率到什么时候被废弃。
Read more…
Recent Comments