Небольшая программа Haskell, скомпилированная с GHC в огромную двоичную

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

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

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

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

Ответ 1

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

  $ 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

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

Динамически связанные библиотеки 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...

И, voilà!

$ 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, что некоторые системы забывают делать.

Ответ 2

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