热门标签 | HotTags
当前位置:  开发笔记 > 编程语言 > 正文

[Erlang0029]ErlangInline编译

内联函数建议编译器将制定的函数体插入并取代每一处调用该函数的地方,从而节省了每一次函数调用带来的时间开支,选择内联函数时,必须要在程序占用空间和程序执行效率之间进行权衡,因为过多的

  内联函数建议编译器将制定的函数体插入并取代每一处调用该函数的地方,从而节省了每一次函数调用带来的时间开支,选择内联函数时,必须要在程序占用空间和程序执行效率之间进行权衡,因为过多的对较为复杂的函数进行内联扩展将带来很大的存储资源开支.另外注意对于递归函数的内联扩展可能带来部分编译器的无穷编译.内联扩展是一种特别的用于消除调用函数时所造成的固有的时间消耗方法。一般用于能够快速执行的函数,因为在这种情况下函数调用的时间消耗显得更为突出。
                                                                                                                                                                            --维基百科内联函数摘要

从维基百科的描述中可以看到内联函数解决的问题是:函数的调用时间比函数执行时间相当的时候,通过空间换时间,获得执行效率.实现角度Inlining是通过代码复制的方式节省进栈出栈的开销.Erlang的编译器可以将Erlang模块中的函数进行内联编译,内联(inlining)的含义是把一个方法的调用替换成函数体并把参数替换成实际值.
Erlang内联不是默认值;必须明确指定compile选项( 形式: {inline,[{Name,Arity},...]} ) 或者在源代码使用-compile.

%%Example of explicit inlining:

-compile({inline,[pi/0]}).

pi() -> 3.1416.


%% Example of implicit inlining:

-compile(inline).


%% Aggressive inlining - will increase code size.

-compile(inline).

-compile({inline_size,100}).

如果一个函数被编译成inline,原始的函数还是会被保留,我们可以直接在erlang shell中调用这个方法.

-module(test).

-compile(export_all).

-compile({inline,[server_id/0]}).


server_id() ->
2396.

内联编译不一定提高运行时的效率.例如内联可能增加Beam的栈消耗,对于递归函数调用这显然是有损性能的.{inline_size,Size}就是用来控制方法在多大程度上可以inline.Size默认值是24,这样inline代码与没有做inline的代码size相当,只有相当小的方法会被做inline.

那这个Size到底是指什么的大小呢?是代码函数?是代码个数?还是别的什么?我在erlangqa.org提了这个问题,得到了litaocheng的解答:

http://www.erlangqa.com/?qa=100/inline_size-size-%E4%B8%AD%E7%9A%84size%E6%98%AF%E6%8C%87%E4%BB%80%E4%B9%88

请参考otp_src/compiler/src/cerl_inline.erl的weight/1函数。
相应的Erlang表达式都有不同的权重。inline_size指的是函数汇编后的权重值。
可以通过 erlc +\'S\' your.erl来得到汇编文件:your.S。

同时参考cerl_inline.erl文件中:当inline_size为30时,90%的情况下可以得到最大加速。inline_size为100-150时,98%的情况下可以最大优化。如果指定更大值,会使代码尺寸变大,性能反而受到影响。

按图索骥找到cerl_inline.erl weight/1的代码:
weight Code
也就是说,不同的表达式有不同的权重值,Size既不是代码函数也不是函数个数,而是依赖于该权重值.这里我不再继续跟进了,cerl_inline.erl的注释提供了详细的信息:
Normal execution times for inlining are between 0.1 and 0.3 seconds (on the author's current equipment). The default effort limit of 150 is high enough that most normal programs never hit the limit even once, and for difficult programs, it generally keeps the execution times below 2-5 seconds. Using an effort counter of 1000 will thus have no further effect on most programs, but some programs may take as much as 10 seconds or more. Effort counts larger than 2500 have never been observed even on very ill-conditioned programs.

Size limits between 6 and 18 tend to actually shrink the code, because of the simplifications made possible by inlining. A limit of 16 seems to be optimal for this purpose, often shrinking the executable code by up to 10%. Size limits between 18 and 30 generally give the same code size as if no inlining was done (i.e., code duplication balances out the simplifications at these levels). A size limit between 1 and 5 tends to inline small functions and propagate constants, but does not cause much simplifications do be done, so the net effect will be a slight increase in code size. For size limits above 30, the executable code size tends to increase with about 10% per 100 units, with some variations depending on the sizes of functions in the source code.

Typically, about 90% of the maximum speedup achievable is already reached using a size limit of 30, and 98% is reached at limits around 100-150; there is rarely any point in letting the code size increase by more than 10-15%. If too large functions are inlined, cache effects will slow the program down.
感兴趣的可以找到原始论文看下,论文地址: "Fast and Effective Procedure Inlining", International Static Analysis Symposium 1997  http://citeseer.ist.psu.edu/viewdoc/summary?doi=10.1.1.54.2438 
算法不跟进,但是实验要做的,就按照litaocheng建议的方法,我做了一个这样的demo,写一个简单的方法,这个方法在另一个方法里面被多次调用(代码如下).我是在windows环境中使用,在shell中使用命令c(test,['S']).得到assembler code文件.

-module(test).

-export([get_name/0, show/1]).


get_name() ->
"This is Test Module".


show(A) ->

A=get_name(),

A=get_name(),

A=get_name(),

A=get_name(),

A=get_name(),

A=get_name(),
io:format("This is ~p ~n",[A]).

生成的assembler code文件如下:

{module, test}. %% version = 0


{exports, [{get_name,0},{module_info,0},{module_info,1},{show,1}]}.


{attributes, []}.


{labels, 15}.



{function, get_name, 0, 2}.

{label,1}.

{line,[{location,"test.erl",22}]}.

{func_info,{atom,test},{atom,get_name},0}.

{label,2}.

{move,{literal,"This is Test Module"},{x,0}}.

return.



{function, show, 1, 4}.

{label,3}.

{line,[{location,"test.erl",25}]}.

{func_info,{atom,test},{atom,show},1}.

{label,4}.

{allocate,1,1}.

{move,{x,0},{y,0}}.

{line,[{location,"test.erl",26}]}.

{call,0,{f,2}}.

{test,is_eq_exact,{f,5},[{x,0},{y,0}]}.

{line,[{location,"test.erl",27}]}.

{call,0,{f,2}}.

{test,is_eq_exact,{f,6},[{x,0},{y,0}]}.

{line,[{location,"test.erl",28}]}.

{call,0,{f,2}}.

{test,is_eq_exact,{f,7},[{x,0},{y,0}]}.

{line,[{location,"test.erl",29}]}.

{call,0,{f,2}}.

{test,is_eq_exact,{f,8},[{x,0},{y,0}]}.

{line,[{location,"test.erl",30}]}.

{call,0,{f,2}}.

{test,is_eq_exact,{f,9},[{x,0},{y,0}]}.

{line,[{location,"test.erl",31}]}.

{call,0,{f,2}}.

{test,is_eq_exact,{f,10},[{x,0},{y,0}]}.

{test_heap,2,0}.

{put_list,{y,0},nil,{x,1}}.

{move,{literal,"This is ~p ~n"},{x,0}}.

{line,[{location,"test.erl",32}]}.

{call_ext_last,2,{extfunc,io,format,2},1}.

{label,5}.

{line,[{location,"test.erl",26}]}.

{badmatch,{x,0}}.

{label,6}.

{line,[{location,"test.erl",27}]}.

{badmatch,{x,0}}.

{label,7}.

{line,[{location,"test.erl",28}]}.

{badmatch,{x,0}}.

{label,8}.

{line,[{location,"test.erl",29}]}.

{badmatch,{x,0}}.

{label,9}.

{line,[{location,"test.erl",30}]}.

{badmatch,{x,0}}.

{label,10}.

{line,[{location,"test.erl",31}]}.

{badmatch,{x,0}}.



{function, module_info, 0, 12}.

{label,11}.

{line,[]}.

{func_info,{atom,test},{atom,module_info},0}.

{label,12}.

{move,{atom,test},{x,0}}.

{line,[]}.

{call_ext_only,1,{extfunc,erlang,get_module_info,1}}.



{function, module_info, 1, 14}.

{label,13}.

{line,[]}.

{func_info,{atom,test},{atom,module_info},1}.

{label,14}.

{move,{x,0},{x,1}}.

{move,{atom,test},{x,0}}.

{line,[]}.

{call_ext_only,2,{extfunc,erlang,get_module_info,2}}.

添加了inline选项之后的结果,这个demo比较变态生成的代码效果也比较明显:

{module, test}. %% version = 0


{exports, [{get_name,0},{module_info,0},{module_info,1},{show,1}]}.


{attributes, []}.


{labels, 10}.



{function, get_name, 0, 2}.

{label,1}.

{line,[{location,"test.erl",23}]}.

{func_info,{atom,test},{atom,get_name},0}.

{label,2}.

{move,{literal,"This is Test Module"},{x,0}}.

return.



{function, show, 1, 4}.

{label,3}.

{line,[{location,"test.erl",26}]}.

{func_info,{atom,test},{atom,show},1}.

{label,4}.

{test,is_eq_exact,{f,5},[{literal,"This is Test Module"},{x,0}]}.

{test_heap,2,1}.

{put_list,{x,0},nil,{x,1}}.

{move,{literal,"This is ~p ~n"},{x,0}}.

{line,[{location,"test.erl",33}]}.

{call_ext_only,2,{extfunc,io,format,2}}.

{label,5}.

{line,[{location,"test.erl",27}]}.

{badmatch,{literal,"This is Test Module"}}.



{function, module_info, 0, 7}.

{label,6}.

{line,[]}.

{func_info,{atom,test},{atom,module_info},0}.

{label,7}.

{move,{atom,test},{x,0}}.

{line,[]}.

{call_ext_only,1,{extfunc,erlang,get_module_info,1}}.



{function, module_info, 1, 9}.

{label,8}.

{line,[]}.

{func_info,{atom,test},{atom,module_info},1}.

{label,9}.

{move,{x,0},{x,1}}.

{move,{atom,test},{x,0}}.

{line,[]}.

{call_ext_only,2,{extfunc,erlang,get_module_info,2}}.

官方文档: http://www.erlang.org/doc/man/compile.html
Erlang Assembly Code处于 not documented的状态,下面有两篇相关的文章:
Howto dump Core Erlang and assembly  http://untroubled.be/docs/erlang/howto_dump_core_erlang_and_assembly.html
How to modify Erlang assembly? Are any resources available?  http://stackoverflow.com/questions/7935054/how-to-modify-erlang-assembly-are-any-resources-available
 



推荐阅读
  • CentOS 7.6环境下Prometheus与Grafana的集成部署指南
    本文旨在提供一套详细的步骤,指导读者如何在CentOS 7.6操作系统上成功安装和配置Prometheus 2.17.1及Grafana 6.7.2-1,实现高效的数据监控与可视化。 ... [详细]
  • 本文将指导如何向ReactJS计算器应用添加必要的功能,使其能够响应用户操作并正确计算数学表达式。 ... [详细]
  • 本文详细介绍了如何在现有的Android Studio项目中集成JNI(Java Native Interface),包括下载必要的NDK和构建工具,配置CMakeLists.txt文件,以及编写和调用JNI函数的具体步骤。 ... [详细]
  • 本文探讨了如何利用HTML5和JavaScript在浏览器中进行本地文件的读取和写入操作,并介绍了获取本地文件路径的方法。HTML5提供了一系列API,使得这些操作变得更加简便和安全。 ... [详细]
  • 本文详细介绍了如何解压并安装MySQL集群压缩包,创建用户和组,初始化数据库,配置环境变量,并启动相关服务。此外,还提供了详细的命令行操作步骤和常见问题的解决方案。 ... [详细]
  • 本文将详细探讨 Java 中提供的不可变集合(如 `Collections.unmodifiableXXX`)和同步集合(如 `Collections.synchronizedXXX`)的实现原理及使用方法,帮助开发者更好地理解和应用这些工具。 ... [详细]
  • 本文详细介绍了虚拟专用网(Virtual Private Network, VPN)的概念及其通过公共网络(如互联网)构建临时且安全连接的技术特点。文章探讨了不同类型的隧道协议,包括第二层和第三层隧道协议,并提供了针对IPSec、GRE以及MPLS VPN的具体配置指导。 ... [详细]
  • 在寻找轻量级Ruby Web框架的过程中,您可能会遇到Sinatra和Ramaze。两者都以简洁、轻便著称,但它们之间存在一些关键区别。本文将探讨这些差异,并提供详细的分析,帮助您做出最佳选择。 ... [详细]
  • 2017-2018年度《网络编程与安全》第五次实验报告
    本报告详细记录了2017-2018学年《网络编程与安全》课程第五次实验的具体内容、实验过程、遇到的问题及解决方案。 ... [详细]
  • 本文探讨了如何通过一系列技术手段提升Spring Boot项目的并发处理能力,解决生产环境中因慢请求导致的系统性能下降问题。 ... [详细]
  • HTTPS与TLS/SSL协议详解:握手及记录协议
    HTTPS,即HTTP over TLS/SSL,通过在HTTP通信层引入安全协议,确保数据传输的安全性。本文将深入探讨TLS/SSL协议的基本概念、HTTPS的必要性,以及TLS握手和记录协议的工作原理。 ... [详细]
  • Spring Cloud Config 使用 Vault 作为配置存储
    本文探讨了如何在Spring Cloud Config中集成HashiCorp Vault作为配置存储解决方案,基于Spring Cloud Hoxton.RELEASE及Spring Boot 2.2.1.RELEASE版本。文章还提供了详细的配置示例和实践建议。 ... [详细]
  • 本文详细介绍了 Kubernetes 集群管理工具 kubectl 的基本使用方法,涵盖了一系列常用的命令及其应用场景,旨在帮助初学者快速掌握 kubectl 的基本操作。 ... [详细]
  • 本文详细介绍了如何搭建和配置ZooKeeper集群,包括环境变量设置、配置文件调整、主机映射关系配置及启动验证等关键步骤。 ... [详细]
  • docker镜像重启_docker怎么启动镜像dock ... [详细]
author-avatar
mobiledu2402852357
这个家伙很懒,什么也没留下!
PHP1.CN | 中国最专业的PHP中文社区 | DevBox开发工具箱 | json解析格式化 |PHP资讯 | PHP教程 | 数据库技术 | 服务器技术 | 前端开发技术 | PHP框架 | 开发工具 | 在线工具
Copyright © 1998 - 2020 PHP1.CN. All Rights Reserved | 京公网安备 11010802041100号 | 京ICP备19059560号-4 | PHP1.CN 第一PHP社区 版权所有