5

I have a project written in C++, and the platform to be deployed in has a limitation of 256KB of binary size.

The toolchain is wasi-sdk-16.0 clang++, we use this compiler to compile the source code into binary in WASM format. In this step we compile the sources with the following CXX_FLAGS.

-fstack-protector-strong -Os -D_FORTIFY_SOURCE=2 -fPIC -Wformat -Wformat-security -Wl,-z,relro,-z,now -Wno-psabi

Then we strip the binary with

strip -s output_binary.wasm

After steps above, the compiled binary size in this step is 254KB.

Then we use wamrc in WAMR to compile the WASM binary with AOT runtime, the command is shown below.

wamrc --enable-indirect-mode --disable-llvm-intrinsics -o output.aot output.wasm

the output binary size become 428KB, much bigger than the limitation (256KB).


After google'd, I use wasm-opt to reduce size,

wasm-opt -Oz output.wasm -o output.wasm

The size become 4KB smaller. (almost useless)


I tried to comfirm how much does my code affects the binary size, so I write simple minimum sample code just called the standard c++ library, as the following,

#include <vector>
#include <iostream>

int main() {
    std::vector<int> vec = {1,2,3,4,5};
    for (auto v: vec) {
        std::cout << v << " ";
    }
}

The binary size after compiled has alrealy become 205KB.


I also tried to use a binary size profiler (twiggy) to track the size of each part of the bianry, but the tool failed to open this binary.


So I want to ask

  1. While just including two standard C++ headers makes the binary size reach the size limitation, how can I strip the C++ standard library with the function I really use(I cannot use strip unused function flag because my project is a library provide to others), or is really the c++ standard library affected the binary size?

  2. Is there any other compiler flags, or strip flags, or any other optimization tools can significantly reduce the binary size?

Acane
  • 167
  • 1
  • 9
  • 2
    C++ `iostream` is pretty bulky, involving a lot of library code. It matters a lot *which* two headers you used; e.g. `#include ` would only be template functions so you'd only pay for what you use. Same with `#include `; it's pretty light weight. – Peter Cordes Dec 07 '22 at 20:08
  • 3
    Try building everything with LTO (Link Time Optimization) enabled. Besides usually producing faster code it also often results in smaller binaries. – Jesper Juhl Dec 08 '22 at 01:35
  • 3
    The linker can also do some optimizations of its own (besides LTO). You can enable those with `-Wl,-O` and that can sometimes also reduce binary size (a bit). – Jesper Juhl Dec 08 '22 at 01:44
  • Also look into the `--gc-sections` linker option. It may be of use to you. – Jesper Juhl Dec 08 '22 at 01:48
  • Although the `-Os` compiler option is supposed to optimize for size, it does not always result in the smallest possible binary. Try experimenting with `-Og`, `-O1`, `-O2` & `-O3` as well. – Jesper Juhl Dec 08 '22 at 02:00
  • You can often save space by rearranging your `struct`/`class` members such that you reduce the amount of padding the compiler has to insert between members. This can often save a lot of space for non-trivial programs. See for example https://www.ibm.com/docs/en/i/7.4?topic=requirements-arranging-variables-reduce-padding – Jesper Juhl Dec 08 '22 at 02:19
  • Another thing you can do is to ensure that only functions / types that you *need* to export have non-hidden visibility. See https://gcc.gnu.org/wiki/Visibility – Jesper Juhl Dec 08 '22 at 02:25
  • 2
    Declaring `struct`s and `class`s that you do not need/want to inherit from as `final` can also in some cases help the compilers optimizer to generate faster and sometimes smaller code. – Jesper Juhl Dec 08 '22 at 02:28
  • The `-Oz` compiler option is supposed to more aggressively optimize for size than `-Os` - give it a try. – Jesper Juhl Dec 08 '22 at 02:41
  • Also check out the [sstrip](https://github.com/aunali1/super-strip) tool as a way to possibly shave off a few more bytes from your files. – Jesper Juhl Dec 08 '22 at 02:47
  • Instead of editing an answer into the question, please post your answers as a proper answer to the question. You can even click the accept checkmark on your own answer to show future readers it's what worked for you. – Peter Cordes Dec 08 '22 at 16:05

2 Answers2

2

One of the things I didn't see mentioned in your post is wabt's wasm-strip. I am not familiar enough with it to know if it does more than the simple strip command, but maybe it is worth a try. You can install it with apt install wasm-strip on a Debian system.

From the few micro-benchmarks I see on the internet, C++ wasm binaries have large overhead. For a totally-not-scientific slide you can watch this.

If, for whatever reason, you cannot work the language to produce smaller binaries, you may try to optimize also at the link level, as busybox does.

TachyonicBytes
  • 700
  • 1
  • 8
2

I solve this issue with just replacing iostream and fstream to cstdio. It reduces size from 254KB to 85KB, because there contains too many templates in iostream.

Using iostream Count of functions (with readelf -Ws) Size of Binary
Yes 685 254KB
No 76 85KB

While specifying compiler flags such as -Oz also reduces some size, but the main factor is too many codes were generated from templates. So, do not use C++ stream API (and any other template-heavy general-purpose libraries) when there are binary size limitations. (C libraries are always worth to believe.)

Fedor
  • 17,146
  • 13
  • 40
  • 131
Acane
  • 167
  • 1
  • 9