如何获得最小的 ocamlopt 编译本机二进制文件?
how to get the smallest ocamlopt compiled native binary?
我很惊讶地看到即使是像这样的简单程序:
print_string "Hello world !\n";
当通过 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
如何从ocamlopt获取最小的二进制文件?
190KB
的大小对于像今天的约束(iot、android、alpine VM...)这样的简单程序来说太多了,并且与简单的 C 程序相比很糟糕(大约 6KB,或者直接编码 ASM 并调整一些东西以获得一个大约 150B 的工作二进制文件)。我天真地认为我可以简单地放弃 C
来编写简单的静态程序来做一些琐碎的事情,并且在编译之后我会得到一些简单的汇编代码,这些代码的大小与等效的 C 程序相比还不够大。那可能吗 ?
我觉得我明白了:
当删除 gcc 的 -s
以获得一些关于二进制文件中剩余内容的提示时,我可以注意到很多 ocaml
符号,而且我还读到了 [= 的一些环境变量18=] are meant to be interpreted even in this form。就好像 ocamlopt
调用 "native compilation" 是关于将程序的 ocamlrun
和非本机 bytecode
打包在一个文件中并使其可执行。不完全是我所期望的。我显然错过了一些重要的观点。但如果是这样的话,我会对为什么它不像我预期的那样感兴趣。
其他编译为本机代码的语言也有同样的问题:让一些天真的用户(比如我自己)有大致相同的问题:
- 开始:
- 生锈:
我也用 Haskell 进行了测试,并且在没有调整的情况下,所有语言编译器都为 "hello world" 程序制作了超过 700KB 的二进制文件(在调整之前 Ocaml 也是如此)。
你的问题很宽泛,我不确定它是否符合 Whosebug 的格式。它值得彻底 discussion。
A size of 190KB is way too much for a simple program like that in today's constraints (iot, android, alpine VM...), and compares badly with simple C program (around ~6KB, or directly coding ASM and tweaking things to get a working binary that could be around 150B)
首先,这不是一个公平的比较。如今,编译后的 C 二进制文件远非独立的二进制文件。它应该更像是一个框架中的插件。因此,如果您想计算给定二进制文件实际使用了多少字节,我们将计算加载程序、shell、libc 库和整个 linux 或 windows 的大小内核 - 总的来说就是 运行 应用程序的时间。
OCaml 与 Java 或 Common Lisp 不同,它对通用 C 运行time 非常友好,并尝试重用其大部分功能。但是 OCaml 仍然有它自己的 运行time,其中最大(也是最重要的部分)是垃圾收集器。 运行时间不是很大(大约 30 KLOC),但仍然对权重有贡献。由于 OCaml 使用静态链接,每个 OCaml 程序都会有它的副本。
因此,C 二进制文件具有显着优势,因为它们通常 运行 在 C 运行 时间已经可用的系统中(因此它通常被排除在等式之外)。但是,有些系统根本没有 C 运行time,只有 OCaml 运行time 存在,请参阅 Mirage for example. In such systems, OCaml binaries are much more favorable. Another example is the OCaPic project, in which (after tweaking the compiler and runtime) they managed to fit OCaml runtime and programs into 64Kb Flash (read the paper 它对二进制大小非常有见地)。
How to get the smallest binary from ocamlopt?
当确实需要最小化尺寸时,使用 Mirage Unikernels 或实现您自己的 运行 时间。对于一般情况,使用 strip
和 upx
。 (例如,使用 upx --best
我能够将您的示例的二进制大小减少到 50K,而无需任何其他技巧)。如果性能不是那么重要,那么您可以使用字节码,它通常比机器码小。因此,您将支付一次费用(运行 时间大约为 200k),每个程序只需支付几个字节(例如,您的 helloworld 为 200 字节)。
另外,不要创建很多小的二进制文件,而是创建一个二进制文件。在您的特定示例中,helloworld 编译单元的大小在字节码中为 200 个字节,在机器码中为 700 个字节。剩下的 50k 是启动线束,应该只包含一次。此外,由于 OCaml 支持 运行 时间内的动态链接,您可以轻松创建一个加载器,在需要时加载模块。在这种情况下,二进制文件将变得非常小(数百字节)。
It is as if what ocamlopt calls "native compilation" is about packing ocamlrun and the not-native bytecode of your program in one file and make it executable. Not exactly what I would have expected. I obviously missed some important point. But if that is the case, I'll be interested why it isn't as I expected.
不-不,这是完全错误的。本机编译是指将程序编译为机器码,无论是 x86、ARM 还是其他。 运行time 是用 C 语言编写的,编译为机器代码,并且也被链接。 OCaml Standard Library大部分是用OCaml写的,也编译成机器码,也链接成二进制(只有那些用到的模块,OCaml静态链接效率很高,前提是程序被拆分成模块(编译单元)相当好)。
关于OCAMLRUNPARAM
环境变量,它只是一个参数化运行时间行为的环境变量,主要是垃圾收集器的参数。
我很惊讶地看到即使是像这样的简单程序:
print_string "Hello world !\n";
当通过 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
如何从ocamlopt获取最小的二进制文件?
190KB
的大小对于像今天的约束(iot、android、alpine VM...)这样的简单程序来说太多了,并且与简单的 C 程序相比很糟糕(大约 6KB,或者直接编码 ASM 并调整一些东西以获得一个大约 150B 的工作二进制文件)。我天真地认为我可以简单地放弃 C
来编写简单的静态程序来做一些琐碎的事情,并且在编译之后我会得到一些简单的汇编代码,这些代码的大小与等效的 C 程序相比还不够大。那可能吗 ?
我觉得我明白了:
当删除 gcc 的 -s
以获得一些关于二进制文件中剩余内容的提示时,我可以注意到很多 ocaml
符号,而且我还读到了 [= 的一些环境变量18=] are meant to be interpreted even in this form。就好像 ocamlopt
调用 "native compilation" 是关于将程序的 ocamlrun
和非本机 bytecode
打包在一个文件中并使其可执行。不完全是我所期望的。我显然错过了一些重要的观点。但如果是这样的话,我会对为什么它不像我预期的那样感兴趣。
其他编译为本机代码的语言也有同样的问题:让一些天真的用户(比如我自己)有大致相同的问题:
- 开始:
- 生锈:
我也用 Haskell 进行了测试,并且在没有调整的情况下,所有语言编译器都为 "hello world" 程序制作了超过 700KB 的二进制文件(在调整之前 Ocaml 也是如此)。
你的问题很宽泛,我不确定它是否符合 Whosebug 的格式。它值得彻底 discussion。
A size of 190KB is way too much for a simple program like that in today's constraints (iot, android, alpine VM...), and compares badly with simple C program (around ~6KB, or directly coding ASM and tweaking things to get a working binary that could be around 150B)
首先,这不是一个公平的比较。如今,编译后的 C 二进制文件远非独立的二进制文件。它应该更像是一个框架中的插件。因此,如果您想计算给定二进制文件实际使用了多少字节,我们将计算加载程序、shell、libc 库和整个 linux 或 windows 的大小内核 - 总的来说就是 运行 应用程序的时间。
OCaml 与 Java 或 Common Lisp 不同,它对通用 C 运行time 非常友好,并尝试重用其大部分功能。但是 OCaml 仍然有它自己的 运行time,其中最大(也是最重要的部分)是垃圾收集器。 运行时间不是很大(大约 30 KLOC),但仍然对权重有贡献。由于 OCaml 使用静态链接,每个 OCaml 程序都会有它的副本。
因此,C 二进制文件具有显着优势,因为它们通常 运行 在 C 运行 时间已经可用的系统中(因此它通常被排除在等式之外)。但是,有些系统根本没有 C 运行time,只有 OCaml 运行time 存在,请参阅 Mirage for example. In such systems, OCaml binaries are much more favorable. Another example is the OCaPic project, in which (after tweaking the compiler and runtime) they managed to fit OCaml runtime and programs into 64Kb Flash (read the paper 它对二进制大小非常有见地)。
How to get the smallest binary from ocamlopt?
当确实需要最小化尺寸时,使用 Mirage Unikernels 或实现您自己的 运行 时间。对于一般情况,使用 strip
和 upx
。 (例如,使用 upx --best
我能够将您的示例的二进制大小减少到 50K,而无需任何其他技巧)。如果性能不是那么重要,那么您可以使用字节码,它通常比机器码小。因此,您将支付一次费用(运行 时间大约为 200k),每个程序只需支付几个字节(例如,您的 helloworld 为 200 字节)。
另外,不要创建很多小的二进制文件,而是创建一个二进制文件。在您的特定示例中,helloworld 编译单元的大小在字节码中为 200 个字节,在机器码中为 700 个字节。剩下的 50k 是启动线束,应该只包含一次。此外,由于 OCaml 支持 运行 时间内的动态链接,您可以轻松创建一个加载器,在需要时加载模块。在这种情况下,二进制文件将变得非常小(数百字节)。
It is as if what ocamlopt calls "native compilation" is about packing ocamlrun and the not-native bytecode of your program in one file and make it executable. Not exactly what I would have expected. I obviously missed some important point. But if that is the case, I'll be interested why it isn't as I expected.
不-不,这是完全错误的。本机编译是指将程序编译为机器码,无论是 x86、ARM 还是其他。 运行time 是用 C 语言编写的,编译为机器代码,并且也被链接。 OCaml Standard Library大部分是用OCaml写的,也编译成机器码,也链接成二进制(只有那些用到的模块,OCaml静态链接效率很高,前提是程序被拆分成模块(编译单元)相当好)。
关于OCAMLRUNPARAM
环境变量,它只是一个参数化运行时间行为的环境变量,主要是垃圾收集器的参数。