Streamについて


投稿ツリー



前の投稿 - 次の投稿 | 親投稿 - 子投稿.1 .2 | 投稿日時 2010/7/25 2:27
SHOO  管理人   投稿数: 658
Handleを定義するためのモジュールを試作してみました。
まだ色々と足りない部分が多いのですが、ご参考までに御覧ください。


http://www.ideone.com/BDA0Q


他に、I/Oならこれが欲しいよ!とか、これ使いにくそう!等の相談は、是非是非是非、気軽に発言してください。私自身、何を実装したら良いのか今イチ分かっていないので…

コードの方は、行数こそ長い((1500行程度))ものの、とりたてて難しい技術は使っていませんので、テンプレートメタプログラミングが出来るのであれば、読み下せるのではないかと思います。

以下説明です。

----

ハンドルは大きく分けて2種類あります。InputHandleとOutputHandleです。
また、オプションによって
バッファ機能付き入力ハンドル(BufferedInputHandle)
バッファ機能付き出力ハンドル(BufferedOutputHandle)
シーク可能ハンドル(Seekable)
の属性が付属する可能性があります。
以下にこれらを判定するためのテンプレートを示します

- isInputHandle
入力ハンドルかどうかを判定します。
入力ハンドルの場合
-- size_t read(ubyte[] dst);
引数により渡されたdstバッファへと入力します。
戻り値は入力したバイト数が返ります。
-- @property const bool eoh();
End Of Handle
ハンドルの終端にたどり着き、これ以上読み込めない場合にはtrueが返る
-- void close();
ハンドルを閉じ、他の場所でオープン可能な状態にします。
上記3つのメソッドが利用可能です。

- isOutputHandle
出力ハンドルかどうかを判定します
出力ハンドルの場合
-- size_t write(in ubyte[] src);
引数により渡されたsrcのバイト列をハンドルへと出力します。
戻り値は出力したバイト数が返ります。
-- @property const bool eoh();
End Of Handle
ハンドルの終端にたどり着き、これ以上読み込めない場合にはtrueが返る
-- void close();
ハンドルを閉じ、他の場所でオープン可能な状態にします。
上記3つのメソッドが利用可能です。

- isBufferedInputHandle
入力ハンドルで、かつバッファ機能が付いているかを判定します。
バッファ機能付き入力ハンドルの場合
-- void fill();
バッファをハンドルからの入力情報で埋め尽くします。
readメソッドから自動的に呼ばれます。
-- @property const const(ubyte)[] buffer();
現在のバッファの中身を返します。
上記2つのメソッドが利用可能です。


- isBufferedOutputHandle
出力ハンドルで、かつバッファ機能が付いているかを判定します。
バッファ機能付き出力ハンドルの場合
-- void flush();
バッファに溜まっているデータを全てHandleへと出力します。

上記1つのメソッドが利用可能です。

- isSeekable
シーク可能なハンドルかどうかを判定します。
シーク可能ハンドルの場合
-- void seek(long pos, bool relative = false);
posの位置へとシークします。 relativeがtrueになっている場合は
現在のシーク位置からの移動量です。falseになっている場合はHandle先頭
位置からの移動量です。
-- @property const ulong size();
Handleで入出力可能な全体のサイズを返す
-- @property const ulong position();
現在のシーク位置を返します。

上記3つのメソッドが利用可能です。

どうしてもインターフェースを使いたい場合のため、各種interfaceとwrap関数を
用意しました。
- InputHandle
- OutputHandle
- BufferedInputHandle
- BufferedOutputHandle
- Seekable
- AnyHandle!Handle wrap(Handle)(Handle handle);
インターフェースを使う場合については、以下のような場合が挙げられます。
#code(d){{{
interface X
{
InputHandle input();
}
class Y: X
{
InputHandle input(){ return wrap(FileHandle("test.dat", "wb")); }
}
class Z: X
{
InputHandle input(){ return wrap(MemoryHandle()); }
}

void foo()
{
X[] x;
x ~= new Y;
x ~= new Z;
}
}}}

汎用バッファ機能付きハンドルとして、入力用と出力用をそれぞれ用意しました
- InputBuffer!Handle
- OutputBuffer!Handle

入出力を指定バイト確実に行うための関数を用意しました。
- void readExact(ubyte[] dst);
- void writeExact(in ubyte[] src);
なお、この関数は元々Handleに実装してある場合
- hasReadExact
- hasWriteExact
のテンプレートがtrueを示し、そちらを利用します。

汎用のByChunkを用意しました
- ChunkReaderRange!Handle
- ChunkReaderRange!Handle byChunk(Handle)(Handle handle, ubyte[] data);
- ChunkReaderRange!Handle byChunk(Handle)(Handle handle, size_t size);
指定バイトづつ読み込みます。

ファイル用、メモリ用のハンドルとして、ハンドルを用意しました
- FileHandle
-- this(string filename);
-- this(string filename, string attrib);
- MemoryHandle
-- this(ref ubyte[] buf);

エンディアンを考慮した出力のために、Rangeを用意しました
- EndianOutBuffer!(Range, Endian)
- EndianOutBuffer!(Range, LittleEndian) leoutbuf(Range)(Range r);
- EndianOutBuffer!(Range, BigEndian) beoutbuf(Range)(Range r);
- EndianOutBuffer!(Range, LittleEndian) byLittleEndian(Handle)(Handle h);
- EndianOutBuffer!(Range, BigEndian) byBigEndian(Handle)(Handle h);
投票数:52 平均点:3.85
返信する

なし Re: Streamについて

msg# 1.6.1.1
前の投稿 - 次の投稿 | 親投稿 - 子投稿.1 | 投稿日時 2010/7/25 14:05 | 最終変更
tama  一人前   投稿数: 111
rsinfuさんがHandle + Portというのを考えておられました(Waveなのでアカウントが必要です).

古いWave(Range I/O)
http://tinyurl.com/25r2dr6
新しいWave(Handle + Port)
http://tinyurl.com/2v9prz2

これはRangeでバッファ?Handleでバッファ?というどっちでも微妙だなぁという問題に対してPortを挟んで解決しています.C stdioとかの微妙さを考えると,この辺システムコールを直接ほげほげしたいなぁと考えていたので,自分としては賛成です.

std.socketに関してAndreiがアクション起こしてくれないので,もう書き直すことで強引に置き換えようと考えていて,その時にこのインターフェイスも提案出来たらなぁとか思ったりもしてます.

ということで,これも交えて考えて見ても面白いんじゃないかと思います.
投票数:17 平均点:5.29
返信する

なし Re: Streamについて

msg# 1.6.1.1.1
前の投稿 - 次の投稿 | 親投稿 - 子投稿.1 | 投稿日時 2010/7/25 23:02
SHOO  管理人   投稿数: 658
拝見しました。

私はバッファリングをHandleで行い、Range対応をフリー関数で行なおうとしましたが、Portみたいなものでもいいかも知れませんね。
ただ、概念が Handle -> Port -> Range の3層に分かれてしまうため、複雑さ(初見での理解のし難さ)も増えてしまいそうなのが難点でしょうか。
また、テキストとバイナリの途中での切り替えも難しそうかもと最後にコメントされていますね。

正直、自分でbyLineみたいなのを真面目に作ったことがないので、バッファにどのような操作が必要なのか分かっていなかったりします。(なので私の実装ではubyte[]に読み込ませるだけです)
ところで、byLineみたいなテキストの取り扱いは、バッファが必須になるのでしょうか?
投票数:17 平均点:5.29
返信する

なし Re: Streamについて

msg# 1.6.1.1.1.1
前の投稿 - 次の投稿 | 親投稿 - 子投稿.1 | 投稿日時 2010/7/26 14:05
rsinfu  新米   投稿数: 4
引用:
私はバッファリングをHandleで行い、Range対応をフリー関数で行なおうとしましたが、Portみたいなものでもいいかも知れませんね。
ただ、概念が Handle -> Port -> Range の3層に分かれてしまうため、複雑さ(初見での理解のし難さ)も増えてしまいそうなのが難点でしょうか。
また、テキストとバイナリの途中での切り替えも難しそうかもと最後にコメントされていますね。


僕も Port の複雑さは嫌いです.
Device -> Reader/Writer という名前なら理解しやすいかなぁと思いますが,根本的に多段なので面倒臭いですね.

ただ,I/O主体がバッファのコントロールを握っていると便利で,
- byLine はバッファの先頭から改行コードを探していって,見つかったところまでのスライスを返すだけで良い
- byChunk はバッファをそのまま返せば良いので,コピーがまったく発生しない
- 大きなデータはバッファリングせず,そのままデバイスに書き込める

…というような,効率面のメリットがあったりします.


引用:
正直、自分でbyLineみたいなのを真面目に作ったことがないので、バッファにどのような操作が必要なのか分かっていなかったりします。(なので私の実装ではubyte[]に読み込ませるだけです)
ところで、byLineみたいなテキストの取り扱いは、バッファが必須になるのでしょうか?


テキストのように「ちまちました」「長さ不定な」ものの読み込みにはバッファ必須だと思います.
アンバッファで行読み込みをすると,1バイトごとにシステムコール (read,recv 等) を呼び出して非効率すぎるんで….
手元にバッファがあれば,単にバッファの先頭から改行コードまでメモリを読むだけで済みます.

テキスト読み込みの Port に dchar[] バッファを持たせたいという部分へのツッコミなら,僕も変かなぁと思い始めてます.
僕にとって「テキストI/O」は文字コード変換をともなうので,バッファ内容の整合性を気にしてdchar型を付けたかったのですが,バッファリングは生の ubyte[] でやった方が柔軟で良さそうです.
必要なときに dchar へ変換するなりした方がまともだなと.
投票数:22 平均点:5.00
返信する

なし Re: Streamについて

msg# 1.6.1.1.1.1.1
前の投稿 - 次の投稿 | 親投稿 - 子投稿なし | 投稿日時 2010/7/26 19:00
SHOO  管理人   投稿数: 658
Portがバッファを持つ場合だとテキストとバイナリの途中での切り替えも難しそう。これは確かにわかる。
#code(d){{{
auto hnd = new FileHandle("x.txt");

// ↓テキスト入力用にバッファを確保
auto tip = new TextInputPort(hnd);

/* テキストの入力処理 */

// ↓バイナリ入力用にバッファを確保したいが、既にテキスト入力のバッファを埋めるために
// ↓読み込みが行われているため、バッファの整合性が取れなくなる
auto bip = new BinaryInputPort(hnd);

/* バイナリの入力処理 */

}}}
Handleがバッファを持つ場合だと
#code(d){{{
// ↓ハンドルでバッファを確保
auto hnd = new InputBuffer(new FileHandle("x.txt"));

// ↓ハンドルのバッファを使って、テキスト入力
// ↓※ただし、TextInputPortはBufferedなハンドルしか受け付けない
auto tip = new TextInputPort(hnd);

/* テキストの入力処理 */

// ↓ハンドル自体がバッファを持っているため、バッファの整合性に問題はない。
// ↓しかし、本来テキストの部分なのにバイナリとして読んでしまう…など、
// ↓「ダメなことは出来ない」というやり方には反する。
// ↓ダメなこともできてしまうため、利用者が気をつけなければならない。
auto bip = new BinaryInputPort(hnd);

/* バイナリの入力処理 */

}}}

とまあ、ダメなこともできてしまうインターフェースになってしまうのが問題といったところでしょうか。

効率面では、
>
- byLine はバッファの先頭から改行コードを探していって,見つかったところまでのスライスを返すだけで良い
- byChunk はバッファをそのまま返せば良いので,コピーがまったく発生しない
<
これは、BufferedHandleからバッファの内容へ直接アクセス可能なインターフェースを提供することで解決しそうです。
現在私の実装で言う、BufferedHandle.bufferがそれにあたります。
>
- 大きなデータはバッファリングせず,そのままデバイスに書き込める
<
これは、BufferedInput.readの実装や、byChunk等Rangeの実装によって性能が変わりそうですね。isBufferedInput!Handleがtrueを返すときにはバッファを考慮した処理を…など、効率的にする方法はありそうです。

>テキストのように「ちまちました」「長さ不定な」ものの読み込みにはバッファ必須だと思います.
アンバッファで行読み込みをすると,1バイトごとにシステムコール (read,recv 等) を呼び出して非効率すぎるんで….
手元にバッファがあれば,単にバッファの先頭から改行コードまでメモリを読むだけで済みます.
<
まさに私がMLで話題に出した時のコードをデジャヴしたw
改行コードが3種類あるとか蠱毒法で…というのはまた別のお話。

>テキスト読み込みの Port に dchar[] バッファを持たせたいという部分へのツッコミなら,僕も変かなぁと思い始めてます.
僕にとって「テキストI/O」は文字コード変換をともなうので,バッファ内容の整合性を気にしてdchar型を付けたかったのですが,バッファリングは生の ubyte[] でやった方が柔軟で良さそうです.
必要なときに dchar へ変換するなりした方がまともだなと.
<
文字コードの変換って、FilterRange見たいの作って出来ないかなぁ…
#code(d){{{
auto rngLines = byLine(hnd);
auto rngDLines = convDstring(rngLines);
}}}
投票数:38 平均点:4.47
返信する

なし Re: Streamについて

msg# 1.6.1.2
前の投稿 - 次の投稿 | 親投稿 - 子投稿なし | 投稿日時 2010/7/26 22:58
SHOO  管理人   投稿数: 658
Twitter上でも指摘を受けましたが、私の実装したバッファ云々はまだまだ洗練されているインターフェースではありませんので、そのへんについても十分検討の余地があると思います。
そもそも、バッファリングしない必要があるのか否か…速度面以外ではなにかあるでしょうか?

時に、他の言語ではどうなっているのでしょう?

C++ではstreambufなるものがあった気がします。streambufは今回のHandleに近いものがある気がします。iostreamがPortにそうとすうる概念で、ostream_iteratorとかがRangeに相当する概念でしょう。
……Handle == Buffer!?
でも、なんだかんだで詳しくはわかりません。ぶっちゃけ拡張しようとしたことがあったけれど、挫折しました。資料も殆ど無かった気がします。

C言語ではFILE*の内部でバッファリングしますね。

Tangoでは、ConduitがHandleに対応する概念で、Port+RangeがStreamという感じでしょうか。バッファはConduitに持たせています。たしか。 …ただし、うっすら残る記憶の中での話なので、最近どうなっているかは知りません。

C#, Java, Ruby, Python等、他の言語は知りません。
投票数:24 平均点:4.17
返信する
前の投稿 - 次の投稿 | 親投稿 - 子投稿なし | 投稿日時 2010/8/29 2:46
SHOO  管理人   投稿数: 658
現在、Andreiが[http://lists.puremagic.com/pipermail/phobos/2010-August/002140.html Improvement of stream]を掘り返してきたため、再び議論が行われる可能性があります。

> 改めて議論しようと思ってるんだけど、新しいコードある?

ちなみに、私はこれに対して、

> 新しいコードはここにあります
> http://ideone.com/YuJ99
>
> 私はあまりこのコードに積極的じゃないので、みなさん自分でいじくりまわして熟考してみてくださいね。

と返信しておきました。
投票数:20 平均点:4.50
返信する
前の投稿 - 次の投稿 | 親投稿 - 子投稿なし | 投稿日時 2010/10/5 1:32 | 最終変更
SHOO  管理人   投稿数: 658
途中経過

[https://wave.google.com/wave/waveref/googlewave.com/w+Z3c0SJQhA/~/conv+root/b+Z3c0SJQhB rsinfuさん・s50さん・私(SHOO)のwaveでの議論]
[https://wave.google.com/wave/waveref/googlewave.com/w+WLAND1-OE/~/conv+root/b+WLAND1-OF rsinfuさん・s50さん・私(SHOO)のwaveでの議論2]
[http://www.slideshare.net/monoshoo/d-io 私(SHOO)の個人的まとめ]

#video(slideboom,217885);
その他Twitterなんかで議論してましたが、最後のスライドにだいたい要点は書いたと思います。
投票数:49 平均点:5.10
返信する

このトピックに投稿する

題名
ゲスト名
投稿本文

  条件検索へ


メインメニュー

ログイン

ユーザー名:


パスワード:





パスワード紛失  |新規登録

Menu