ask AI

Table of Contents

stack ghc

前回stackを入れたんだけど、これでghcなんて叩くと文句を言われるので、下 記のアイリアスを設定した。サブコマンドで拡張する方式って好きになれない な。

alias ghc='stack ghc -- '
alias ghci='stack ghci -- '

DSL for gnuplot

前回失敗こいた、gnuplotの媒介変数を使う描画があった。

ghci> plotFunc [] (linearScale 1000 (-10,10::Double)) sin(x)/x
<interactive>:1:53: error: Variable not in scope: x

改めてコードを見ていたら、下記のようにすれば良いことに気付いた。

ghci> plotFunc [] (linearScale 100 (-10,10::Double)) (\x -> (sin x) / x)

高階関数でsinを指示しているんだから、ラムダ式でもOKって訳。

gnuplotのスクリプトと描画に必要なデータを発生する為のDSLなんですねぇ。 全体像を把握出来ていなかったオイラーは、スットコドッコイだ、反省。 データ計算の核心部分は、これ。

functionToGraph :: [x] -> (x -> y) -> [(x,y)]
functionToGraph args f = map (\x -> (x, f x)) args

X軸の範囲をLinearScaleでリストに格納。それとY軸値を計算したい関数を、 functionToGraphに渡して、CSVファイルの元データを生成。

compare with …

ふと思いたって、各種静的言語で作成されるサイズを調べてみた。

golang

Go by Example: Hello World

sakae@deb:/tmp$ ./byGO
hello world
sakae@deb:/tmp$ ls -sh byGO
1.8M byGO*

シングルバイナリーが売りだから、納得なサイズだ。

by rust

Generating a new project

sakae@deb:/tmp/hello/src$ ./byRust
Hello, rust!
sakae@deb:/tmp/hello/src$ ls -sk byRust
3752 byRust*

なんでこんなに巨大なんだ。

by ghc

sakae@deb:/tmp/hoge/app$ ./byGHC
Hello, Haskell!
sakae@deb:/tmp/hoge/app$ ls -sk byGHC
2984 byGHC*

同じく、なんでこんなに巨大なんだよう。

上記は、いずれも Debian 11.6(32Bit)な環境。最近出た、本の虫 バージョン は、使いません。だって、裏にFreeBSDが居て、そちらの環境まで壊れるのは、 いやだから(アップデートは見送り)。

ask ChatGPT

聞いてみよう。

haskell 実行ファイルは、何故 巨大なのか

Haskellの実行ファイルが巨大になる理由はいくつかあります。

  1. 静的な型システム:Haskellは強力な静的型システムを持っています。これは、コンパイル時に型エラーを検出することができるという利点がありますが、同時に型情報を保持するための追加のコードが必要になります。この型情報は実行ファイルに組み込まれるため、ファイルサイズが大きくなる要因の1つです。
  2. 高度な最適化:Haskellのコンパイラは高度な最適化を行い、効率的なコードを生成します。これにはインライン展開、特殊化、遅延評価の最適化などが含まれます。これらの最適化手法は、追加のコードやデータ構造を生成することがあり、実行ファイルのサイズが増える原因になります。
  3. 標準ライブラリの組み込み:Haskellには豊富な標準ライブラリがあります。これにはリスト操作、テキスト処理、数値計算などの機能が含まれます。これらのライブラリは便利ですが、実行ファイルに組み込む必要があり、ファイルサイズの増加につながります。
  4. 関数型プログラミングの特性:Haskellは関数型プログラミング言語であり、再帰や高階関数などの特性を持っています。これにより、一般的な手続き型言語と比較して、実行ファイルに含まれるコードの量が増えることがあります。

以上が、Haskellの実行ファイルが巨大になる一般的な理由です。ただし、ファイルサイズは必ずしも問題とは言えません。Haskellの強力な型システムや最適化手法により、効率的な実行が可能になることもあります。


説明に利用したURLを6個、提示して

申し訳ありませんが、私はURLを直接提供することはできません。私はテキス トベースの情報源から学習しており、インターネット上の特定のウェブページ を参照することはありません。ただし、必要な情報や具体的な質問があればお 伝えください。可能な範囲でお手伝いいたします。


実行ファイルのサイズを低減する方法を教えて

Haskellの実行ファイルのサイズを低減するためのいくつかの方法を以下に示します:

  1. スタティックリンクの使用: Haskellの実行ファイルは通常、動的リンクされたライブラリを使用しますが、スタティックリンクを行うことで実行ファイルにライブラリのコードを埋め込むことができます。これにより、ファイルサイズが削減される可能性があります。ただし、スタティックリンクを使用すると、実行ファイルの起動時間が長くなる場合があることに注意してください。
  2. プログラムの最小化: 不要な依存関係やコードを取り除くことで、実行ファイルのサイズを減らすことができます。不要なライブラリのインポート文を削除したり、使用されない関数やデータ型を削除したりするなど、プログラムを最小限の機能に絞ることが重要です。
  3. パッケージの最適化: パッケージマネージャーを使用してHaskellのパッケージを管理している場合、パッケージの依存関係を最適化することで実行ファイルのサイズを減らすことができます。不要なパッケージの依存関係を除外したり、最新の最適化フラグを使用したりするなど、パッケージの構成を見直すことが重要です。
  4. 最適化フラグの使用: Haskellのコンパイラには最適化フラグがあります。これらのフラグを使用することで、生成されるコードの効率性やサイズを向上させることができます。例えば、GHCの場合、"-O2"フラグを使用して最適化レベルを上げることができます。
  5. データ圧縮: 実行ファイルに含まれるデータを圧縮することで、ファイルサイズを減らすことができます。Haskellのビルドツールやパッケージマネージャーには、データの圧縮オプションがある場合がありますので、それを利用することができます。

これらの方法は、実行ファイルのサイズを低減するための一般的なアプローチ です。ただし、ファイルサイズの削減と引き換えに、実行速度やパフォーマン ス


  1. は、平気で嘘の説明だなあ。

参考までに、rustも聞いてみる。

rust 実行ファイルは、何故 巨大なのか

Rust言語による実行ファイルが比較的大きい理由は、いくつかの要素が組み合わさっているためです。

  1. 静的リンク: Rustにはデフォルトで静的リンクが使用されます。これは、実行ファイルにすべての依存関係が含まれることを意味します。他の言語では、依存関係はシステムの共有ライブラリとして提供されることが一般的ですが、Rustでは実行ファイルに組み込まれます。このため、実行ファイルのサイズが大きくなります。
  2. パフォーマンスとセキュリティ: Rustはメモリセーフを重視しているため、ランタイムやガベージコレクタのオーバーヘッドが少なく、高速な実行を提供します。しかし、このために一部のコードが静的にインライン化され、最適化が行われることがあります。最適化されたコードはサイズが大きくなる傾向があります。
  3. 標準ライブラリの含まれること: Rustの標準ライブラリは比較的大きく、多機能です。これは、開発者が多くの一般的なタスクを標準ライブラリで処理できるようにするためです。しかし、これにより実行ファイルのサイズが増えることがあります。
  4. ビルド設定や依存関係: Rustのビルドシステムや依存関係管理ツールであるCargoは、便利で強力な機能を提供しますが、その一環として実行ファイルのサイズが増えることがあります。例えば、ビルドに必要な情報や依存関係の解決結果などが実行ファイルに含まれることがあります。

これらの要素が組み合わさることで、Rustによる実行ファイルのサイズが大きくなることがあります。ただし、Rustは実行速度やメモリ効率に優れており、大規模なアプリケーションや高パフォーマンスを必要とする場合には有利な言語と言えます。また、ビルドオプションや最適化レベルの設定などを調整することで、実行ファイルのサイズを最小限に抑えるとこ


ChatGPTと付き合うには、よきインタビュアー性能が人間側に必要。そして一 番大事な事は疑ってかかる事だな。

ask Bing AI

セカンド・オピニオン

haskell 実行ファイルは、何故 巨大なのか

Haskellの実行ファイルが巨大になる理由は、様々な実行環境での安定稼働を目的として、当該Haskellプログラムが依存するモジュールを、全てスタティックリンクするためだそうです1。スタティックリンクとは、プログラムが実行される際に必要なライブラリを全て実行ファイルに含めることで、実行環境に依存しないようにすることです。そのため、Haskellの実行ファイルは他の言語の実行ファイルよりも大きくなる傾向があります1。

1: teratail.com

もし、Haskellの実行ファイルをコンパクトにしたい場合は、スタティックリ ンクをしない方法があります。その場合、プログラムが依存するライブラリを 別途インストールする必要があります1。

Haskell(ghc)の巨大な実行ファイルをコンパクトにしたい

GHCで巨大なバイナリにコンパイルされた小さなHaskellプログラム


Bingの場合は、エビデンスと言うか、元になったリンクを提示してくるので、 ChatGPTみたいに、いきなりの神託じゃ無いのが高印象だ。

いわば、私的な司書と言うか学芸員を雇っている。無料なんで使い倒せ。 ある人が言っていた。AIが進化して仕事が無くなるとか、置いていかれない様 にこの本を読めとかって、ショック・ドクトリンだと。構造はY2K問題と同一 だよね、ってね。うむ、面白い着眼だな。

それにしても、AIだとコピペで、どんどん記事を量産できる。楽な世の中になっ たものです。オィ、それじゃ人間放棄じゃん。

fat check

ヒントが提示されてたので、少しは人間らしさを発動して、調査する。

まずは、型通りのチェック。原型は

module Main where

main :: IO ()
main = putStrLn "Hello, Haskell!"

これ以上、簡単にならないぐらいの奴。

sakae@deb:/tmp/t/app$ file Main
Main: ELF 32-bit LSB executable, Intel 80386, version 1 (SYSV), dynamically linked, interpreter /lib/ld-linux.so.2, for GNU/Linux 3.2.0, BuildID[sha1]=9bbbab8c215b297366baed3edb4fc26e14d78580, with debug_info, not stripped
sakae@deb:/tmp/t/app$ ldd Main
        linux-gate.so.1 (0xb7edc000)
        libtinfo.so.6 => /lib/i386-linux-gnu/libtinfo.so.6 (0xb7e97000)
        librt.so.1 => /lib/i386-linux-gnu/librt.so.1 (0xb7e8b000)
        libutil.so.1 => /lib/i386-linux-gnu/libutil.so.1 (0xb7e86000)
        libdl.so.2 => /lib/i386-linux-gnu/libdl.so.2 (0xb7e80000)
        libpthread.so.0 => /lib/i386-linux-gnu/libpthread.so.0 (0xb7e5e000)
        libgmp.so.10 => /lib/i386-linux-gnu/libgmp.so.10 (0xb7dd0000)
        libc.so.6 => /lib/i386-linux-gnu/libc.so.6 (0xb7be8000)
        libm.so.6 => /lib/i386-linux-gnu/libm.so.6 (0xb7ae4000)
        /lib/ld-linux.so.2 (0xb7ede000)

外見上は、普通に見えます。いや、C言語屋からすれば、libcだけでいいじゃ んと、文句が出るか。このままじゃ、絶対組み込みシステムには採用してもら いないな。

sakae@deb:/tmp/t/app$ ls -s Main
2984 Main*
sakae@deb:/tmp/t/app$ strip Main
sakae@deb:/tmp/t/app$ ls -s Main
724 Main*

しっかりdebug情報が残っていたんですかね。ghc Main.hsだったんだけど。 もう一度コンパイルしてから、gdbしてみる。

sakae@deb:/tmp/t/app$ gdb -q Main
Reading symbols from Main...
(gdb) b main
Breakpoint 1 at 0x804f249
(gdb) r
Starting program: /tmp/t/app/Main
[Thread debugging using libthread_db enabled]
Using host libthread_db library "/lib/i386-linux-gnu/libthread_db.so.1".

Breakpoint 1, 0x0804f249 in main ()
(gdb) s
Single stepping until exit from function main,
which has no line number information.
hs_main (argc=1, argv=0xbffff5d4, main_closure=0x80f6200, rts_config=...)
    at rts/RtsMain.c:46
46      rts/RtsMain.c: No such file or directory.

確かにdebug用の贅肉がたっぷりと付着してますなあ。

で、贅肉を落として、ひきしまったかと言うと、まだサイズがでかい。この原 因は何だろう?

次は、コンパイルの工程を視察。

sakae@deb:/tmp/t/app$ ghc -v Main.hs >LOG 2>&1

とりあえず、リンカー工程だけを詳細に吟味する。

gcc -U__i686 '-fuse-ld=gold' -Wl,--no-as-needed
 -o Main -no-pie -fno-PIC -Wl,--gc-sections Main.o
-L/home/sakae/.ghcup/ghc/9.2.8/lib/ghc-9.2.8/integer-gmp-1.1
-L/home/sakae/.cabal/store/ghc-9.2.8/gnuplot-0.5.7-bf80e37560bc62b44b2916a9af2400c891c77b5fcccc8b4af40e3fd0fcb50124/lib
 :
-L/home/sakae/.ghcup/ghc/9.2.8/lib/ghc-9.2.8/time-1.11.1.1
-L/home/sakae/.cabal/store/ghc-9.2.8/cassava-0.5.3.0-683a3a057505a425744abdabfd97b5699293edd9e31a9024d90d05de454d1998/lib
 :
/tmp/ghc1623_0/ghc_5.o
/tmp/ghc1623_0/ghc_8.o
-Wl,-u,base_GHCziTopHandler_runIO_closure
-Wl,-u,base_GHCziTopHandler_runNonIO_closure
-Wl,-u,ghczmprim_GHCziTuple_Z0T_closure
 :
-lHSinteger-gmp-1.1
-lHSgnuplot-0.5.7-bf80e37560bc62b44b2916a9af2400c891c77b5fcccc8b4af40e3fd0fcb50124
-lHSutility-ht-0.0.17-fb35ca0d1f37e548f648e1b67b190acfbb3336d241ec3ef1dde866e682d31cac
 :
-lHSghc-bignum-1.2
-lHSghc-prim-0.8.0
-lHSrts -lCffi -ltinfo -lrt -lutil -ldl -lpthread -lgmp -lc -lm -ldl -lpthread

全部入りって事かな。

to slim

減量は、下記のようにするみたい。要するにスタチィクを止めてダイナミック にするんだな。

ghc -O2 --make -dynamic A.hs
cabal install opengl --enable-shared --reinstall    

やってみる。

sakae@deb:/tmp/t/app$ ghc -dynamic Main.hs
sakae@deb:/tmp/t/app$ ls -s Main
24 Main*
sakae@deb:/tmp/t/app$ ldd Main
        linux-gate.so.1 (0xb7f39000)
        libHSinteger-gmp-1.1-ghc9.2.8.so => /home/sakae/.ghcup/ghc/9.2.8/lib/ghc-9.2.8/integer-gmp-1.1/libHSinteger-gmp-1.1-ghc9.2.8.so (0xb7f22000)
        :
        libHSrts-ghc9.2.8.so => /home/sakae/.ghcup/ghc/9.2.8/lib/ghc-9.2.8/rts/libHSrts-ghc9.2.8.so (0xb0a73000)
        libffi.so.7 => /home/sakae/.ghcup/ghc/9.2.8/lib/ghc-9.2.8/rts/libffi.so.7 (0xb0a67000)
        libtinfo.so.6 => /lib/i386-linux-gnu/libtinfo.so.6 (0xb0a28000)
        librt.so.1 => /lib/i386-linux-gnu/librt.so.1 (0xb0a1c000)
        libutil.so.1 => /lib/i386-linux-gnu/libutil.so.1 (0xb0a17000)
        libdl.so.2 => /lib/i386-linux-gnu/libdl.so.2 (0xb0a11000)
        libpthread.so.0 => /lib/i386-linux-gnu/libpthread.so.0 (0xb09ef000)
        libgmp.so.10 => /lib/i386-linux-gnu/libgmp.so.10 (0xb095f000)
        libc.so.6 => /lib/i386-linux-gnu/libc.so.6 (0xb0777000)
        libm.so.6 => /lib/i386-linux-gnu/libm.so.6 (0xb0673000)
        libtinfo.so.5 => /lib/i386-linux-gnu/libtinfo.so.5 (0xb064b000)
        /lib/ld-linux.so.2 (0xb7f3b000)

62個もライブラリィーが外側から注入されるようになりました。そのかわり本 体は、痩せましたとな。副作用で、gdb用の情報も削ぎ落ちています。

other sample

前回、作成したもの

[sakae@deb bin]$ pwd
/home/sakae/.cabal/store/ghc-9.2.8/gnuplot-0.5.7-e-gnuplot-demo-1a6c38fa6bd0065d0296a0ee39e0b39d4c97c15b30f98d98a559d573b313d4a3/bin
[sakae@deb bin]$ ls -sk gnuplot-demo
4884 gnuplot-demo*

作り直してみる。

[sakae@deb gnuplot-0.5.7]$ cabal install --enable-shared -fbuildExamples gnuplot --overwrite-policy=always --reinstall

サイズが縮小しないなあ。

[sakae@deb bin]$ pwd
/home/sakae/.cabal/store/ghc-9.2.8/gnuplot-0.5.7-e-gnuplot-demo-52e889865f4b093d481d48d2631b0b133eb826a03a816c148bd1e031bc8e9fb4/bin
[sakae@deb bin]$ ls -s gnuplot-demo
4884 gnuplot-demo*

キャッシュをクリアしても変化なし。もっと根本のパッケージからダイナミッ ク形式にしていかないと駄目なのかな。だとしたら、とんでもなく大変だな。 すごすご撤退する。

at 64Bit

翌日、再チャレンジの闘志が湧いた。場所を変更。ArchLinuxにする。壊して も実害が無いから。現在の状態。

[sakae@arch bin]$ pwd
/home/sakae/.cabal/store/ghc-9.2.8/gnuplot-0.5.7-e-gnuplot-demo-ec4be8c7629cbe990edcc410d2225b4e87c783bb3185bfb808ab398b702be62f/bin
[sakae@arch bin]$ ls -l gnuplot-demo
-rwxr-xr-x 1 sakae sakae 5809056 Jun 16 15:39 gnuplot-demo*
[sakae@arch bin]$ ldd ./gnuplot-demo
        linux-vdso.so.1 (0x00007ffdca216000)
        libgmp.so.10 => /usr/lib/libgmp.so.10 (0x00007fab86f03000)
        libc.so.6 => /usr/lib/libc.so.6 (0x00007fab86d19000)
        libm.so.6 => /usr/lib/libm.so.6 (0x00007fab86c2c000)
        /lib64/ld-linux-x86-64.so.2 => /usr/lib64/ld-linux-x86-64.so.2 (0x00007fab86fb7000)

やはりスタティックリンクになってる。壊しちゃえってか、~/.cabalを削除。 おっと、その前に、.cabal/bin/hlintは別の所に退避しとく。そして、作り直 し。

[sakae@arch gnuplot-0.5.7]$ cabal v2-install --enable-executable-dynamic -fbuildExamples gnuplot --overwrite-policy=always

やはり駄目だった。元のやつと同サイズだった。.cabal/config に設定があっ たんで有効にした。executable-dynamic: True

[sakae@arch bin]$ ls -s gnuplot-demo
176 gnuplot-demo*

5.8M有った物が、176kになった。成功である。外を参照するのは、

ldd gnuplot-demo
        linux-vdso.so.1 (0x00007ffc760b3000)
        libHSgnuplot-0.5.7-35e9d90b027708a768a475efa9832c80027beea2c362a5c163bd3e513145d115-ghc9.2.8.so
        libHSutility-ht-0.0.17-78ad26471545f3ffd261d28a407b2d972452b4471341524a552c745b05de2045-ghc9.2.8.so
        libHStemporary-1.3-9e733eee48417193acc9d2d20b439896a372d68079c909ac78e11d753cb182d4-ghc9.2.8.so
        libHSrandom-1.2.1.1-2df66accaaebb5b654c58a9ae9966551d18f5263480aa81ea088180dcc95837e-ghc9.2.8.so
        libHSsplitmix-0.1.0.4-12bd7346cc03dbf9ffc962ef899d74814dbd795ca977a48a4201679382367ced-ghc9.2.8.so
        libHSexceptions-0.10.4-ghc9.2.8.so
        libHSstm-2.5.0.2-ghc9.2.8.so
        libHSmtl-2.2.2-ghc9.2.8.so
	:

ファイル名が鬼のように長い(そんな訳で、=> の左側だけを表示した)。64文字のIDが目立つな。おまけにghcのヴァージョ ンまで埋め込まれている。全リンク数は38個だったよ。

こちらは、gnuplot-lib 格納場所の中身。

[sakae@arch lib]$ pwd
/home/sakae/.cabal/store/ghc-9.2.8/gnuplot-0.5.7-35e9d90b027708a768a475efa9832c80027beea2c362a5c163bd3e513145d115/lib
[sakae@arch lib]$ ls -s lib*
1816 libHSgnuplot-0.5.7-35e9d90b027708a768a475efa9832c80027beea2c362a5c163bd3e513145d115.a
1632 libHSgnuplot-0.5.7-35e9d90b027708a768a475efa9832c80027beea2c362a5c163bd3e513145d115-ghc9.2.8.so*

sha256

64文字のIDと言ったら、あれしかないな。SHA256

唯一無二を求めると、これぐらいは欲しいと思ったんでしょう。これを使って 真贋の判定をやる風でもないので、オイラーに取っては迷惑このうえ ない。これらのIDを見付けて "SHA256" ぐらいの文字列に変換するフィルターを発 明したいぞ。sed 's/hexDigit x 64/SHA256/g' ぐらいな表現。

資料は、cabal/cabal-installあたりがいいのかな?

import qualified Crypto.Hash.SHA256 as SHA256

    sha256parsed :: Either String BS.ByteString
    sha256parsed = explicitEitherParsec fragmentParser uriFrag

    fragmentParser = do
      _ <- P.string "#sha256="
      str <- some P.hexDigit
      let bs = Base16.decode (BS8.pack str)
#if MIN_VERSION_base16_bytestring(1,0,0)
      either fail return bs
#else
      return (fst bs)

パーサーが登場ですか。64個のHex文字って、どういう風にパースするんだろ う。そういう個々の事より、全体的な構成方法だな。識者に質問してみよう。それまで、逃げ道を探して、 コードを書いてみる。って、ghciでポチポチしてたのを纏めただけ。

import Data.Char(isHexDigit)
import Data.List(group)

sx = "libHSgnuplot-0.5.7-35e9d90b027708a768a475efa9832c80027beea2c362a5c163bd3e513145d115-ghc9.2.8.so"

yn = group $ map isHexDigit sx
gr = map length yn
(be, af) =  break (==64) gr
out = take (sum be) sx ++ "____" ++ drop (sum be + 64) sx

hex文字かどうか(True/False)のリストを作ってグループ化しとく。グループ 化されたリストの長さを求める。それを64の所で 分割。

λ>  gr
[2,1,10,1,1,1,1,1,1,64,3,2,1,1,1,1,3]
λ>  be
[2,1,10,1,1,1,1,1,1]
λ>  af
[64,3,2,1,1,1,1,3]

後は takeとdropで切り貼り。中央のsha256跡地は、"__"の方が、感じが出 てて良かった。

λ> out
"libHSgnuplot-0.5.7-____-ghc9.2.8.so"

lddの出力だと、2箇所にsha256が表われるので、2回変換しないと駄目だ。早 くこういう制限を排除したい。やっぱりパーサー様が理想なんだろうね。

with gdb

ghc 9.7.20230607 が有るので、gdbしてみた。

(gdb) bt
#0  0x0835e310 in rts_evalIO ()
#1  0x083314ba in hs_exit_.part ()
#2  0x08331c1e in shutdownHaskellAndExit ()
#3  0x0833101b in hs_main ()
#4  0x0805005c in main ()

何故かソースがその場に出てこない。しょうがないので、手動で参照。

rts/RtsMain.c

// The rts entry point from a compiled program using a Haskell main
// function.  This gets called from a tiny main function generated by
// GHC and linked into each compiled Haskell program that uses a
// Haskell main function.

int hs_main ( int argc, char *argv[],       // program args
              StgClosure *main_closure,     // closure for Main.main
              RtsConfig rts_config)         // RTS configuration

Main.mainをローダーとする、haskell実行システムなんだな。

GHC workshop

GHC devlopper workshop

スイスのお花畑の所でGHCの合宿、楽しそうだな。WASMが流行っぽい。 参加費 学生: 40US$ 個人: 400US$ 企業人: 1200US$ 貧乏人は、ご相談ください。場合によっては、料金が減額または免除されます。 rubyの場合は、どうだった?

オイラーは、そんなのより、こちらに興味あり。

GHC Runtime System (RTS)

GHC packaging systemp (cabal, stack ..)

Type check GHCのレジェンド登壇

スイスで花畑しなくても、国内で楽しい事あるよ。

Template Haskellでコンパイル時にFizzBuzz


This year's Index

Home