如何减小可执行文件的大小?
How to reduce the size of the executable?
当我使用 {fmt}
库编译此代码时,executable 大小变为 255 KiB,而仅使用 iostream
header 则变为 65 KiB(使用GCC v11.2).
time_measure.cpp
#include <iostream>
#include "core.h"
#include <string_view>
int main( )
{
// std::cout << std::string_view( "Oh hi!" );
fmt::print( "{}", std::string_view( "Oh hi!" ) );
return 0;
}
这是我的构建命令:
g++ -std=c++20 -Wall -O3 -DNDEBUG time_measure.cpp -I include format.cc -o runtime_measure.exe
与 iostream
相比,{fmt}
库不应该是轻量级的吗?或者我做错了什么?
编辑:通过在命令中添加 -s
以从 executable 中删除所有符号 table 和重定位信息,它变为 156 KiB。但仍然比 iostream
版本多 2.5 倍。
您忘记了 iostreams 已经包含在 stdlibc++ 中。因此它不计入二进制大小,因为它是一个共享库(通常)。我相信默认情况下 fmt 是作为静态库文件构建的,因此它会增加二进制文件的大小。您需要使用 -DBUILD_SHARED_LIBS=TRUE
将 fmt 编译为共享库,如 the building instructions
中所述
与任何其他图书馆一样,有固定成本和每次调用成本。 {fmt} 库的固定成本确实是 around 100-150k,没有调试信息(这取决于编译器标志)。在您的示例中,您正在比较与库链接的固定成本,而 iostreams 看起来更小的原因是因为它包含在动态链接的标准库本身中,不计入可执行文件的二进制大小。
请注意,此大小的很大一部分来自浮点格式化功能,它甚至不存在于 iostreams(最短往返表示)中。
如果您想比较每次调用的二进制大小,这对于具有大量格式化函数调用的实际代码更为重要,您可以查看目标文件或生成的程序集。例如:
#include <fmt/core.h>
int main() {
fmt::print("Oh hi!");
}
生成 (https://godbolt.org/z/qWTKEMqoG)
.LC0:
.string "Oh hi!"
main:
sub rsp, 24
pxor xmm0, xmm0
xor edx, edx
mov edi, OFFSET FLAT:.LC0
mov rcx, rsp
mov esi, 6
movaps XMMWORD PTR [rsp], xmm0
call fmt::v8::vprint(fmt::v8::basic_string_view<char>, fmt::v8::basic_format_args<fmt::v8::basic_format_context<fmt::v8::appender, char> >)
xor eax, eax
add rsp, 24
ret
而
#include <iostream>
int main() {
std::cout << "Oh hi!";
}
生成 (https://godbolt.org/z/frarWvzhP)
.LC0:
.string "Oh hi!"
main:
sub rsp, 8
mov edx, 6
mov esi, OFFSET FLAT:.LC0
mov edi, OFFSET FLAT:_ZSt4cout
call std::basic_ostream<char, std::char_traits<char> >& std::__ostream_insert<char, std::char_traits<char> >(std::basic_ostream<char, std::char_traits<char> >&, char const*, long)
xor eax, eax
add rsp, 8
ret
_GLOBAL__sub_I_main:
sub rsp, 8
mov edi, OFFSET FLAT:_ZStL8__ioinit
call std::ios_base::Init::Init() [complete object constructor]
mov edx, OFFSET FLAT:__dso_handle
mov esi, OFFSET FLAT:_ZStL8__ioinit
mov edi, OFFSET FLAT:_ZNSt8ios_base4InitD1Ev
add rsp, 8
jmp __cxa_atexit
除了 cout
的静态初始化外,没有太大区别,因为这里几乎没有格式化,所以在这两种情况下都只是一个函数调用。添加格式后,您将很快看到 {fmt} 的好处,请参阅例如http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2019/p0645r10.html#BinaryCode.
在您的 build/link 命令中,您为什么不使用 -Os 选项(优化大小)?
当我使用 {fmt}
库编译此代码时,executable 大小变为 255 KiB,而仅使用 iostream
header 则变为 65 KiB(使用GCC v11.2).
time_measure.cpp
#include <iostream>
#include "core.h"
#include <string_view>
int main( )
{
// std::cout << std::string_view( "Oh hi!" );
fmt::print( "{}", std::string_view( "Oh hi!" ) );
return 0;
}
这是我的构建命令:
g++ -std=c++20 -Wall -O3 -DNDEBUG time_measure.cpp -I include format.cc -o runtime_measure.exe
与 iostream
相比,{fmt}
库不应该是轻量级的吗?或者我做错了什么?
编辑:通过在命令中添加 -s
以从 executable 中删除所有符号 table 和重定位信息,它变为 156 KiB。但仍然比 iostream
版本多 2.5 倍。
您忘记了 iostreams 已经包含在 stdlibc++ 中。因此它不计入二进制大小,因为它是一个共享库(通常)。我相信默认情况下 fmt 是作为静态库文件构建的,因此它会增加二进制文件的大小。您需要使用 -DBUILD_SHARED_LIBS=TRUE
将 fmt 编译为共享库,如 the building instructions
与任何其他图书馆一样,有固定成本和每次调用成本。 {fmt} 库的固定成本确实是 around 100-150k,没有调试信息(这取决于编译器标志)。在您的示例中,您正在比较与库链接的固定成本,而 iostreams 看起来更小的原因是因为它包含在动态链接的标准库本身中,不计入可执行文件的二进制大小。
请注意,此大小的很大一部分来自浮点格式化功能,它甚至不存在于 iostreams(最短往返表示)中。
如果您想比较每次调用的二进制大小,这对于具有大量格式化函数调用的实际代码更为重要,您可以查看目标文件或生成的程序集。例如:
#include <fmt/core.h>
int main() {
fmt::print("Oh hi!");
}
生成 (https://godbolt.org/z/qWTKEMqoG)
.LC0:
.string "Oh hi!"
main:
sub rsp, 24
pxor xmm0, xmm0
xor edx, edx
mov edi, OFFSET FLAT:.LC0
mov rcx, rsp
mov esi, 6
movaps XMMWORD PTR [rsp], xmm0
call fmt::v8::vprint(fmt::v8::basic_string_view<char>, fmt::v8::basic_format_args<fmt::v8::basic_format_context<fmt::v8::appender, char> >)
xor eax, eax
add rsp, 24
ret
而
#include <iostream>
int main() {
std::cout << "Oh hi!";
}
生成 (https://godbolt.org/z/frarWvzhP)
.LC0:
.string "Oh hi!"
main:
sub rsp, 8
mov edx, 6
mov esi, OFFSET FLAT:.LC0
mov edi, OFFSET FLAT:_ZSt4cout
call std::basic_ostream<char, std::char_traits<char> >& std::__ostream_insert<char, std::char_traits<char> >(std::basic_ostream<char, std::char_traits<char> >&, char const*, long)
xor eax, eax
add rsp, 8
ret
_GLOBAL__sub_I_main:
sub rsp, 8
mov edi, OFFSET FLAT:_ZStL8__ioinit
call std::ios_base::Init::Init() [complete object constructor]
mov edx, OFFSET FLAT:__dso_handle
mov esi, OFFSET FLAT:_ZStL8__ioinit
mov edi, OFFSET FLAT:_ZNSt8ios_base4InitD1Ev
add rsp, 8
jmp __cxa_atexit
除了 cout
的静态初始化外,没有太大区别,因为这里几乎没有格式化,所以在这两种情况下都只是一个函数调用。添加格式后,您将很快看到 {fmt} 的好处,请参阅例如http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2019/p0645r10.html#BinaryCode.
在您的 build/link 命令中,您为什么不使用 -Os 选项(优化大小)?