【问题标题】:How to reduce the size of the executable?如何减小可执行文件的大小?
【发布时间】:2022-01-13 03:04:25
【问题描述】:

当我使用 {fmt} 库编译此代码时,可执行文件大小变为 255 KiB,而仅使用 iostream 标头则变为 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 添加到命令中以从可执行文件中删除所有符号表和重定位信息,它变为156 KiB。但仍然比 iostream 版本高出约 2.5 倍。

【问题讨论】:

  • 我不认为它会做太多,但用 替换“core.h”,只包含你使用的内容。
  • @Pepijn Kramer 您的意思是在 C++20 中添加的标头?但我想用fmt::print 替换cout,这可能会执行得更快。
  • 它是关于最小化您包含的代码“core.h”可能包含比您实际需要的更多的代码。通过包含 您只包含编译代码实际需要的头文件。在这种情况下,如果您只包含 (如果我是对的,它会为您包含 string_view ,因为它将它作为其 api 的一部分公开),您的代码应该编译
  • 轻量级库并不一定意味着“更小的可执行文件大小”。它也可以指运行时内存使用量或 CPU 周期的减少(类似的可观察效果)——可能会实现但也会增加可执行文件大小的事情——或者是开发人员想到的任何其他事情。无论如何,一个或另一个库的细节会导致更大/更小的可执行文件取决于许多因素。
  • @Peter 如您所见,与 iostreams 相比,{fmt} 在生成的二进制代码大小方面的开销减少了 60%,并且非常接近 printf。github.com/fmtlib/fmt/blob/master/README.rst

标签: c++ gcc executable fmt


【解决方案1】:

与任何其他图书馆一样,有固定费用和每次调用费用。 {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.

【讨论】:

  • 如果我将 {fmt} 构建为共享库然后使用它会怎样?它会减少二进制大小吗?我可以在另一台计算机上运行该程序吗?还是需要访问共享库?
  • @digito_evo 它将需要访问共享库 libfmt.so。就像 iostreams 变体需要在兼容版本中访问 libstdc++.so 一样。不同之处可能是无论如何你都会有 libstdc++.so。
  • 请注意,您想将您的工具发送到其他计算机,您需要发送它使用的所有库(包括 x86_64 2.2 MB libstdc++.so,然后 libfmt.so 贡献了一些相当小的150 kB)或者你需要静态链接,我不相信使用 iostreams 在大小方面不会更糟。
  • @Marcus Müller 究竟什么是静态链接?我该怎么做?
  • @digito_evo 相当大的问题,我相信您会在互联网上大量找到有关此的文档,但是如果您阅读了一些文档并且无法完全理解它们,请不要犹豫打开一个新问题!
【解决方案2】:

您忘记了 iostreams 已经包含在 stdlibc++ 中。所以它不计入二进制大小,因为它是一个共享库(通常)。我相信默认情况下 fmt 是作为静态库文件构建的,因此它会增加二进制大小。您需要使用-DBUILD_SHARED_LIBS=TRUE 将 fmt 编译为共享库,如the building instructions 中所述

【讨论】:

  • 所以这会减少二进制大小?
  • 您是在构建 fmt 库还是从发行版中包含?
  • 我刚刚从其文件夹中包含了core.h。我已经从他们的网站下载了 zip 文件。
  • 是的,但仅在磁盘上。一旦它被带入内存,它将与共享库链接。
  • 我希望二进制文件的大小很小。那么这就意味着我需要使用 CMake 来构建 fmt 库,然后在我的代码中使用它?
【解决方案3】:

在您的构建/链接命令中,为什么不使用 -Os 选项(优化大小)?

【讨论】:

  • 这不会有太大的不同。
猜你喜欢
  • 2010-10-01
  • 1970-01-01
  • 2010-09-17
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多