Linux 中 GHC 的编译、依赖项和可执行文件大小



问题 1

为了缩短 haskell 中的编译时间(无论是否使用底层功能,都会编译包含的任何模块),是否有任何工具可以警告 Haskell程序员正在包含不必要的模块?例如,假设我们有以下设置:

必要模块1.hs

module NecessaryModule1 where
addNumber1 :: Int -> Int -> Int
addNumber1 a b = a + b

必要模块2.hs

module NecessaryModule2 where
addNumber2 :: Int -> Int -> Int
addNumber2 a b = a + b

测试.hs

module Test where
import NecessaryModule1
import NecessaryModule2

主.hs

module Main where
import Test
myadd :: Int->Int->Int
myadd a b = a + b
main::IO()
main = print(myadd 5 6)

然后这样的工具会警告您:

  • Main.hs :因为未使用测试的功能
  • Test.hs : 因为未使用 Essential Module1 和 NeedModule2 的功能


问题2

如果我通过以下方式编译上面的代码:

ghc -o testProg Main.hs

然后我得到一个 833504 字节的可执行文件大小。但是,如果我将 Main.hs 更改为:

主.hs

module Main where
--import Test
myadd :: Int->Int->Int
myadd a b = a + b
main::IO()
main = print(myadd 5 6)

然后可执行文件大小减小到833057。鉴于 Main.hs 中未使用测试模块的功能,为什么可执行文件大小存在差异?

Q1 Haskell无法警告您未使用的模块,因为它们将来可能会被另一个包导入。但是,导入模块时,只会链接导入的模块,因此,如果使用从未导入的模块创建可执行文件,则该模块将不会包含在可执行文件中,除非您明确告诉cabal链接它。

当您实际导入模块时,如果您在编译时传递 -fwarn-unused-imports 标志,GHC 可以警告您不使用该模块。您还应考虑使用 -Wall ,这将启用此警告和许多其他有用的警告。使用-Werror将使 GHC 拒绝编译带有警告的模块,例如未使用的导入或死代码。

您还可以将标志-split-objs传递给 GHC,这将使 GHC 为每个函数创建一个对象文件(或多或少),而不是每个模块创建一个对象文件,从而可以大大减少可执行文件的大小。

Q2 模板 Haskell 语言扩展可以浏览模块的本地范围,作为其某些功能的一部分。因此,GHC 在编译模块时必须包含显式导入的代码,因为可能有一些 TH 功能依赖于它。启用某些级别的优化(如-O2)可能会再次删除未使用的代码,但不能保证。

您可以考虑使用 -shared 编译标志进行编译,

该标志将使用共享库进行编译并大幅减少整体二进制大小,缺点是如果要在其他计算机上使用编译的二进制文件,则必须复制库文件。

最新更新