Маленькая программа на Haskell, скомпилированная с помощью GHC, в огромный двоичный файл

127

Даже тривиально маленькие программы на Haskell превращаются в гигантские исполняемые файлы.

Я написал небольшую программу, которая была скомпилирована (с помощью GHC) в двоичный файл размером более 7 МБ!

Что может заставить даже небольшую программу на Haskell компилироваться в огромный двоичный файл?

Что я могу сделать, чтобы уменьшить это?

Дунайский моряк
источник
2
Вы пробовали просто раздеть его?
Фред Фу
21
Запустите программу stripна двоичном файле, чтобы удалить таблицу символов.
Фред Фу,
1
@ tm1rbt: Беги strip test. Эта команда удаляет часть отладочной информации из программы и уменьшает ее размер.
fuz
8
Кроме того, ваши типы данных в трехмерной математической библиотеке должны быть более строгими по соображениям производительности: data M3 = M3 !V3 !V3 !V3и data V3 = V3 !Float !Float !Float. Скомпилируйте с помощью ghc -O2 -funbox-strict-fields.
Дон Стюарт
8
Этот пост обсуждается на мета .
Патрик Хофман

Ответы:

215

Посмотрим, что происходит, попробуем

  $ du -hs A
  13M   A

  $ file A
  A: ELF 64-bit LSB executable, x86-64, version 1 (SYSV), 
     dynamically linked (uses shared libs), for GNU/Linux 2.6.27, not stripped

  $ ldd A
    linux-vdso.so.1 =>  (0x00007fff1b9ff000)
    libXrandr.so.2 => /usr/lib/libXrandr.so.2 (0x00007fb21f418000)
    libX11.so.6 => /usr/lib/libX11.so.6 (0x00007fb21f0d9000)
    libGLU.so.1 => /usr/lib/libGLU.so.1 (0x00007fb21ee6d000)
    libGL.so.1 => /usr/lib/libGL.so.1 (0x00007fb21ebf4000)
    libgmp.so.10 => /usr/lib/libgmp.so.10 (0x00007fb21e988000)
    libm.so.6 => /lib/libm.so.6 (0x00007fb21e706000)
    ...      

Из lddвывода видно, что GHC создал динамически скомпонованный исполняемый файл, но только библиотеки C связаны динамически ! Все библиотеки Haskell скопированы дословно.

В сторону: поскольку это приложение с интенсивной графикой, я бы определенно компилировал с ghc -O2

Вы можете сделать две вещи.

Удаление символов

Простое решение: удалите двоичный файл:

$ strip A
$ du -hs A
5.8M    A

Стрип удаляет символы из объектного файла. Обычно они нужны только для отладки.

Динамически подключаемые библиотеки Haskell

Совсем недавно GHC получил поддержку динамической компоновки библиотек C и Haskell . Большинство дистрибутивов теперь распространяют версию GHC, созданную для поддержки динамического связывания библиотек Haskell. Общие библиотеки Haskell могут использоваться многими программами Haskell, не копируя их каждый раз в исполняемый файл.

На момент написания поддерживаются Linux и Windows.

Чтобы позволить библиотекам Haskell быть динамически связанными, вам нужно скомпилировать их -dynamic, например:

 $ ghc -O2 --make -dynamic A.hs

Кроме того, любые библиотеки, которыми вы хотите поделиться, должны быть созданы с помощью --enabled-shared:

 $ cabal install opengl --enable-shared --reinstall     
 $ cabal install glfw   --enable-shared --reinstall

В итоге вы получите исполняемый файл гораздо меньшего размера, в котором зависимости C и Haskell будут динамически разрешены.

$ ghc -O2 -dynamic A.hs                         
[1 of 4] Compiling S3DM.V3          ( S3DM/V3.hs, S3DM/V3.o )
[2 of 4] Compiling S3DM.M3          ( S3DM/M3.hs, S3DM/M3.o )
[3 of 4] Compiling S3DM.X4          ( S3DM/X4.hs, S3DM/X4.o )
[4 of 4] Compiling Main             ( A.hs, A.o )
Linking A...

И вуаля!

$ du -hs A
124K    A

которые можно разделить, чтобы сделать еще меньше:

$ strip A
$ du -hs A
84K A

Очень приятный исполняемый файл, созданный из множества динамически связанных частей C и Haskell:

$ ldd A
    libHSOpenGL-2.4.0.1-ghc7.0.3.so => ...
    libHSTensor-1.0.0.1-ghc7.0.3.so => ...
    libHSStateVar-1.0.0.0-ghc7.0.3.so =>...
    libHSObjectName-1.0.0.0-ghc7.0.3.so => ...
    libHSGLURaw-1.1.0.0-ghc7.0.3.so => ...
    libHSOpenGLRaw-1.1.0.1-ghc7.0.3.so => ...
    libHSbase-4.3.1.0-ghc7.0.3.so => ...
    libHSinteger-gmp-0.2.0.3-ghc7.0.3.so => ...
    libHSghc-prim-0.2.0.0-ghc7.0.3.so => ...
    libHSrts-ghc7.0.3.so => ...
    libm.so.6 => /lib/libm.so.6 (0x00007ffa4ffd6000)
    librt.so.1 => /lib/librt.so.1 (0x00007ffa4fdce000)
    libdl.so.2 => /lib/libdl.so.2 (0x00007ffa4fbca000)
    libHSffi-ghc7.0.3.so => ...

И последнее: даже в системах со статической компоновкой вы можете использовать -split-objs , чтобы получить один файл .o для каждой функции верхнего уровня, что может еще больше уменьшить размер статически связанных библиотек. Ему нужно, чтобы GHC был построен с -split-objs, что некоторые системы забывают делать.

Дон Стюарт
источник
7
когда должна появиться динамическая компоновка для ghc на Mac?
Картер Тацио Шонвальд
1
... не cabal installудаляет установленный двоичный файл по умолчанию?
hvr
1
похоже, что выполнение этого в Windows делает получившийся файл неработоспособным, он жалуется на отсутствие libHSrts-ghc7.0.3.dll
is7s
3
будет ли этот двоичный файл работать на других машинах Linux после этих процедур?
ア レ ッ ク ス
1
Привет ОП с 2011 года! Я из будущего и могу сказать, что исполняемый файл pandoc в Ubuntu 16.04 имеет размер 50 МБ и не будет изменяться на основе packages.ubuntu.com/zesty/pandoc . Сообщение для себя и других в ближайшем будущем: свяжитесь с сопровождающим пакета и спросите, было ли enable-sharedрассмотрено. launchpad.net/ubuntu/+source/pandoc/+bugs
Стефан Гуришон
11

По умолчанию Haskell использует статическое связывание. То есть все привязки к OpenGL копируются в вашу программу. Поскольку они довольно большие, ваша программа излишне раздувается. Вы можете обойти это, используя динамическое связывание, хотя по умолчанию оно не включено.

FUZ
источник
5
Чтобы обойти это, вы можете динамически связывать библиотеки. Не уверен, почему важно то, что установлено по умолчанию, флаг достаточно простой.
Thomas M. DuBuisson
4
Проблема в том, что «любые библиотеки, которыми вы хотите поделиться, должны быть построены --enabled-shared», поэтому, если ваша платформа Haskell поставляется с библиотеками, созданными без --enabled sharedвас, вам придется перекомпилировать базовые библиотеки, что может быть довольно болезненным.
nponeccop 04