この章では、効率のよいドライバを書くための短い概要を紹介します。この章は、すでにドライバを良く理解している人を対象としています。
ランタイムシステムはドライバ内のコードを実行する前はいつも、何かしらのロックをかけます。
デフォルトでは、ドライバのレベルでロックがかけられます。そのため、1つのドライバでいくつかのポートを開いていたとすると、同時に2つ以上のポートに対する処理を実行することはできません。
それぞれのポートごとにロックをかけられるように、設定することもできます。
もしドライバが関数的に使われる(状態をもたず、重い計算を行って返すだけ)の場合には、あらかじめポートに名前をつけてオープンしておいて、次のようなスケジュールIDに基づいてポートが選ばれるようにすることができます。
-define(PORT_NAMES(),
{some_driver_01, some_driver_02, some_driver_03, some_driver_04,
some_driver_05, some_driver_06, some_driver_07, some_driver_08,
some_driver_09, some_driver_10, some_driver_11, some_driver_12,
some_driver_13, some_driver_14, some_driver_15, some_driver_16}).
client_port() ->
element(erlang:system_info(scheduler_id) rem tuple_size(?PORT_NAMES()) + 1,
?PORT_NAMES()).
この場合は、16以上のスケジューラがない限りは、ドライバに対するポートのロックは行われません。
バイナリ呼び出し時にバイナリのコピーを避ける方法は2つあります。
もし、 port_control/3() の Data 引き数がバイナリであった場合、ドライバはバイナリの内容のを指すポイントを渡すため、バイナリのコピーは行われません。もし、 Data 引き数が iolist であった場合には(バイナリのリストとリスト)、 iolist 内のすべてのバイナリはコピーされます。
そのため、もし既存のバイナリと追加のデータを、コピーせずに一緒に渡したいと思うのであれば、 port_control/3() を2回呼び出す必要があります。しかし、この方法は1つのプロセスだけがそのポートにアクセスしているときしか正常に動作しません。その2回の呼び出しに間に他のプロセスが呼び出しをするかもしれないからです。
バイナリのコピーを防ぐもう1つの方法は、 output() コールバックの代わりに、 outputv() コールバックを実装する方法です。もしドライバが outputv() コールバックを実装していれば、 port_command/2() の Data 引き数に iolist に渡されて、その中にrefcバイナリが含まれていた場合、参照だけが渡されます。
ランタイムシステムは、64バイトまでのバイナリは、ヒープバイナリとして扱うことができます。メッセージに載せて送られる時は、常にコピーされます。送られない場合よりもメモリ消費は負えますが、ガベージコレクションは軽くなります。
もし返ってくるバイナリがいつも小さいということが分かっているのであれば、事前にアロケートされたバイナリを必要としないAPI呼び出しを使用すべきでしょう。 driver_output() の代わりに、 実行時にヒープバイナリを作れる、 ERL_DRV_BUF2BINARY フォーマットを指定した driver_output_term() を使ってください。
ドライバ側から、巨大なバイナリをErlangのプロセスに返すときに、データのコピーを避けたいのであれば、ドライバー側でバイナリのアロケートを行い、それを何らかの方法でErlangプロセスに送る必要があります。
バイナリの割り当てには、 driver_alloc_binary() を使用してください。
driver_alloc_binary() を使って作られたバイナリの送信方法には何通りかあります。
PORT_CONTROL_FLAG_BINARY を付けて set_port_control() が制御コールバックから呼ばれていると、バイナリをかえすことができます。
一つののバイナリは、 driver_output_binary() を使って送信することができます。
driver_output_term() 、もしくは driver_send_term() を使うと、Erlangの項にバイナリを含めることができます。