如何获得最小的ocamlopt编译本机二进制文件?

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而言是相同的)。

ivg*_*ivg 5

您的问题非常广泛,我不确定它是否适合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或实现自己的运行时。对于一般情况,请使用stripupx。(例如,通过这种方式,upx --best我可以将示例的二进制大小减少到50K,而无需任何其他技巧)。如果性能无关紧要,则可以使用字节码,该字节码通常小于机器码。因此,您只需支付一次(运行时约200k),每个程序只需支付几个字节(例如,helloworld为200字节)。

另外,不要创建许多小的二进制文件,而要创建一个二进制文件。在您的特定示例中,helloworld编译单元的大小在字节码中为200字节,在机器码中为700字节。其余的50k是启动线束,应仅包含一次。此外,由于OCaml在运行时支持动态链接,因此您可以轻松创建一个加载器,该加载器将在需要时加载模块。在这种情况下,二进制文件将变得非常小(数百个字节)。

好像ocamlopt所谓的“本地编译”是关于将ocamlrun和程序的非本地字节码打包在一个文件中并使其可执行。不完全是我所期望的。我显然错过了一些重要的观点。但是,如果是这样,我会对为什么它不是我期望的感兴趣。

不,这是完全错误的。本机编译是指将程序编译为机器代码(无论是x86,ARM还是其他类型)时。运行时用C编写,编译为机器代码,并且也被链接。OCaml标准库主要用OCaml编写,也编译成机器代码,并且也链接到二进制文件中(仅使用所使用的那些模块,如果将程序拆分为模块(编译单元),则OCaml静态链接非常有效。相当好)。

关于OCAMLRUNPARAM环境变量,只是一个参数化了运行时行为的环境变量,主要是垃圾收集器的参数。

  • 谢谢,这样更好。我并不是不同意,我只是想提一下,对于 Java/CL,仅根据语言规范来判断在实践中是不够的,因为有多种实现(例如 ECL 尽可能接近 C,带有 libecl.so和静态链接)。顺便说一句,答案很好。 (3认同)
  • 谢谢,这确实是一个非常糟糕的措辞:)我只是将句子分成几个较小的句子,希望现在更清楚了。 (2认同)