.NET FrameworkにはVersion 1.0からSocketクラスがあり、APM; Asynchronous Programming Modelと呼ばれるBegin / End系メソッドが用意されています。しかし実際にはIOの度にIAsyncResultオブジェクトを作成する必要があり、ハイパフォーマンスなアプリケーションは実現しづらいものでした。
そのため、Version 2.0 SP1にてSocketAsyncEventArgsクラス及びAsync系のメソッドが新規に追加されました。こちらは内部状態を持つSocketAsyncEventArgsクラスを再利用することで効率の良い非同期処理が行えるものとなっています。なお、Version 4.5で導入された非同期処理とメソッド名の命名規則が一致していますが全くの別物となっています。
これをF#で扱えないものかと検索したところF#-friendly SocketAsyncEventArgsを見つけました。ただし、残念なことにSocketAsyncEventArgsクラスの設計思想を意識されておらず、毎回SocketAsyncEventArgsオブジェクトを再作成するだけの単なるwrapperでしかありませんでした。さらに言えばF#には非同期ワークフローもありますからこちらも利用したいところです。
仕方がないので自作してみました。
書いただけでまだ使っていないので動くかわかりません。
蛇足ですが、Socketクラスは内部でWinsockを使っていますが、このWinsockの機能の一つにaccept()で接続を受け付けると同時にrecv()を行うことができます。また対称にconnect()と同時にsend()もできます。こうすることでHTTPなど一般的なプロトコルでリクエストの送受信ができ、システムコール回数を減らし、システムの応答性能が向上します。Socketクラスはこの機能に対応しているため、今回の拡張メソッドにも含めています。
2014年12月24日水曜日
F#における非同期Socket
2014年9月10日水曜日
WPF ListView (GridView) のソート
WPFにはListViewのGridViewモードとDataGridの2つのコントロールで表形式の表示が行えます。DataGridの方はソート機能が組み込まれていますが、ListViewの方は自前でソートコードを記述する必要があります。MSDNにも方法 : ヘッダーがクリックされたときに GridView 列を並べ替えるという記事が用意されていたりしますがいまいちパッとしません。 そこで簡単に扱えるようにライブラリ化しました。 使い方は
<ListView ItemsSource="{Binding SelectedValue.Files, ElementName=tree}" xmlns:v="clr-namespace:Sayuri.Windows;assembly=GridViewSortLibrary" v:GridViewSort.IsEnabled="True"> <ListView.ItemContainerStyle> <Style TargetType="ListViewItem"> <Setter Property="HorizontalContentAlignment" Value="Stretch" /> </Style> </ListView.ItemContainerStyle> <ListView.View> <GridView> <GridViewColumn Header="名前" DisplayMemberBinding="{Binding Name}" /> <GridViewColumn Header="サイズ" v:GridViewSort.MemberPath="Length"> <GridViewColumn.CellTemplate> <DataTemplate> <TextBlock TextAlignment="Right" Text="{Binding Length, StringFormat=N0}" /> </DataTemplate> </GridViewColumn.CellTemplate> </GridViewColumn> </GridView> </ListView.View> </ListView>こんな感じです。要点は
- xmlnsでアセンブリ・名前空間を指定します
- <ListView>に添付プロパティGridViewSort.IsEnabled="True"を指定します
- <GridViewColumn>にDisplayMemberBindingの指定があればそのプロパティでソートが行われます
- <GridViewColumn>にDisplayMemberBindingを指定できない場合はGridViewSort.MemberPathにプロパティ名をしてします
作成にあたって次の2つの記事を参考にしました。
2014年8月16日土曜日
F# のアセンブリ表現
F#は言語としてはとても素晴らしい設計をしていますが、コンパイルされたアセンブリは結構残念だったりします。F# + Entity Framewrok で ASP.NET WebAPI サーバ立てたら、返ってくる JSON がおかしいという記事を見かけたのでスコープについてまとめておこうと思います。 まずF#言語でアクセス制御するためにはpublic internal privateの3つのキーワードが用意されています。次にアセンブリでのアクセス制御についてはCorHdr.hに定義されています。関連部分を引用すると
// TypeDef/ExportedType attr bits, used by DefineTypeDef. typedef enum CorTypeAttr { // Use this mask to retrieve the type visibility information. tdVisibilityMask = 0x00000007, tdNotPublic = 0x00000000, // Class is not public scope. tdPublic = 0x00000001, // Class is public scope. tdNestedPublic = 0x00000002, // Class is nested with public visibility. tdNestedPrivate = 0x00000003, // Class is nested with private visibility. tdNestedFamily = 0x00000004, // Class is nested with family visibility. tdNestedAssembly = 0x00000005, // Class is nested with assembly visibility. tdNestedFamANDAssem = 0x00000006, // Class is nested with family and assembly visibility. tdNestedFamORAssem = 0x00000007, // Class is nested with family or assembly visibility. ... } CorTypeAttr; // MethodDef attr bits, Used by DefineMethod. typedef enum CorMethodAttr { // member access mask - Use this mask to retrieve accessibility information. mdMemberAccessMask = 0x0007, mdPrivateScope = 0x0000, // Member not referenceable. mdPrivate = 0x0001, // Accessible only by the parent type. mdFamANDAssem = 0x0002, // Accessible by sub-types only in this Assembly. mdAssem = 0x0003, // Accessibly by anyone in the Assembly. mdFamily = 0x0004, // Accessible only by type and sub-types. mdFamORAssem = 0x0005, // Accessibly by sub-types anywhere, plus anyone in assembly. mdPublic = 0x0006, // Accessibly by anyone who has visibility to this scope. // end member access mask ... } CorMethodAttr;という感じです。 先にC#言語のアクセス制御の説明をしておくとわかりやすいでしょうか。C#言語ではpublic private protected internal protected internalの5種類です。これがアセンブリとどのような対応をしているかというと非常にわかりやすく
| C#クラス | アセンブリ表現 |
|---|---|
| public | tdPublic |
| internal | tdNotPublic |
| C#メンバー | アセンブリ表現 |
|---|---|
| public | mdPublic |
| private | mdPrivate |
| protected | mdFamily |
| internal | mdAssem |
| protected internal | mdFamORAssem |
| F#クラス | アセンブリ表現 | C#相当クラス |
|---|---|---|
| public | tdPublic | public |
| private | tdNotPublic | internal |
| internal | tdNotPublic | internal |
| F#メンバー | アセンブリ表現 | C#相当メンバー |
|---|---|---|
| public | mdPublic | public |
| private | mdAssem | internal |
| internal | mdAssem | internal |
| F#メンバー | アセンブリ表現 | C#相当メンバー |
|---|---|---|
| public | mdPublic | public |
| private | mdAssem | internal |
| internal | mdAssem | internal |
| backing field | mdAssem | internal |
| F#クラス | F#メンバー | アセンブリ表現 | C#相当メンバー |
|---|---|---|---|
| public | public | mdPublic | public |
| private | mdAssem | internal | |
| internal | mdAssem | internal | |
| backing field | mdAssem | internal | |
| private | public | mdAssem | internal |
| private | mdAssem | internal | |
| internal | mdAssem | internal | |
| backing field | mdAssem | internal | |
| internal | public | mdAssem | internal |
| private | mdAssem | internal | |
| internal | mdAssem | internal | |
| backing field | mdAssem | internal |
- backing fieldはmdPrivateにして欲しい
- privateメンバーもmdPrivateにして欲しい
- internal / privateクラスであってもpublicメンバーはmdPublicにして欲しい
2014年6月9日月曜日
艦これ 司令部室について
F# 談話室の15回に参加し、艦これ 司令部室について発表してきました。これに合わせてGitHubでリポジトリも公開しました。
COMの素晴らしさを力説し、また布教してきました。比較的に好感触でした。
たいしたことをは書いていませんが、発表に使ったプレゼンテーションはこちら
COMダメ、絶対! #fsroom
— 赤ペン先生テラモナギ (@teramonagi) June 8, 2014
2014年3月12日水曜日
WinFormsのTextBox ControlでのIME制御
WinFormsのTextBox Controlの話題です。とても基本的なControlですが落とし穴がありました。IMEで漢字変換中にフォーカスを失うと未確定文字はそのままIMEが持って行ってしまいます。 WinFormsがこのような挙動をすることを知らず何も制御していなかったために、自作のアプリケーションがクソ呼ばわりされる事態に陥ってしまいました。 とても悲しかったのでTextBoxを派生して挙動を改良してみました。 IMEが開いているかどうかはImeContext.IsOpen()で調べることができますが、そのあと同じハンドルを使用して処理することになるため、IsOpenは使いませんでした。
2013年12月14日土曜日
続続 C は高級アセンブラ
C# は高級アセンブラ、続 C# は高級アセンブラの続きです。
C#ではおよそ最適化できることろがなくなってきていて、最後のブレイクスルーとして、altriveさんの
@haxe さんのコード見て、これ以上最適化できそうもないので、自分のコードも公開。https://t.co/rnekjQyrjx 工夫してるのは下記の2点。
・標準入力が遅かったので、PInvokeでCのライブラリを呼び出し
・RadixSortを条件に合わせて最適化
— altrive (@altrive) December 6, 2013
を真似させてもらいました。標準入出力を扱うConsoleクラスはstaticメソッドになっているため、マルチスレッドセーフにするためにどうしても処理が重くなっていることはわかっていました。代替手段としてMono.Posixアセンブリに含まれているMono.Unix.Native名前空間、Syscall.read()を使う案もありましたが、Assembly.Load()で読み込むことができずに没になりました。
仕方がないため、libcのread()を直接DllImportしています。
これによりめでたくCase3でも0.01のスコアを出すことができました。逆に言うと、Consoleクラスのオーバーヘッドが0.02秒存在していることになります。ソースコードはこちら。
ところでこの記事のタイトル「高級アセンブラ」ですが、どうも「私が『高級アセンブラ』とは『単にプリミティブっぽい処理を並べること』と解釈している」と誤解されている方がいるようなので説明しておきます。
私なりの解釈では高級アセンブラとは、その言語のコードから生成されるアセンブリが容易に想像でき、また生成されるアセンブリ内容を制御すべくコーディングすることにあると考えています。
例えば初回に公開したコードにはコメントに「引数4、変数4なので効率よく参照可能。」とあるようにレジスタ割り付けを考慮したコーディングを行っています。(C言語であればコンパイラにレジスタ割り付けを指示するregisterキーワードが存在しました。)
と言うだけでは何なので、C言語でも書いてみました。スコアは普通に0.01 / 0.01 / 0.01です。 なお、しょうもないところでは #include IO_H の行。インクルードするファイル名は直接の文字列でなくても文字列定数を使うこともできます。もちろんコンパイラの出力を貼り付けただけだろうと言わせないためにも、今どきのコンパイラが使用しない AAh なんかを使ってます…実際には効率悪いとは思いますが。
その他、C#とロジックは同じなので、÷10、÷100、÷1000、÷10000、÷100000が存在していますが、この辺りもコンパイラは逆数の乗算に変換します。…というだけなのも悔しいのでコードでは使用していませんが手計算で調べたメモを書いておきます。
- 0~99の範囲で x / 10 に相当するのは x * 205 >> 11
- 0~999の範囲で x / 100 に相当するのは x * 41 >> 12
- 0~9999の範囲で x / 1000 に相当するのは x * 8839 >> 23
- 0~99999の範囲で x / 10000 に相当するのは (int)(x * 429497L) >> 36)
- 0~999999の範囲で x / 100000 に相当するのは (int)(x * 687195L) >> 40)
乗数を小さくする利点は、乗算が高速に完了することもありますが、更にもう1つ、LEA命令が視野に入ってきます。LEAはアドレス計算する命令ですがこれを応用すると乗算を更に高速化できます。
例えば、x * 41の場合、
LEA EBX, [EAX * 4 + EAX] ; EBX = EAX * 5 LEA EAX, [EBX * 8 + EAX] ; EAX = EBX * 8 + EAX = EAX * 41と2命令で表現できます。そしてx * 205も3命令です。
LEA EBX, [EAX * 4 + EAX] ; EBX = EAX * 5 LEA EAX, [EBX * 8 + EAX] ; EAX = EBX * 8 + EAX = EAX * 41 LEA EAX, [EAX * 4 + EAX] ; EAX = EAX * 205やりだすと本当に奥が深くなります。
F# と OCaml
この記事はF# Advent Calendar 2013に参加しています。14日目を担当させてもらいます。
F#とは
F#についてはMSDNではF# は、従来のオブジェクト指向プログラミングと命令型 (手続き型) プログラミングに加えて、関数型プログラミングをサポートするプログラミング言語です。 Visual F# 製品は、F# アプリケーションの開発と F# コードを使用した他の .NET Framework アプリケーションの拡張をサポートします。 F# は、.NET Framework 言語のファースト クラスのメンバーであり、関数型言語の ML ファミリに著しく似ています。…と説明されています。MLファミリと書かれていますが、中でもオブジェクト指向的要素が追加されたOCamlとはかなりの類似点していて、F#で提供されるコアライブラリの基本部分はOCamlのモジュールと一致しています。そこでOCamlとF#の違い、その理由について探ってみようと思います。
F#とOCamlの違い
何よりも大きいのはオブジェクトおよびガベージコレクタでしょうか。F#は.NET Framework上で動作するため、すべてのオブジェクトは.NETオブジェクトでありSystem.Objectの派生クラスです。このことは全てのオブジェクトはSystem.Typeを通じてRTTI; Run-Time Type Infomationが得られることを意味します。対してOCamlは独自のGCでありSystem.TypeのようなRTTIは提供されていません。元々コンパイル時に型チェックされているため、実行時に型情報は不要なわけです。そのため実行時にObj.magicを用いてデータを強引にキャストしてしまうこともできます。これはC++言語でいうreinterpret_castであり、F# / .NETでは不可能な行為です。構文に対する細かい優先順位もところどころ違います。もちろん構文の違いといえばF#には独自の軽量構文がありますし私自身便利に使っていますが、OCamlとの比較においては論外ということで、それ以外について。 F#では.NET Frameworkで提供されるnamespaceが扱え、「.」(ピリオド)で区切ります。更にプロパティやメソッドも「.」でつながれ、正しいメンバー参照である限りどこまででもつなぐことができます。例えばSystem.String.Empty.Count.ToString()とか。しかしOCamlではUIDとLIDしかありません。UIDとは大文字から始まる識別子、LIDとは小文字から始まる識別子です。先ほどのObj.magicはObjがUIDでmagicがLIDとなります。namespaceはなくクラスメソッド参照は「#」となるため「.」の優先順位が異なります。
構文といえば独自に新しい演算子が作成できる点、これはF#とOCamlと共通で、通常の.NET Frameworkからするとかなり異質な行為です。逆にコンピュテーション式はF#独自の機能です。コンピュテーション式内は通常のF#構文のままですが、書かれた式は直接コンパイルされるのではなく、構文解析後、コンピュテーション式のビルダークラスのメソッド呼び出しへと変換されてコンパイルされます。このような機能はOCamlにはありません。
…というのは嘘で、Camlp4という特殊なモジュールがあります。Camlp4とはPre-Processor-Pretty-Printer for OCamlという意味です。一般的にコンパイラというのは、構文パーサーがソースコードからAST; Abstract Syntax Treeを構築し、ASTを何等かのバイナリに変換を行います。OCamlでは通常の構文パーサー以外にCamlp4が提供する別のParserに差し替えることが可能です。といってもただ構文パーサーを差し替えても同じ構文しか使えないのでは実装が2種類あるだけ何も面白くありません。独自のParserを作成しそれに差し替えることもできます。 ただし、独自のParserを全て作り上げるのは大変なことなので、新たに構文を増やさないのであればFilterというASTを別のASTに変換する機能もあります。これはF#のコンピュテーション式に近い行為ですが、どのように変換するかをプログラム的に制御できるため自由度は高いです。 また、ParserとFilterまで用意するならついでということでPrinterも用意されています。こちらはASTをコンパイルするのではなく別のデータ形式に変換することができます。それ以外にもいろいろありますが、あまり詳しくないのでこの辺りまでで。
実はCamlp4はRevisedというOCamlとは別の構文で書かれています。つまりCamlp4をコンパイルするにはCamlp4のRevised Parserが必要であり、ある種のセルフホスト状態になっています。なぜそうなっているかというとOCamlの構文が気に食わないそうで、funとfunctionは同じものだからキーワードを分ける意味がないとか、変数と関数を同じletにすべきではないとか、どこかに書かれていました。
FSharp Printer for Camlp4
というわけで、Camlp4に含まれているOCaml Printerを元にFSharp Printerを作ってみました。Camlp4がparse可能なソースコードをF#コードに変換して出力できることになります。patch形式にしているのは、修正個所がわかりやすくなるのとASTの変更に追従を考えて、ですが…無意味かな?先に使い方を説明しておくと、コンパイルするにはコンパイラとパッチ元ファイルのバージョンを一致させる必要があります。現時点でリポジトリに含めているのは4.01.0向けのソースになります。コンパイル方法は、通常のOCamlと少し異なり、Camlp4を使います。
$ ocamlc -I +camlp4 -pp camlp4rf -c Camlp4FSharpPrinter.mlでCamlp4FSharpPrinter.cmoが生成されます。
使うためにはこのファイルを参照可能なディレクトリならどこでも構わないですがとりあえず、
$ cp Camlp4FSharpPrinter.cmo $ocaml/lib/camlp4/Camlp4Printers/とコピーします(尚、パッチ元のOCaml.mlのあるディレクトリとは別です)。その上で、OCamlソースコードをF#コードに変換するには
$ camlp4of -printer Camlp4FSharpPrinter -o fsharp-source.ml ocaml-source.mlと実行します。
ということでコードをペタペタ。
F#とOCamlの違い
またですが…FSharp Printerを作っていて気づいた点をいくつか。F#の軽量構文はインデントで表現するため、メンバーの無いクラスを表現することができません。ごくまれに存在するmarker interfaceのようなことが表現できなくて困ったりします。こういう場合でも冗長構文なら表現することができます。
OCamlはモジュールをまたぐ構造体メンバーアクセスの際にはモジュール名を挟みます。someObject.ModuleName.memberNameという感じにいきなりモジュール名が出現するため心臓に悪いです(最初の方に書いたUID / LIDはこのことです)。F#では型情報は全て把握できているためsomeObject.memberNameになります。
F#では配列を含むindexerが全て .[] 演算子で表現されます。そのため .[] が出現した時点でindexerの存在する型に確定していないとコンパイルできません。対してOCamlにはindexerという汎用的な概念はなく、配列要素にアクセスする .() と文字列にアクセスする .[] のみとなっています。つまり、F#とは逆でこれらの演算子から型推論することができます。この型推論条件の違いを埋め合わせるために、FSharp PrinterではArray.get / String.getに置き換えています。
OCamlの==演算子、!=演算子も困ったことに。単純に=演算子、<>演算子に置き換えることはできません。とりあえずわかる範囲でパターンマッチに展開することにしました。この辺りの細かいOCamlの動作(及びそれに相当するF#のコード)はよくわかっていません。
OCamlはパターンマッチに'0'..'9'のように文字範囲を表現することができますがF#にはありません。こちらはwhen句に展開しています。
F#ではクラス内のletはprivateスコープですが、OCamlはいわゆるprotectedスコープであり派生クラスから参照可能です。こういった違いはさすがにFSharp Printerで埋め合わせることはできません。コンストラクターの書式も違いますがこちらは結構強引に対応させています。
もっと大きな問題として、OCamlにあってF#に存在しないモジュールの呼び出し…これについてはF# PowerPackを使ってください。
最後にどうにもならない爆弾を…。F#というか.NETには値型があるため効率のいい配列操作ができますが、OCamlはこれができません。そのため、stringがbyte arrayのように使われています。つまりOCamlのstringはmutableです。対してF#のstringはimmutableです。この違いはどうにも吸収できません。OCamlソースをF#に移植する際には、mutableに扱われているstringをbyte[]に変換するところから始まると言ってもいいでしょう。
以上、とりとめもないF#とOCamlの比較でした! Camlp5…? 知らない子ですね。
