vaa*_*aab 2 size executable ocaml compilation minimum
看到一个简单的程序,例如:
print_string "Hello world !\n";
Run Code Online (Sandbox Code Playgroud)
当通过ocamlopt一些非常激进的选项(使用musl)静态编译为本机代码时,在我的系统上仍约为190KB。
$ ocamlopt.opt -compact -verbose -o helloworld \
-ccopt -static \
-ccopt -s \
-ccopt -ffunction-sections \
-ccopt -fdata-sections \
-ccopt -Wl \
-ccopt -gc-sections \
-ccopt -fno-stack-protector \
helloworld.ml && { ./helloworld ; du -h helloworld; }
+ as -o 'helloworld.o' '/tmp/camlasm759655.s'
+ as -o '/tmp/camlstartupfc4271.o' '/tmp/camlstartup5a7610.s'
+ musl-gcc -Os -o 'helloworld' '-L/home/vaab/.opam/4.02.3+musl+static/lib/ocaml' -static -s -ffunction-sections -fdata-sections -Wl -gc-sections -fno-stack-protector '/tmp/camlstartupfc4271.o' '/home/vaab/.opam/4.02.3+musl+static/lib/ocaml/std_exit.o' 'helloworld.o' '/home/vaab/.opam/4.02.3+musl+static/lib/ocaml/stdlib.a' '/home/vaab/.opam/4.02.3+musl+static/lib/ocaml/libasmrun.a' -static -lm
Hello world !
196K helloworld
Run Code Online (Sandbox Code Playgroud)
如何从ocamlopt获取最小的二进制文件?
190KB对于像今天这样的约束(iot,android,alpine VM ...)这样的简单程序而言,的大小太大了,与简单的C程序(约6KB,或者直接编码ASM并进行调整以获得一个工作二进制文件,可能约为150B)。我很天真地以为我可以简单地C放弃编写简单的静态程序来完成一些琐碎的事情,并且在编译之后,我会得到一些简单的汇编代码,而这些代码在等效的C程序中还没有那么大。那可能吗 ?
我认为我的理解:
当删除gcc -s以便对二进制文件中剩余的内容有一些提示时,我会注意到很多ocaml符号,并且我还读到有些环境变量ocamlrun 甚至应该以这种形式进行解释。好像ocamlopt所谓的“本地编译”是关于将程序ocamlrun的非本地打包和打包bytecode到一个文件中并使之可执行。不完全是我所期望的。我显然错过了一些重要的观点。但是,如果是这样,我会对为什么它不是我期望的感兴趣。
其他语言编译为具有相同问题的本机代码:给一些天真的用户(如我自己)留下大致相同的问题:
我也使用Haskell进行了测试,并且没有进行任何调整,所有语言的编译器都在为“ hello world”程序制作700KB以上的二进制文件(在进行调整之前,对于Ocaml而言是相同的)。
您的问题非常广泛,我不确定它是否适合Stackoverflow的格式。它值得进行彻底的讨论。
190KB的大小对于像今天这样的约束(iot,android,alpine VM ...)这样的简单程序来说实在太大了,并且与简单的C程序(大约6KB左右,或者直接编码ASM并进行调整以获取内容)相比差强人意一个有效的二进制文件,可能约为150B)
首先,这不是一个公平的比较。如今,已编译的C二进制文件已远远不是一个独立的二进制文件。应该将其视为框架中的插件。因此,如果您要计算给定二进制文件实际使用的字节数,我们将计算加载程序,shell,libc库以及整个linux或Windows内核的大小-总体上构成了应用程序的运行时。
与Java或Common Lisp不同,OCaml对通用C运行时非常友好,并尝试重用其大多数功能。但是OCaml仍然具有自己的运行时,其中最大(也是最重要的部分)是垃圾收集器。运行时间不是很大(大约30 KLOC),但仍然会增加重量。而且由于OCaml使用静态链接,所以每个OCaml程序都会有一个副本。
因此,C二进制文件具有显着的优势,因为它们通常在已经可以使用C运行时的系统中运行(因此通常将其从等式中排除)。但是,在有些系统中根本没有C运行时,仅存在OCaml运行时,例如,请参见Mirage。在这样的系统中,OCaml二进制文件更为有利。另一个示例是OCaPic项目,在该项目中(对编译器和运行时进行了调整),他们设法使OCaml运行时和程序适合64Kb Flash(阅读该论文对二进制大小非常有见地)。
如何从ocamlopt获取最小的二进制文件?
如果确实需要最小化大小,请使用Mirage Unikernels或实现自己的运行时。对于一般情况,请使用strip和upx。(例如,通过这种方式,upx --best我可以将示例的二进制大小减少到50K,而无需任何其他技巧)。如果性能无关紧要,则可以使用字节码,该字节码通常小于机器码。因此,您只需支付一次(运行时约200k),每个程序只需支付几个字节(例如,helloworld为200字节)。
另外,不要创建许多小的二进制文件,而要创建一个二进制文件。在您的特定示例中,helloworld编译单元的大小在字节码中为200字节,在机器码中为700字节。其余的50k是启动线束,应仅包含一次。此外,由于OCaml在运行时支持动态链接,因此您可以轻松创建一个加载器,该加载器将在需要时加载模块。在这种情况下,二进制文件将变得非常小(数百个字节)。
好像ocamlopt所谓的“本地编译”是关于将ocamlrun和程序的非本地字节码打包在一个文件中并使其可执行。不完全是我所期望的。我显然错过了一些重要的观点。但是,如果是这样,我会对为什么它不是我期望的感兴趣。
不,这是完全错误的。本机编译是指将程序编译为机器代码(无论是x86,ARM还是其他类型)时。运行时用C编写,编译为机器代码,并且也被链接。OCaml标准库主要用OCaml编写,也编译成机器代码,并且也链接到二进制文件中(仅使用所使用的那些模块,如果将程序拆分为模块(编译单元),则OCaml静态链接非常有效。相当好)。
关于OCAMLRUNPARAM环境变量,只是一个参数化了运行时行为的环境变量,主要是垃圾收集器的参数。