1. 程式人生 > >erlang程序優化點的總結(轉)

erlang程序優化點的總結(轉)

數據庫 機器 ria 嚴重 多線程 分別是 簡單 構造 代碼實現

註意,這裏只是給出一個總結,具體性能需要根據實際環境和需要來確定

霸爺指出,新的erlang虛擬機有很多調優啟動參數,今後現在這個方面深挖一下。

1. 進程標誌設置:

消息和binary內存:erlang:process_flag(min_bin_vheap_size, 1024*1024),減少大量消息到達或處理過程中產生大量binary時的gc次數

堆內存:erlang:process_flag(min_heap_size, 1024*1024),減少處理過程中產生大量term,尤其是list時的gc次數

進程優先級:erlang:process_flag(priority, high),防止特殊進程被其它常見進程強制執行reductions

進程調度器綁定:erlang:process_flag(scheduler, 1),當進程使用了port時,還需要port綁定支持,防止進程在不同調度器間遷移引起性能損失,如cache、跨numa node拷貝等,當進程使用了port時,主要是套接字,若進程與port不在一個scheduler上,可能會引發嚴重的epoll fd鎖競爭及跨numa node拷貝,導致性能嚴重下降

2. 虛擬機參數:

+S X:X :啟用調度器數量,多個調度器使用多線程,有大量鎖爭用

-smp disable :取消smp,僅使用單線程,16個-smp_disabled虛擬機性能高於+S 16:16

+sbt db :將scheduler綁定到具體的cpu核心上,再配合erlang進程和port綁定,可以顯著提升性能,但是如果綁定錯誤,反而會有反效果

3. 消息隊列:

消息隊列長度對性能的影響主要體現在以下兩個方面:進程binary堆的gc和進程內消息匹配,前者可以通過放大堆內存來減少gc影響,後者需要謹慎處理。

若進程在處理消息時是通過消息匹配方式取得消息,同時又允許其它進程無限制投遞消息到本進程,此時會引發災難,匹配方式取得消息會引發遍歷進程消息隊列,如果此時仍然有其它進程投遞消息,會導致進程消息隊列暴漲,遍歷過程也將增大代價,引發惡性循環。已知模式有:在gen_server中使用file:write(raw模式)或gen_tcp:send等,這些操作都是erlang虛擬機內部通過port driver實現的,均有內部receive匹配接收,對於這些操作,最好的辦法是將其改寫為nif,直接走進程堆進行操作,次之為將file:write或gen_tcp:send改寫為兩階段,第一階段為port_command,第二階段由gen_server接收返回結果,這種異步化可能有些正確性問題,對於gen_tcp:send影響不大,因為網絡請求本身要麽同步化要麽異步化,都需要內部的確認機制;對於file:write影響較大,file:write的錯誤通常為目錄不存在或磁盤空間不足,確保這兩個錯誤不造成影響即可,同時如果進程的其它部分需要使用file的其它操作,必須首先清空之前file:write產生的所有file的port消息,否則有可能產生消息序列紊亂的問題。

對於套接字的接口調用,可以參考rabbitmq的兩階段套接字發送方法,而對於文件接口調用,可以參考riak的bitcask引擎將文件讀寫封裝為nif的方法

4. 內存及ets表:

ets表可以用於進程間交換大數據,或充當緩存,以及復雜匹配代理等,其性能頗高,並發讀寫可達千萬級qps,並有兩個並發選項,在建立表時設置,分別是{write_concurrency, true} | {read_concurrency, true},以允許ets的並發讀寫

使用ets表可以繞過進程消息機制,從而在一定程度上提高性能,並將編程模式從面向消息模式變為面向共享內存模式

5. CPU密集型操作:

erlang執行流程的問題:

1. 其指令都是由其虛擬機執行的,一條指令可能需要cpu執行3-4條指令,一些大規模的匹配或遍歷操作會嚴重影響性能;

2. 其bif調用執行過程類似於操作系統的系統調用,需要對傳入參數進行轉換,在大量小操作時損失性能較為嚴重

3. 其port driver流程較為繁冗復雜,需要經歷大量的回調等,一般的小功能操作,不要通過port driver實現

建議:

字符串匹配不要通過list進行,最好通過binary;單字節匹配,尤其是語法解析,如xmerl、mochijson2、lexx等,盡管使用binary,但是它們是一個字節一個字節匹配的,性能會退化到list的水平,應該盡量將其nif化;

對於一些小操作,反而應該去bif化、去nif化、去port driver化,因為進入erlang內部函數的執行代價也不小;

已知的性能瓶頸:re、xmerl、mochijson2、lexx、erlang:now、calendar:local_time_to_universal_time_dst等

6. 數據結構:

減少遍歷,盡量使用API提供的操作

由於各種類型的變量實際可以當做c的指針,因此erlang語言級的操作並不會有太大代價

lists:reverse為c代碼實現,性能較高,依賴於該接口實現的lists API性能都不差,避免list遍歷,[||]和foreach性能是foldl的2倍,不在非必要的時候遍歷list

dict:find為微秒級操作,內部通過動態hash實現,數據結構先有若幹槽位,後根據數據規模變大而逐步增加槽位,fold遍歷性能低下

gb_trees:lookup為微秒級操作,內部通過一個大的元組實現,iterator+next遍歷性能低下,比list的foldl還要低2個數量級

其它常用結構:queue,set,graph等

7. 計時器:

erlang的計時器timer是通過一個唯一的timer進程實現的,該進程是一個gen_server,用戶通過timer:send_after和timer:apply_after在指定時間間隔後收到指定消息或執行某個函數,每個用戶的計時器都是一條記錄,保存在timer的ets表timer_tab中,timer的時序驅動通過gen_server的超時機制實現。若同時使用timer的用戶過多,則tiemr將響應不過來,成為瓶頸。

更好的方法是使用erlang的原生計時器erlang:send_after和erlang:start_timer,它們把計時器附著在進程自己身上。

8. 尾調用和尾遞歸:

尾調用和尾遞歸是erlang函數式語言最強大的優化,盡量保持函數尾部有尾調用或尾遞歸

9. 文件預讀,批量寫,緩存:

這些方式都是局部性的體現:

預讀:讀空間局部性,文件提供了read_ahead選項

批量寫:寫空間局部性

對於文件寫或套接字發送,存在若幹級別的批量寫:

1. erlang進程級:進程內部通過list緩存數據

2. erlang虛擬機:不管是efile還是inet的driver,都提供了批量寫的選項delayed_write|delay_send,

它們對大量的異步寫性能提升很有效

3. 操作系統級:操作系統內部有文件寫緩沖及套接字寫緩沖

4. 硬件級:cache等

緩存:讀寫時間局部性,讀寫空間局部性,主要通過操作系統系統,erlang虛擬機沒有內部的緩存

10.套接字標誌設置:

延遲發送:{delay_send, true},聚合若幹小消息為一個大消息,性能提升顯著

發送高低水位:{high_watermark, 128 * 1024} | {low_watermark, 64 * 1024},輔助delay_send使用,delay_send的聚合緩沖區大小為high_watermark,數據緩存到high_watermark後,將阻塞port_command,使用send發送數據,直到緩沖區大小降低到low_watermark後,解除阻塞,通常這些值越大越好,但erlang虛擬機允許設置的最大值不超過128K

發送緩沖大小:{sndbuf, 16 * 1024},操作系統對套接字的發送緩沖大小,在延遲發送時有效,越大越好,但有極值

接收緩沖大小:{recbuf, 16 * 1024},操作系統對套接字的接收緩沖大小

11. 序列化/反序列化:

通常情況下,為了簡化實現,一般將erlang的term序列化為binary,傳遞到目的地後,在將binary反序列化為term,這通常涉及到兩個操作:

term_to_binary及binary_to_term,這兩個操作性能消耗極為嚴重,應至多只做一次,減少甚至消除它們是最正確的,例如直接構造binary進行跨虛擬機數據交換;

但對比與其它的序列化和反序列化方式,如利用protobuf等,term_to_binary和binary_to_term的性能是高於這些方式的,畢竟是erlang原生格式,對於力求簡單的應用,其序列化和反序列化方式推薦term_to_binary和binary_to_term

12. 並發化

在一些場景下,如web請求、數據庫請求、分布式文件系統等,單個接入接口已經不能滿足性能需求,需要有多個接入接口,多個數據通道,等等,這要求所有請求處理過程必須是無狀態的,或者狀態更改同步進入一個公共存儲,而公共存儲也必須是支持並發處理的,如並發數據庫、類hdfs、類dynamo存儲等,若一致性要求較高,最好選用並發數據庫,如mysql等,若在此基礎上還要求高可用,最好選擇同步多結點存儲,

mnesia、zk都是這方面的典型;若不需要較高的一致性,類hdfs、類dynamo這類no sql存儲即可滿足

13. hipe

將erlang匯編翻譯成機器碼,減少一條erlang指令對應的cpu指令數

轉自:http://wqtn22.iteye.com/blog/1820587

erlang程序優化點的總結(轉)