热门标签 | 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
 



推荐阅读
  • 深入理解Java虚拟机的并发编程与性能优化
    本文主要介绍了Java内存模型与线程的相关概念,探讨了并发编程在服务端应用中的重要性。同时,介绍了Java语言和虚拟机提供的工具,帮助开发人员处理并发方面的问题,提高程序的并发能力和性能优化。文章指出,充分利用计算机处理器的能力和协调线程之间的并发操作是提高服务端程序性能的关键。 ... [详细]
  • 微软头条实习生分享深度学习自学指南
    本文介绍了一位微软头条实习生自学深度学习的经验分享,包括学习资源推荐、重要基础知识的学习要点等。作者强调了学好Python和数学基础的重要性,并提供了一些建议。 ... [详细]
  • 本文介绍了RPC框架Thrift的安装环境变量配置与第一个实例,讲解了RPC的概念以及如何解决跨语言、c++客户端、web服务端、远程调用等需求。Thrift开发方便上手快,性能和稳定性也不错,适合初学者学习和使用。 ... [详细]
  • 加密世界下一个主流叙事领域:L2、跨链桥、GameFi等
    本文介绍了加密世界下一个主流叙事的七个潜力领域,包括L2、跨链桥、GameFi等。L2作为以太坊的二层解决方案,在过去一年取得了巨大成功,跨链桥和互操作性是多链Web3中最重要的因素。去中心化的数据存储领域也具有巨大潜力,未来云存储市场有望达到1500亿美元。DAO和社交代币将成为购买和控制现实世界资产的重要方式,而GameFi作为数字资产在高收入游戏中的应用有望推动数字资产走向主流。衍生品市场也在不断发展壮大。 ... [详细]
  • Android源码深入理解JNI技术的概述和应用
    本文介绍了Android源码中的JNI技术,包括概述和应用。JNI是Java Native Interface的缩写,是一种技术,可以实现Java程序调用Native语言写的函数,以及Native程序调用Java层的函数。在Android平台上,JNI充当了连接Java世界和Native世界的桥梁。本文通过分析Android源码中的相关文件和位置,深入探讨了JNI技术在Android开发中的重要性和应用场景。 ... [详细]
  • Java学习笔记之面向对象编程(OOP)
    本文介绍了Java学习笔记中的面向对象编程(OOP)内容,包括OOP的三大特性(封装、继承、多态)和五大原则(单一职责原则、开放封闭原则、里式替换原则、依赖倒置原则)。通过学习OOP,可以提高代码复用性、拓展性和安全性。 ... [详细]
  • 3.223.28周学习总结中的贪心作业收获及困惑
    本文是对3.223.28周学习总结中的贪心作业进行总结,作者在解题过程中参考了他人的代码,但前提是要先理解题目并有解题思路。作者分享了自己在贪心作业中的收获,同时提到了一道让他困惑的题目,即input details部分引发的疑惑。 ... [详细]
  • 如何搭建Java开发环境并开发WinCE项目
    本文介绍了如何搭建Java开发环境并开发WinCE项目,包括搭建开发环境的步骤和获取SDK的几种方式。同时还解答了一些关于WinCE开发的常见问题。通过阅读本文,您将了解如何使用Java进行嵌入式开发,并能够顺利开发WinCE应用程序。 ... [详细]
  • Java中包装类的设计原因以及操作方法
    本文主要介绍了Java中设计包装类的原因以及操作方法。在Java中,除了对象类型,还有八大基本类型,为了将基本类型转换成对象,Java引入了包装类。文章通过介绍包装类的定义和实现,解答了为什么需要包装类的问题,并提供了简单易用的操作方法。通过本文的学习,读者可以更好地理解和应用Java中的包装类。 ... [详细]
  • 本文介绍了深入浅出Linux设备驱动编程的重要性,以及两种加载和删除Linux内核模块的方法。通过一个内核模块的例子,展示了模块的编译和加载过程,并讨论了模块对内核大小的控制。深入理解Linux设备驱动编程对于开发者来说非常重要。 ... [详细]
  • 海马s5近光灯能否直接更换为H7?
    本文主要介绍了海马s5车型的近光灯是否可以直接更换为H7灯泡,并提供了完整的教程下载地址。此外,还详细讲解了DSP功能函数中的数据拷贝、数据填充和浮点数转换为定点数的相关内容。 ... [详细]
  • 本文介绍了使用Spark实现低配版高斯朴素贝叶斯模型的原因和原理。随着数据量的增大,单机上运行高斯朴素贝叶斯模型会变得很慢,因此考虑使用Spark来加速运行。然而,Spark的MLlib并没有实现高斯朴素贝叶斯模型,因此需要自己动手实现。文章还介绍了朴素贝叶斯的原理和公式,并对具有多个特征和类别的模型进行了讨论。最后,作者总结了实现低配版高斯朴素贝叶斯模型的步骤。 ... [详细]
  • 全面介绍Windows内存管理机制及C++内存分配实例(四):内存映射文件
    本文旨在全面介绍Windows内存管理机制及C++内存分配实例中的内存映射文件。通过对内存映射文件的使用场合和与虚拟内存的区别进行解析,帮助读者更好地理解操作系统的内存管理机制。同时,本文还提供了相关章节的链接,方便读者深入学习Windows内存管理及C++内存分配实例的其他内容。 ... [详细]
  • DSP中cmd文件的命令文件组成及其作用
    本文介绍了DSP中cmd文件的命令文件的组成和作用,包括链接器配置文件的存放链接器配置信息、命令文件的组成、MEMORY和SECTIONS两个伪指令的使用、CMD分配ROM和RAM空间的目的以及MEMORY指定芯片的ROM和RAM大小和划分区间的方法。同时强调了根据不同芯片进行修改的必要性,以适应不同芯片的存储用户程序的需求。 ... [详细]
  • PHP反射API的功能和用途详解
    本文详细介绍了PHP反射API的功能和用途,包括动态获取信息和调用对象方法的功能,以及自动加载插件、生成文档、扩充PHP语言等用途。通过反射API,可以获取类的元数据,创建类的实例,调用方法,传递参数,动态调用类的静态方法等。PHP反射API是一种内建的OOP技术扩展,通过使用Reflection、ReflectionClass和ReflectionMethod等类,可以帮助我们分析其他类、接口、方法、属性和扩展。 ... [详细]
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社区 版权所有