Erlang list的++操作和append函数的底层实现

原文:http://blog.csdn.net/zhongruixian/article/details/9417201

当提到Erlang中list的++操作符时,我们常会想到它的性能问题。

有些人知道++操作比较耗时,就改用函数append来代替。

到底++操作和append函数之间有什么区别?


我们来查看一下它们在Erlang源码及C源码中的实现。


在$ERL_TOP/lib/stdlib/src/lists.erl可以找到如下代码:
[plain]  view plai copy
  1. %% append(X, Y) appends lists X and Y  
  2.   
  3.   
  4. -spec append(List1, List2) -> List3 when  
  5.       List1 :: [T],  
  6.       List2 :: [T],  
  7.       List3 :: [T],  
  8.       T :: term().  
  9.    
  10. append(L1, L2) -> L1 ++ L2. %% 使用了++操作  
  11.   
  12.   
  13. %% append(L) appends the list of lists L  
  14.   
  15.   
  16. -spec append(ListOfLists) -> List1 when  
  17.       ListOfLists :: [List],  
  18.       List :: [T],  
  19.       List1 :: [T],  
  20.       T :: term().  
  21.   
  22.   
  23. append([E]) -> E;  
  24. append([H|T]) -> H ++ append(T); %% 使用了++操作  
  25. append([]) -> [].  


在$ERL_TOP/erts/emulator/beam/erl_bif_lists.c中可以看到:

[cpp]  view plain copy
  1. /* 
  2.  * erlang:'++'/2 
  3.  */  
  4.   
  5.   
  6. Eterm  
  7. ebif_plusplus_2(BIF_ALIST_2)  
  8. {  
  9.     return append(BIF_P, BIF_ARG_1, BIF_ARG_2);  
  10. }  
  11.   
  12.   
  13. BIF_RETTYPE append_2(BIF_ALIST_2)  
  14. {  
  15.     return append(BIF_P, BIF_ARG_1, BIF_ARG_2);  
  16. }  

结合上面两个文件中的源码,我们可以得出这样的结论:


++操作,以及函数lists:append/1和lists:append/2,
在Erlang底层都是调用了C函数append(Process* p, Eterm A, Eterm B)来实现。



为什么Erlang的++操作耗性能?


要想揭秘++操作是如何耗性能的,我们只有彻底弄清楚它们的底层实现。

一起来看看$ERL_TOP/erts/emulator/beam/erl_bif_lists.c文件中append函数的C实现:

[cpp]  view plain copy
  1. #define CONS(hp, car, cdr) \  
  2.         (CAR(hp)=(car), CDR(hp)=(cdr), make_list(hp))  
  3.   
  4.   
  5. #define CAR(x)  ((x)[0])  
  6. #define CDR(x)  ((x)[1])  
  7.   
  8.   
  9. static BIF_RETTYPE append(Process* p, Eterm A, Eterm B)  
  10. {  
  11.     Eterm list;  
  12.     Eterm copy;  
  13.     Eterm last;  
  14.     size_t need;  
  15.     Eterm* hp;  
  16.     int i;  
  17.   
  18.   
  19.     if ((i = list_length(A)) < 0) {  
  20.         BIF_ERROR(p, BADARG);  
  21.     }  
  22.     if (i == 0) {  
  23.         // A的长底为0,直接返回B  
  24.         BIF_RET(B);  
  25.     } else if (is_nil(B)) {  
  26.         // B是一个空列表,直接返回A  
  27.         BIF_RET(A);  
  28.     }  
  29.   
  30.   
  31.     need = 2*i;  
  32.     // 为A的拷贝分配内存空间  
  33.     hp = HAlloc(p, need);  
  34.     list = A;  
  35.     // 首先拷贝A的第一个元素,并初始化copy和last变量  
  36.     // 为什么要两个变量指向同一值?  
  37.     // 因为copy指向副本头部,以下不再对copy的变量值做任何修改,最后用来做函数的返回值  
  38.     // last保持指向A副本的最后一个元素,最后要保存B的头指针值  
  39.     copy = last = CONS(hp, CAR(list_val(list)), make_list(hp+2));  
  40.     // 更新list变量值,让它指向A的第2个元素  
  41.     list = CDR(list_val(list));  
  42.     hp += 2;  
  43.     // 注意,上面已经拷贝了A的第一个元素,所以要先i--  
  44.     i--;  
  45.     // 从A的第2个元素开始,逐个拷贝  
  46.     while(i--) {  
  47.         // 取出list指向的元素,8字节,  
  48.         // CAR(listp)为指向当前元素值的指针值  
  49.         // CDR(listp)为指向下一个元素的指针值  
  50.         Eterm* listp = list_val(list);  
  51.         // 注意make_list(hp+2),直至拷贝完成,这个列表的next指针都是指向下一个内存空间,  
  52.         // last总是指向最新的元素,因为是从A的头部开始拷贝的,  
  53.         // 所以last最终也就是指向A副本的最后一个元素  
  54.         last = CONS(hp, CAR(listp), make_list(hp+2));  
  55.         // 让list移动到下一个元素  
  56.         list = CDR(listp);  
  57.         hp += 2;  
  58.     }  
  59.     // 上面的拷贝之后,副本尾部没有NIL值,在这里正好把这个位置的值更新为B的值,  
  60.     // 至此,两个list的合并完成  
  61.     CDR(list_val(last)) = B;  
  62.     // 返回副本头部的指针值  
  63.     BIF_RET(copy);  
  64. }  

以上分析中,我们知道了++操作的具体实现,但我还是有疑问。

为什么Erlang中++操作时要拷贝左边的list?

为什么要对A进行拷贝?

如果通过遍历找到A尾部的NIL值,并把它更新为B的指针值不就OK了?

这是因为Erlang的原则是变量一旦赋值就不可再变,如果直接对A进行了修改操作,将会有什么结果?

我认为这也是一个在写NIF时需要注意的问题。


Erlang ++操作符使用小结

  1. 尽量把长度较小的list放在左边。
  2. 如果左边的list长度很小,并不会造成很大的性能影响,这种情况下可以放心使用++操作符。
  3. lists:append/2函数最终也是调用++操作来完成,还不如直接使用++明了。
  4. 如果需要合并的list个数是未知的,可以使用函数lists:append/1来完成。
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值