Skip to content

[Linux] Adding swift-argument-parser as a dependency increases binary size to 50+MB (10x larger than without) #748

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
2 tasks done
wreedb opened this issue Mar 13, 2025 · 7 comments

Comments

@wreedb
Copy link

wreedb commented Mar 13, 2025

With Swift version 6.0.3, on Linux, compiling a project with swift-argument-parser increases binary size from between 8MB/5MB (un-stripped/stripped), to 68MB/54MB respectively; resulting in a size increase of 8-10x.

ArgumentParser version: 1.5.0, also tested with 1.3.0 with identical results
Swift version: 6.0.3, target: x86_64-unknown-linux-gnu

Checklist

  • If possible, I've reproduced the issue using the main branch of this package
    Results identical to that of versions 1.3.0 and 1.5.0
  • I've searched for existing GitHub issues
    I read through #296, however the sizes there are much smaller and within reason.

Steps to Reproduce

Raw new project:

swift package init --type executable
swift build -c release --static-swift-stdlib

Resulting binary: 8.1MB
Stripped: 5.6MB

A bit beefy, but I'm willing to pay that price for anyone who uses my binaries to not depend on the Swift STD Library, since it's only officially supported on three distributions

In Package.swift, adding swift-argument-parser

let package = Package(
    name: "test",
    dependencies: [
        .package(url: "https://github.com/apple/swift-argument-parser.git", from: "1.5.0")
    ],
    targets: [
        .executableTarget(
            name: "test",
            dependencies: [.product(name: "ArgumentParser", package: "swift-argument-parser")])
    ]
)

swift build -c release --static-swift-stdlib
Resulting binary: 68MB
Stripped: 54MB

... Am I crazy or is this increase in size a bit strange?

When statically linked against the x86_64-swift-linux-musl SDK, the stripped binary is roughly the same, at 56MB.

Expected behavior

Perhaps an increase of a few MB, I probably wouldn't even be bothered by an increase of 10-15MB.

Actual behavior

Shown above in "Steps to Reproduce", the resulting binary isn't just a little bit larger, but exponentially larger.

I will add below the results of running bloaty on both a fresh package with and without swift-argument-parser added as a dependency.

Without swift-argument-parser

    FILE SIZE        VM SIZE    
 --------------  -------------- 
  43.7%  3.50Mi  61.3%  3.50Mi    .text
  20.6%  1.65Mi   0.0%       0    .strtab
  10.5%   857Ki  14.7%   857Ki    .rodata
  10.0%   823Ki   0.0%       0    .symtab
   6.3%   513Ki   8.8%   513Ki    .eh_frame
   4.1%   333Ki   5.7%   333Ki    .rela.dyn
   0.0%       0   2.9%   168Ki    .bss
   1.3%   110Ki   1.9%   110Ki    .eh_frame_hdr
   0.9%  76.2Ki   1.3%  76.2Ki    .data.rel.ro
   0.8%  61.7Ki   1.1%  61.7Ki    .data
   0.6%  48.0Ki   0.8%  48.0Ki    swift5_backtrace
   0.3%  21.5Ki   0.4%  21.5Ki    swift5_typeref
   0.2%  20.0Ki   0.3%  20.0Ki    swift5_assocty
   0.2%  18.2Ki   0.3%  15.4Ki    [35 Others]
   0.2%  16.1Ki   0.3%  16.1Ki    swift5_fieldmd
   0.1%  5.12Ki   0.1%  5.12Ki    swift5_protocol_conformances
   0.1%  4.27Ki   0.1%  4.27Ki    .dynsym
   0.1%  4.19Ki   0.1%  4.19Ki    swift5_reflstr
   0.0%  3.68Ki   0.1%  3.68Ki    .rela.plt
   0.0%  3.56Ki   0.0%       0    [ELF Section Headers]
   0.0%  3.46Ki   0.1%  3.46Ki    [LOAD #3 [RW]]
 100.0%  8.01Mi 100.0%  5.71Mi    TOTAL

With swift-argument-parser

    FILE SIZE        VM SIZE    
 --------------  -------------- 
  50.4%  34.2Mi  62.6%  34.2Mi    .rodata
  20.6%  14.0Mi  25.6%  14.0Mi    .text
  12.9%  8.75Mi   0.0%       0    .strtab
   4.8%  3.26Mi   0.0%       0    .symtab
   3.3%  2.27Mi   4.2%  2.27Mi    .eh_frame
   2.4%  1.63Mi   3.0%  1.63Mi    .rela.dyn
   0.0%       0   1.3%   725Ki    .bss
   0.8%   542Ki   1.0%   542Ki    .data.rel.ro
   0.6%   447Ki   0.0%       0    .debug_info
   0.6%   443Ki   0.8%   443Ki    .eh_frame_hdr
   0.6%   435Ki   0.0%       0    .debug_str
   0.5%   350Ki   0.0%       0    .debug_names
   0.5%   324Ki   0.5%   276Ki    [41 Others]
   0.4%   309Ki   0.0%       0    .debug_loc
   0.4%   305Ki   0.0%       0    .debug_ranges
   0.4%   274Ki   0.5%   274Ki    .data
   0.2%   109Ki   0.2%   109Ki    swift5_typeref
   0.1%  99.8Ki   0.2%  99.8Ki    swift5_fieldmd
   0.1%  93.1Ki   0.2%  93.1Ki    .gcc_except_table
   0.1%  88.5Ki   0.0%       0    .debug_line
   0.1%  65.3Ki   0.1%  65.3Ki    swift5_reflstr
 100.0%  67.9Mi 100.0%  54.6Mi    TOTAL

Note:

These were both compiled with swift build -c release --static-swift-stdlib. Only difference is the dependency added to Package.swift

I'd be willing to try any suggestions you have and report back with my results

@t089
Copy link

t089 commented Mar 13, 2025

I might be wrong, but I think the Mutex type introduced a dependency on Foundation and this causes the bloat. The solution here is either:

  • implement Mutex without Foundation
  • use import FoundationEssentials if available which will reduce the bloat significantly

Edit: I have been (partially) wrong. Indeed Mutex relies on Foundation but also several other files pull in Foundation.

@wreedb
Copy link
Author

wreedb commented Mar 13, 2025

Thanks for the reply,

* use `import FoundationEssentials` if available which will reduce the bloat significantly

I tried this just now, sadly it seems that the problem might not have to do with Foundation at all.
All of the binary sizes that I wrote above were compiled with the default main.swift file in the project, with no import statements at all.

I then tried (all separately), compiling while importing:

  • Foundation
  • FoundationEssentials
  • FoundationEssentials + ArgumentParser
  • Foundation + ArgumentParser
    All of which produced identical results.
    It seems the difference only comes from swift-argument-parser solely being in Package.swift.

@t089
Copy link

t089 commented Mar 14, 2025

No, the problem is that swift-argument-parser itself pulls in Foundation. Not only for the lock there are a couple of other places as well.

I put up a quick hack to only use FoundationEssentials here: main...t089:swift-argument-parser:foundationless

You can try building with

.package(url: "https://github.com/t089/swift-argument-parser.git", branch: "foundationless"),

In my test I got:

68M argtest-default
22M argtest-foundationless
13M argtest-foundationless-stripped

@wreedb
Copy link
Author

wreedb commented Mar 14, 2025

I put up a quick hack to only use FoundationEssentials here: main...t089:swift-argument-parser:foundationless

Ah, right you are.

I tested as well, and my results were identical to yours, both with fully static linking against Musl and static Swift STDLIB with dynamic GLIBC they were under 15MB

I appreciate your efforts to help me troubleshoot this very much; thank you. Is this an issue that the project intends to upstream (I.E. using FoundationEssentials when compiling on linux)?

I'm fine with forking your workaround to hold for personal use; since as of now the library does everything I need it to and more. I know linux isn't as large of a priority as the mainly supported platforms, so you can close/keep the issue at your discretion.

Thanks again.

@t089
Copy link

t089 commented Mar 14, 2025

This issue should stay open until it is resolved, linux support is important!

I've realized there is already a PR open from @0xTim which does the same: #674

So I think we should be able to get this across the finish line!

@t089
Copy link

t089 commented Mar 14, 2025

My branch now passes all tests on both Linux and macOS. I think I will open this as a new PR soon.

@wreedb
Copy link
Author

wreedb commented Mar 14, 2025

My branch now passes all tests on both Linux and macOS. I think I will open this as a new PR soon.

Happy to hear that! Thank you.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

2 participants