[ < ] [ > ]   [ << ] [] [ >> ]         [冒頭] [目次] [見出し] [ ? ]

15. バイトコンパイル

Emacs Lispには, Lispで書いた関数を より効率よく実行可能なバイトコード(byte-code)と呼ばれる 特別な表現に変換するコンパイラ(compiler)があります. コンパイラはLispの関数定義をバイトコードで置き換えます. バイトコード関数を呼び出すと, バイトコードインタープリタ(byte-code interpreter)が その定義を評価します.

(真のコンパイル済みコードのように)計算機ハードウェアが直接実行するかわりに, バイトコードインタープリタがバイトコンパイル済みのコードを評価するので, バイトコードは, 再コンパイルせずに計算機から計算機に移せます. しかしながら, 真のコンパイル済みコードほど速くはありません.

EmacsバイトコンパイラがLispファイルをコンパイルするときには, ‘--unibyte’を指定してEmacsを起動したとしても, ファイルで特に指定しなければ, つねにファイルをマルチバイトテキストとして読みます. コンパイルしても, コンパイルせずに同じファイルを実行した場合と同じ結果を 得るようにするためです. See section 非ASCII文字のロード.

一般に, Emacsの任意の版は, それよりまえの版でバイトコンパイルしたコードを 実行できますが, その逆は真ではありません. Emacs 19.29では互換性のない大きな変更を行いましたから, それ以降の版でコンパイルしたファイルは, 特別なオプションを指定しない限り, それ以前の版ではまったく動きません. さらに, Emacs 19.29では, キーボード文字の修飾ビットを変更しました. その結果, 19.29よりまえの版でコンパイルしたファイルは, 修飾ビットを含む文字定数を使っているとそれ以降の版では動作しません.

バイトコンパイル中に生起するエラーについては, See section コンパイル時の問題のデバッグ.


[ < ] [ > ]   [ << ] [] [ >> ]         [冒頭] [目次] [見出し] [ ? ]

15.1 バイトコンパイルコードの性能

バイトコンパイルした関数は, Cで書いた基本関数ほど効率よくはありませんが, Lispで書いた版よりはよほど速く動きます. 例を示しましょう.

 
(defun silly-loop (n)
  "Return time before and after N iterations of a loop."
  (let ((t1 (current-time-string)))
    (while (> (setq n (1- n)) 
              0))
    (list t1 (current-time-string))))
⇒ silly-loop
(silly-loop 100000)
⇒ ("Fri Mar 18 17:25:57 1994"
    "Fri Mar 18 17:26:28 1994")  ; 31秒
(byte-compile 'silly-loop)
⇒ [コンパイルしたコードは省略]
(silly-loop 100000)
⇒ ("Fri Mar 18 17:26:52 1994"
    "Fri Mar 18 17:26:58 1994")  ; 6秒

この例では, 解釈実行するコードでは実行に31秒必要でしたが, バイトコンパイルしたコードでは6秒でした. この結果は代表的なのもですが, 実際の結果は大きく変動します.


[ < ] [ > ]   [ << ] [] [ >> ]         [冒頭] [目次] [見出し] [ ? ]

15.2 コンパイル関数

関数byte-compileで, 個々の関数定義やマクロ定義をバイトコンパイルできます. byte-compile-fileで1つのファイル全体をコンパイルしたり, byte-recompile-directorybatch-byte-compileで 複数個のファイルをコンパイルできます.

バイトコンパイラは, 各ファイルに対するエラーメッセージや警告メッセージを ‘*Compile-Log*’と呼ばれるバッファに出力します. 読者のプログラムに関してここに報告されたことがらは, 問題点を指摘しますが, 必ずしもエラーとは限りません.

バイトコンパイルする可能性のあるファイルにマクロ呼び出しを書くときには 注意してください. マクロ呼び出しはコンパイル時に展開されるので, 正しくコンパイルするためにはマクロは定義済みである必要があります. 詳しくは, See section マクロとバイトコンパイル.

通常, ファイルをコンパイルしてもファイルの内容を評価したり, ファイルをロードしません. しかし, ファイルのトップレベルに書いたrequireは実行します. コンパイル時に必要なマクロ定義が存在することを保証する1つの方法は, それらを定義するファイルを要求(require)することです (see section 機能). コンパイルしたプログラムを実行するときに マクロ定義ファイルのロードを防ぐには, requireの呼び出しの周りにeval-when-compileを書きます (see section コンパイル時の評価).

Function: byte-compile symbol

この関数は, symbolの関数定義をバイトコンパイルし, 以前の定義をコンパイルしたもので置き換える. symbolの関数定義は, 関数の実際のコードであること. つまり, コンパイラは, 別のシンボルへの間接参照を辿らない. byte-compileは, symbolのコンパイル済みの新たな定義を返す.

symbolの定義がバイトコード関数オブジェクトであると, byte-compileはなにもせずにnilを返す. Lispはどんなシンボルに対しても関数定義を1つだけ記録するので, それがすでにコンパイル済みであると, コンパイルまえのコードはどこにもないのである. したがって, 『同じ定義をコンパイルし直す』方法はない.

 
(defun factorial (integer)
  "Compute factorial of INTEGER."
  (if (= 1 integer) 1
    (* integer (factorial (1- integer)))))
⇒ factorial
(byte-compile 'factorial)
⇒
#[(integer)
  "^H\301U\203^H^@\301\207\302^H\303^HS!\"\207"
  [integer 1 * factorial]
  4 "Compute factorial of INTEGER."]

結果は, バイトコード関数オブジェクトである. この文字列には実際のバイトコードが入っている. その各文字は, 命令や命令のオペランドである. ベクトルには, 特別な命令に符号化される特定の基本関数を除いて, 関数が使うすべての定数, 変数名, 関数名が入っている.

コマンド: compile-defun

このコマンドはポイントを含むdefunを読み取り, それをコンパイルして, 結果を評価する. 実際に関数定義であるdefunでこのコマンドを使うと, その関数をコンパイルしたものをインストールすることになる.

コマンド: byte-compile-file filename

この関数は, filenameという名前のLispコードのファイルを コンパイルしバイトコードのファイルにする. 出力ファイルの名前は, 接頭辞‘.el’を‘.elc’に換えて作る. filenameが‘.el’で終っていないときには, filenameの末尾に‘.elc’を付加する.

入力ファイルから一度に1つずつフォームを読みながらコンパイルを行う. それが関数定義やマクロ定義であると, コンパイルした関数定義やマクロ定義を書き出す. 他のフォームは一塊にして, 各塊をコンパイルして書き出し, ファイルを読むとコンパイルしたコードが実行されるようにする. 入力ファイルを読むときにすべてのコメントを捨てる.

このコマンドはtを返す. 対話的に呼び出すとファイル名を問い合わせる.

 
% ls -l push*
-rw-r--r--  1 lewis     791 Oct  5 20:31 push.el
(byte-compile-file "~/emacs/push.el")
     ⇒ t
% ls -l push*
-rw-r--r--  1 lewis     791 Oct  5 20:31 push.el
-rw-rw-rw-  1 lewis     638 Oct  8 20:25 push.elc
コマンド: byte-recompile-directory directory flag

この関数は, directoryにある再コンパイルが必要な 個々の‘.el’ファイルを再コンパイルする. ファイルを再コンパイルする必要があるのは, ‘.elc’ファイルが存在しても‘.el’ファイルより古い場合である.

.el’ファイルに対応する‘.elc’ファイルが存在しない場合には, flagが動作を指示する. それがnilであると, そのようなファイルは無視する. nil以外であると, そのような各ファイルをコンパイルするかどうか ユーザーに問い合わせる.

このコマンドの戻り値は予測できない.

Function: batch-byte-compile

この関数は, コマンド行に指定したファイル群に対して byte-compile-fileを実行する. この関数はEmacsをバッチモードで実行しているときにだけ使うこと. 完了するとEmacsを終了するからである. 1つのファイルでエラーが発生しても, 後続のファイルの処理には影響しないが, エラーを起こしたファイルに対する出力ファイルは生成せず, Emacsのプロセスは0以外の状態コードで終了する.

 
% emacs -batch -f batch-byte-compile *.el
Function: byte-code code-string data-vector max-stack

この関数はバイトコードを実際に解釈実行する. バイトコンパイルした関数は, 実際には, byte-codeを呼び出すような本体として定義される. この関数を読者自身で呼び出さないこと. この関数の正しい呼び出しを生成する方法はバイトコンパイラだけが知っている.

Emacs 18版では, バイトコードは関数byte-codeをつねに呼び出すことで 実行していた. 現在では, バイトコード関数オブジェクトの一部としてバイトコードを実行するのが 普通であり, byte-codeを明示的に呼び出すことは稀である.


[ < ] [ > ]   [ << ] [] [ >> ]         [冒頭] [目次] [見出し] [ ? ]

15.3 説明文字列とコンパイル

バイトコンパイルしたファイルからロードした関数や変数では, それらの説明文字列は, 必要に応じてそのファイルを動的に参照します. これはEmacs内のメモリを節約しロード処理も速くなります. というのは, ファイルのロード処理で説明文字列を処理する必要がないからです. 説明文字列を実際に参照するのは遅くなりますが, 普通, ユーザーをいらいらさせるほとではありません.

説明文字列を動的に参照することには欠点があります.

読者のサイトでEmacsを通常の手順でインストールした場合には, これらの問題は普通起こらないはずです. 新版のインストールには別のディレクトリを使いますから, 旧版をインストールしてある限り, そのファイル群は意図した場所に 無変更で残っているはずです.

しかしながら, 読者自身がEmacsを構築して, 構築したディレクトリからEmacsを使う場合, Lispファイルを編集して再コンパイルすると, しばしばこの問題を経験するでしょう. そのような場合には, 再コンパイルしたあとでファイルを再ロードすれば 問題を解決できます.

旧版ではこの機能を使えないので, Emacsの(19.29以降の)最近の版でバイトコンパイルしたファイルは 旧版ではロードできません. byte-compile-dynamic-docstringsnilを設定すれば, コンパイル時にこの機能をオフにできます. Emacsの旧版にロードできるようにファイルをコンパイルできるのです. すべてのファイルをこのようにコンパイルしたり, あるいは, この変数をファイルにローカルな束縛に指定して1つのソースファイルだけを このようにコンパイルしたりもできます. そのようにする1つの方法は, つぎの文字列をファイルの先頭行に追加することです.

 
-*-byte-compile-dynamic-docstrings: nil;-*-
Variable: byte-compile-dynamic-docstrings

これがnil以外であると, バイトコンパイラは, 説明文字列を動的にロードするように設定した コンパイル済みファイルを生成する.

説明文字列を動的に扱う場合, コンパイル済みのファイルではLispリーダの特別な構文‘#@count’を 使います. この構文は後続のcount文字を読み飛ばします. また, ‘#$’という構文も使います. これは, 『文字列としてのこのファイルの名前』を表します. Lispのソースファイルでは, これらの構文を使わないのが最良です. これらは人が読むファイル向けに設計したものではないからです.


[ < ] [ > ]   [ << ] [] [ >> ]         [冒頭] [目次] [見出し] [ ? ]

15.4 個別関数の動的ロード

ファイルをコンパイルするとき, 動的関数ロード(dynamic function loading, 遅延ロード(lazy loading)ともいう)機能を指定できます. 動的関数ロードでは, ロードするときにファイル内の関数定義をすべて 読むわけではありません. そのかわりに, 各関数定義には, そのファイルを指す埋め草が入っています. それぞれの関数を初めて呼び出したときに, その完全な定義をファイルから読み取り, 埋め草を置き換えます.

動的関数ロードの利点は, ファイルをロードするよりかなり速いことです. ユーザーが呼び出せる数多くの別々の関数を収めたファイルにおいては, それらの1つだけを使って残りのものを使わないのであれば, これは有利なことです. キーボードコマンドを提供する特別なモードには, しばしばこのような使い方のパターンがあります. ユーザーがモードを起動しても, 提供するコマンドの一部しか使わないのです.

動的関数ロードの機能には, ある種の欠点もあります.

Emacsのファイル群をインストールした普通の状況では, このような問題は起きないはずです. しかし, Lispファイルを読者が変更すると起こりえます. これらの問題を回避するもっとも簡単な方法は, 再コンパイルするたびに新たにコンパイルしたファイルを ただちに再ロードすることです.

バイトコンパイラは, コンパイル時に変数byte-compile-dynamicnil以外であれば, 動的関数ロードの機能を使います. 動的ロードは特定のファイルで必要なだけですから, この変数をグローバルに設定しないでください. そのかわりにファイルにローカルな変数束縛を使って 特定のソースファイルだけでこの機能をオンにします. たとえば, ソースファイルの先頭行につぎのテキストを書けば, そのようにできます.

 
-*-byte-compile-dynamic: t;-*-
Variable: byte-compile-dynamic

これがnil以外であると, バイトコンパイラは, 動的関数ロードを使うように設定した コンパイル済みのファイルを生成する.

Function: fetch-bytecode function

functionを完全にロードしていないと, バイトコンパイルしたファイルからただちにfunctionの定義をロードする. 引数functionは, バイトコード関数オブジェクトか関数名である.


[ < ] [ > ]   [ << ] [] [ >> ]         [冒頭] [目次] [見出し] [ ? ]

15.5 コンパイル時の評価

プログラムのコンパイル時に評価されるようなコードを書くための機能です.

Special Form: eval-and-compile body

このフォームは, コンパイルしたり実行したり (コンパイルしてあってもしてなくても)するときに bodyを評価するように印を付ける.

bodyを別のファイルに収め, そのファイルをrequireで参照しても 同じ結果を得ることができる. bodyが大きい場合には, そのほうが好ましい.

Special Form: eval-when-compile body

このフォームは, コンパイルしたプログラムをロードするときではなく, プログラムのコンパイル時にbodyを評価するように印を付ける. コンパイラが評価した結果は, コンパイルしたプログラム内に定数として現れる. ソースファイルをコンパイルせずにロードすると, bodyを普通どおり評価する.

Common Lispに関した注意: トップレベルでは, Common Lispの(eval-when (compile eval) …)の常套句に似ている. それ以外の箇所では, Common Lispの‘#.’リーダマクロは (解釈実行時ではなければ)eval-when-compileが行うことに近い.


[ < ] [ > ]   [ << ] [] [ >> ]         [冒頭] [目次] [見出し] [ ? ]

15.6 バイトコード関数オブジェクト

バイトコンパイルした関数は, 特別なデータ型, バイトコード関数オブジェクト(byte-code function objects)です.

内部的には, バイトコード関数オブジェクトはベクトルによく似ています. しかし, 評価時にこのデータ型が呼び出すべき関数として現れると, 特別に扱います. バイトコード関数オブジェクトの表示表現はベクトルに似ていますが, 開き角括弧‘[’のまえに余分に‘#’が付きます.

バイトコード関数オブジェクトには, 少なくとも4つの要素が必要です. 最大個数に制限はありませんが, 最初の6つ個の要素にだけ 普通の用途があります. つぎのとおりです.

引数リスト

引数シンボルのリスト.

バイトコード

バイトコード命令を収めた文字列.

定数群

バイトコードが参照するLispオブジェクトのベクトル. 関数名や変数名として使われるシンボルを含む.

スタックサイズ

この関数に必要なスタックサイズの最大値.

説明文字列

(あれば)説明文字列. さもなければnil. 説明文字列がファイルに収めてあれば, 値は数かリストである. 実際の説明文字列を取得するには関数documentationを使う (see section 説明文字列の参照).

対話指定

(あれば)対話指定. これは文字列かLisp式. 対話的でない関数ではnil.

バイトコード関数オブジェクトの例を表示表現でつぎに示します.

 
#[(&optional arg)
  "^H\204^F^@\301^P\302^H[!\207"
  [arg 1 forward-sexp]
  2
  254435
  "p"]

バイトコードオブジェクトを作る基本的な方法は, make-byte-codeを使うことです.

Function: make-byte-code &rest elements

この関数は, elementsを要素とする バイトコード関数オブジェクトを作成し返す.

バイトコード関数の要素を自分で作ったりしないでください. それらに整合性がないと, その関数を呼び出すとEmacsがクラッシュすることもあります. これらのオブジェクトの作成は, バイトコンパイラに任せるべきです. バイトコンパイラは整合した要素を作成します(と期待する).

バイトコードオブジェクトの要素はarefで参照できます. 同じ要素群のベクトルをvconcatで作ることもできます.



[ << ] [ >> ]           [冒頭] [目次] [見出し] [ ? ]

この文書は新堂 安孝によって2009年9月22日texi2html 1.82を用いて生成されました。