Почему исполняемые файлы Haskell/GHC настолько велики в размерах файлов?
Возможный дубликат:
Небольшая программа Haskell, скомпилированная с GHC в огромные бинарные файлы
Недавно я заметил, насколько велики исполняемые файлы Haskell. Все ниже было скомпилировано в GHC 7.4.1 с -O2
в Linux.
-
Hello World (main = putStrLn "Hello World!"
) - более 800 KiB. Запуск strip
над ним уменьшает размер файла до 500 KiB; даже добавление -dynamic
в компиляцию не очень помогает, оставляя меня с удаленным исполняемым файлом около 400 KiB.
-
Компиляция очень примитивного примера с участием Parsec дает 1,7 файла MiB.
-- File: test.hs
import qualified Text.ParserCombinators.Parsec as P
import Data.Either (either)
-- Parses a string of type "x y" to the tuple (x,y).
testParser :: P.Parser (Char, Char)
testParser = do
a <- P.anyChar
P.char ' '
b <- P.anyChar
return (a, b)
-- Parse, print result.
str = "1 2"
main = print $ either (error . show) id . P.parse testParser "" $ str
-- Output: ('1','2')
Parsec может быть большой библиотекой, но я использую только крошечное подмножество, и действительно оптимизированный код ядра, созданный выше, значительно меньше, чем исполняемый файл:
$ ghc -O2 -ddump-simpl -fforce-recomp test.hs | wc -c
49190 (bytes)
Следовательно, это не тот случай, когда огромное количество Parsec действительно найдено в программе, что было моим первоначальным допущением.
Почему исполняемые файлы такого огромного размера? Что-то я могу с этим сделать (кроме динамической компоновки)?
Ответы
Ответ 1
Чтобы эффективно уменьшить размер исполняемого файла, созданного компилятором Glasgow Haskell, вам нужно сосредоточиться на
- использование динамической компоновки с опцией
-dynamic
, переданной в ghc, поэтому код модуля не будет включен в окончательный исполняемый файл, используя общие (динамические) библиотеки. Необходимо наличие общих версий этих библиотек GHC в системе!
- удаление информации об отладке окончательного исполняемого файла (f.E. ленточным инструментом GNU binutils)
- удаление импортных неиспользуемых модулей (не ожидайте прироста при динамической компоновке)
Простой пример hello world имеет окончательный размер 9 KiB и Parsec test около 28 KiB (оба 64-разрядных исполняемых файла Linux), которые я нахожу довольно маленькими и приемлемыми для такой языковой реализации высокого уровня.
Ответ 2
Я понимаю, что если вы используете одну функцию из пакета X, весь пакет статически связан. Я не думаю, что GHC на самом деле связывает функциональные функции. (Если вы не используете взломанные "расколотые объекты", которые "вызывают уродливость компоновщика".)
Но если вы связываете динамически, это должно исправить это. Поэтому я не уверен, что предложить здесь...
(Я уверен, что видел сообщение в блоге, когда впервые появилась динамическая компоновка, демонстрируя Hello World, скомпилированный в двоичный файл 2KB. Очевидно, я не могу найти этот пост в блоге сейчас... grr.)
Рассмотрим также кросс-модульную оптимизацию. Если вы пишете парсер Parsec, скорее всего, GHC проведет все определения парсера и упростит их до самого эффективного кода. И, конечно же, ваши несколько строк Haskell выпустили 50 КБ Core. Должно ли это получиться в 37 раз больше при компиляции в машинный код? Я не знаю. Вы могли бы попытаться взглянуть на код STG и Cmm, созданный на следующих шагах. (Извините, я не помню флагов компилятора с моей головы...)