.NET RemotingとWCFの性能比較

2017/06/27

.NET RemotingよりWCFのほうが遅いという噂もあるようなので、絶対値がどれぐらいなのかを調べるついでに、.NET RemotingとWCFとで比較してみた。

条件は以下の通り

  • 同一マシン内で、IPCにより通信を行う。
  • クライアントとサーバは別プロセスとする
  • クライアント側からサーバ側の手続きを呼び出す処理の速度を比べる
  • Releaseモードでコンパイルする。
  • F#4.1、.NET Framework 4.7で実行する

昨日とほぼ同じだが、.NET RemotingとWCFのコードを以下に示す。

■.NET Remoting版

-----------------------------------------------------------------

open System
open System.Runtime.Remoting
open System.Runtime.Remoting.Channels
open System.Runtime.Remoting.Channels.Ipc
open System.Threading

let LoopCount = 100000UL

// サーバからクライアントに対して公開されるオブジェクト
type CSharedObject() =
    inherit MarshalByRefObject()
    member this.foo arg =
        ( arg / 2UL )

// サーバ側の処理
let Server () =
    ChannelServices.RegisterChannel( new IpcServerChannel "test1", true )
    let rSharedObject = new CSharedObject()
    RemotingServices.Marshal(
        rSharedObject,
        "test2",
        typeof< CSharedObject >
    ) |> ignore

    System.Threading.Thread.Sleep 60000

// クライアント側の処理
let Client () =
    // サーバ側で公開されているオブジェクトを取得する
    ChannelServices.RegisterChannel( new IpcClientChannel(), true )
    let rSharedObject =
        Activator.GetObject(
            typeof< CSharedObject >,
            "ipc://test1/test2"
        ) :?> CSharedObject

    let rec foo cnt sum =
        if cnt < LoopCount then
            foo ( cnt + 1UL ) ( sum + ( rSharedObject.foo cnt ) )
        else
            sum

    for i = 1 to 5 do
        let StartTime = DateTime.Now
        printf "%u\n" ( foo 0UL 0UL )
        let ElapseTime = ( DateTime.Now - StartTime ).TotalMilliseconds
        printf
            "time=%f(ms), count/sec=%f\n"
            ElapseTime ( ( float LoopCount ) / ElapseTime * 1000.0 )

[<EntryPoint>]
let main argv = 
    if argv.[0] = "server" then
        Server ()
    else
        Client ()
    0

-----------------------------------------------------------------

■WCF版

-----------------------------------------------------------------

open System
open System.ServiceModel
open System.Threading

let g_Address = "net.pipe://localhost/test"
let LoopCount = 100000UL

// クライアントに公開されるオブジェクトのインタフェース
[<ServiceContract()>]
type public IRemote =
    [<OperationContract(Name="Hello")>]
    abstract member Hello : arg:uint64 -> uint64

// 遠隔手続きを実装するクラス
type public Remote() =
    interface IRemote with
        member this.Hello arg  =
            ( arg / 2UL )

// サーバ側の処理
let Server () =
    let host =
        new ServiceHost(
            typeof<Remote>,
            new Uri( "net.pipe://localhost" )
        )
    host.AddServiceEndpoint(
        typeof<IRemote>,
        new NetNamedPipeBinding( NetNamedPipeSecurityMode.None ),
        g_Address
    ) |> ignore
    host.Open();

    Threading.Thread.Sleep 60000

// クライアント側の処理
let Client () =
    let proxy =
        ChannelFactory<IRemote>.CreateChannel(
            new NetNamedPipeBinding( NetNamedPipeSecurityMode.None ),
            new EndpointAddress( g_Address )
         )
    let rec foo cnt sum =
        if cnt < LoopCount then
            foo ( cnt + 1UL ) ( sum + ( proxy.Hello cnt ) )
        else
            sum

    for i = 1 to 5 do
        let StartTime = DateTime.Now
        printf "%u\n" ( foo 0UL 0UL )
        let ElapseTime = ( DateTime.Now - StartTime ).TotalMilliseconds
        printf
            "time=%f(ms), count/sec=%f\n"
            ElapseTime ( ( float LoopCount ) / ElapseTime * 1000.0 )
    
[<EntryPoint>]
let main argv = 
    if argv.[0] = "server" then
        Server ()
    else
        Client ()
    0

-----------------------------------------------------------------



これを実行してみる。


.NET Remotingの実行結果

2499950000
time=7941.328300(ms), count/sec=12592.351836
2499950000
time=7799.342400(ms), count/sec=12821.593779
2499950000
time=7746.714100(ms), count/sec=12908.698928
2499950000
time=7709.778300(ms), count/sec=12970.541578
2499950000
time=7731.699700(ms), count/sec=12933.766685

"2499950000"の出力は最適化させないように、無理にでも処理結果を使うようにしているだけであって、意味はない。

とりあえず、1秒間に1万2千回程度呼び出しができるようだ。

次はWCFの実行結果

2499950000
time=4733.906600(ms), count/sec=21124.202155
2499950000
time=4538.663200(ms), count/sec=22032.919297
2499950000
time=4550.678500(ms), count/sec=21974.745085
2499950000
time=4541.667300(ms), count/sec=22018.345553
2499950000
time=4558.723600(ms), count/sec=21935.964707

WCFだと秒間で2万1千回程度実行できているように見える。

よくわからないが、.NET RemotingよりWCFのほうが早い。

何か設定や指定が悪いのか、あるいは.NETのバージョンが上がってWCFの最適化が進んでいるためか、原因は知らんが、まぁこの程度だということを前提に考えておこう。


F#でIPCをやってみる

2017/06/26

F#でプロセス間通信を行うことを考えてみる。

無論、丸裸のTCPやUDP、共有メモリやパイプ・メールスロットなんかでもいいわけだが、それだと正直に言って実装がしんどい。だから、何かいい感じの方法は無いものかと探っていたら、.NET Remotingなる方法があるらしいので、試してみた。

■.NET Remoting版

-------------------------------------------------------------------------------------------

open System
open System.Runtime.Remoting
open System.Runtime.Remoting.Channels
open System.Runtime.Remoting.Channels.Ipc
open System.Diagnostics
open System.Threading

// サーバからクライアントに対して公開されるオブジェクト
type CSharedObject() =
    inherit MarshalByRefObject()

    // publicかつ可変なメンバ変数
    [<DefaultValue>]
    val mutable public v2 : int
    let mutable value = 0

    // publicなメソッド
    member this.foo arg =
        let pid = Process.GetCurrentProcess().Id
        let thid = Thread.CurrentThread.ManagedThreadId
        printf "CSharedObject.foo, pid=%d, thid=%d, value=%d, arg=%d\n" pid thid value arg
        value <- arg
        arg + 1         // 意味はないが、+1した値を返してみる

// サーバ側の処理
let Server () =
    let pid = Process.GetCurrentProcess().Id
    let thid = Thread.CurrentThread.ManagedThreadId
    printf "Server Process started. pid = %d, thid=%d\n" pid thid

    // CSharedObjectのオブジェクトを作って公開する
    ChannelServices.RegisterChannel( new IpcServerChannel "test1", true )
    let rSharedObject = new CSharedObject()
    RemotingServices.Marshal( rSharedObject, "test2", typeof< CSharedObject > ) |> ignore

    //************************************************************
    for i = 1 to 60 do
        // サーバ側でv2の値を定期的に更新してみる
        rSharedObject.v2 <- i
        printf "Server, pid=%d, thid=%d, v2=%d\n" pid thid rSharedObject.v2
        System.Threading.Thread.Sleep 1000
    //************************************************************

// クライアント側の処理
let Client () =
    let pid = Process.GetCurrentProcess().Id
    let thid = Thread.CurrentThread.ManagedThreadId
    printf "Client Process started. pid = %d, thid=%d\n" pid thid

    // サーバ側で公開されているオブジェクトを取得する
    ChannelServices.RegisterChannel( new IpcClientChannel(), true )
    let rSharedObject = Activator.GetObject( typeof< CSharedObject >, "ipc://test1/test2" ) :?> CSharedObject

    //************************************************************
    for i = 1 to 10 do
        // 定期的に公開オブジェクトのメソッドを呼ぶ
        printf
            "Client, pid=%d, thid=%d, foo=%d, v2 = %d\n"
            pid thid ( rSharedObject.foo ( i * 100 ) ) rSharedObject.v2
        Threading.Thread.Sleep 500
    //************************************************************

[<EntryPoint>]
let main argv = 
    if argv.[0] = "server" then
        Server ()
    else
        Client ()
    0

-------------------------------------------------------------------------------------------

上記と合わせて、System.Runtime.Remotingを参照設定に追加して、コンパイルしてやったら動いた。

検索して出てきたC#のソースをほぼそのままF#に変えてやっただけではあるが、非常に素直に動作してくれる。

どうも、挙動としてはサーバ側で公開したオブジェクト(上記ではCSharedObjectクラスのインスタンス)のメソッドをクライアント側から呼び出すと、引数がクライアントからサーバに渡されて、サーバ側のプロセス空間で手続きが実行され、戻り値がクライアント側に返されるらしい。

併せて、CSharedObjectクラスのメンバ変数(上記だとCSharedObject.v2)をサーバないしクライアントで変更すると、それが相手側に反映されるらしい。正直この辺りはどのような実装になっているのか、あるいは、いつのタイミングで変更が反映されるのか、さっぱり理解ができない。しかしまぁ、publicで変更可能でメンバ変数など断じて使うことはないから、動作のメカニズムなど知る必要はないだろう。

非常に素晴らしいではないか! と言いたいところだが、よく調べてみると、.NET Remotingは.NET 3.0以降では非推奨になっているという。今ではWCFを使えと。

WCFは本屋でそういうタイトルの本が売っているのを見たことはあるが、今まで手を出したことがない。とりあえず、統一的に通信できるようにする仕掛けらしいのだが、いまいちよくわからない。かつ、深入りする気力がない。

どうせやりたいこととしては、「同一マシン内で」「お手軽に」プロセス間通信を行いたいだけなのだ。なので、またしてもネットにあったソースをパクってF#に書き直してやってみた。

■WCF版

-------------------------------------------------------------------------------------------

open System
open System.ServiceModel
open System.Diagnostics
open System.Threading

let g_Address = "net.pipe://localhost/test"

// クライアントに公開されるオブジェクトのインタフェース
[<ServiceContract()>]
type public IRemote =
    [<OperationContract(Name="Hello")>]
    abstract member Hello : arg:string -> string

// 遠隔手続きを実装するクラス
type public Remote() =
    interface IRemote with
        member this.Hello( arg ) =
            let pid = Process.GetCurrentProcess().Id
            let thid = Thread.CurrentThread.ManagedThreadId
            printf "Remote.Hello arg=%s, pid=%d, this=%d\n" arg pid thid
            sprintf "Hello %s" arg

// サーバ側の処理
let Server () =
    let pid = Process.GetCurrentProcess().Id
    let thid = Thread.CurrentThread.ManagedThreadId
    printf "Server start pid=%d, this=%d\n" pid thid

    let host = new ServiceHost( typeof<Remote>, new Uri( "net.pipe://localhost" ) )
    host.AddServiceEndpoint(
        typeof<IRemote>,
        new NetNamedPipeBinding( NetNamedPipeSecurityMode.None ),
        g_Address
    ) |> ignore
    host.Open();

    Threading.Thread.Sleep 60000

// クライアント側の処理
let Client () =
    let pid = Process.GetCurrentProcess().Id
    let thid = Thread.CurrentThread.ManagedThreadId
    printf "Client start pid=%d, this=%d\n" pid thid

    let proxy =
        ChannelFactory.CreateChannel(
            new NetNamedPipeBinding( NetNamedPipeSecurityMode.None ),
            new EndpointAddress( g_Address )
         )
    
    for i = 1 to 10 do
        printf "client, i=%d, pid=%d, this=%d\n" i pid thid
        proxy.Hello ( "abc" + i.ToString() ) |> ignore
        Threading.Thread.Sleep 1000

[<EntryPoint>]
let main argv = 
    if argv.[0] = "server" then
        Server ()
    else
        Client ()
    0

-------------------------------------------------------------------------------------------

上記と合わせて、System.Runtime.SerializationとSystem.ServiceModelを参照設定に追加してやる。

こうしてやると、概ね.NET Remotingの場合と同じように、クライアント側からメソッドを呼んでやるとサーバ側の手続きが実行されるという挙動が実現できる。

しかし、ここで1つ嵌ったことがあったから記録として残しておく。

上記のコードの内インタフェースの宣言部分について、初めは以下のように書いていた。

[<ServiceContract()>]
type public IRemote =
    [<OperationContract(Name="Hello")>]
    abstract member Hello : string -> string

Helloメソッドの仮引数名を省略していた。

この状態でサーバ側のプロセスを起動すると、new ServiceHost(……)の処理で"ハンドルされていない例外: System.ArgumentNullException: サービス コントラクトを構成する操作内で使用するすべてのパラメーター名は NULL にできません。"なる例外が発生して処理が異常終了する。

どうにも、発生した事象からは原因が特定できず、まともに動作させられるようになるまで、ひどく苦労した。大体、普通、仮想メソッドの仮引数名など意味はないと思うだろう。F#でもC++でも省略できるし。

どうにも原因がわからなくて、とりあえず試しにC#で書いてみたらあっさり動くし、その時何となくIRemote.Helloの仮引数名(arg)が省略できないから、何となくF#側も明記してやったら、たまたま偶然動くようになった。わかるわけねぇだろう。

まぁ、マーシャリングに必要なんだと言われればそんな気もしなくもないが、しかし、エラーメッセージはもう少しどうにかならないものなのだろうか?

F#でWindows Serviceを実装する。

2017/06/23

.NET FrameworkでWindowsのサービスを実装するのは簡単だろうと高をくくっていたらいろいろとはまったので記録を残しておく。

大きくやらなければならないのは、

  • Serviceとして実行される処理の実装
  • インストーラの実装

の2つ。

そのうち、インストーラの実装の方が、大体「Visual StudioでWindowsサービス用のテンプレートを使って……云々」という記載だけで誤魔化しているものばかりで、ほとんどまともな説明がない。わかってないなら、偉そうに書くなと。

(1)Serviceの実装

Serviceの実装は、

  1. System.ServiceProcess.ServiceBaseクラスを継承したサブクラスを実装する。
  2. System.ServiceProcess.ServiceBase.Runメソッドで、サービスを起動してやる。

2つをやる必要がある。

なので、以下のようなプログラムを記述する。

module File1

open System.ServiceProcess
open System.IO

// Windowsサービスに関連するイベントを処理するクラス
type ServiceTest() =
    inherit ServiceBase()
    do
        base.ServiceName <- "ServiceTest"
        base.CanStop <- true
        base.CanPauseAndContinue <- true
        base.AutoLog <- true

    override this.OnContinue () =
        File.AppendAllText( @"d:\a.txt", "OnContinue\r\n" )

    override this.OnCustomCommand command =
        File.AppendAllText( @"d:\a.txt", "OnCustomCommand " + command.ToString () + "\r\n" )

    override this.OnPause () =
        File.AppendAllText( @"d:\a.txt", "OnPause\r\n" )

    override this.OnPowerEvent powerStatus =
        File.AppendAllText( @"d:\a.txt", "OnPowerEvent " + powerStatus.ToString () + "\r\n" )
        true

    override this.OnSessionChange changeDescription =
        File.AppendAllText( @"d:\a.txt", "OnSessionChange " + changeDescription.ToString () + "\r\n" )

    override this.OnShutdown () =
        File.AppendAllText( @"d:\a.txt", "OnShutdown\r\n" )

    override this.OnStart args =
        File.AppendAllText( @"d:\a.txt", "OnStart ArgCnt=" + args.Length.ToString() + "\r\n" )

// エントリポイント
[<EntryPoint>]
let main args =
    File.AppendAllText( @"d:\a.txt", "main start\r\n" )
    // 起動してやる
    ServiceBase.Run( new ServiceTest() )
    File.AppendAllText( @"d:\a.txt", "main end\r\n" )
    0

この辺については、MSDNの以下を見るとおおむね記載されている。

https://msdn.microsoft.com/ja-jp/library/76477d2t(v=vs.110).aspx

併せて、参照設定で以下を追加しなければならない。

・System.ServiceProcess

(2)インストーラの実装

.NET Frameworkでサービスを実装する場合、インストールするのに「InstallUtil.exe」を使えと指定されている。それについては、MSDNの以下のページに記載がある。

https://msdn.microsoft.com/ja-jp/library/sd8zc8ha(v=vs.110).aspx

問題は、このコマンドでインストールされるサービスのプログラムには、いくつかのおまじないが必要になるということである。でもって、その唱えるべき呪文が正しく記載された情報が見当たらないのが腹が立つ。

まず、実装すべき内容そのものは、以下に記載がある。

https://msdn.microsoft.com/ja-jp/library/system.serviceprocess.serviceprocessinstaller(v=vs.110).aspx

だが、ここに書かれている情報は不完全で、これだけでは動かない。糞が。

概ね、やらなければならないこととしては

  1. System.Configuration.Install.Installerクラスを継承したクラスを実装する
  2. 上記のクラスのメンバとして、System.ServiceProcess.ServiceProcessInstallerと、System.ServiceProcess.ServiceInstallerのインスタンスを持たせる。
  3. コンストラクタで、上記メンバの初期化とSystem.Configuration.Install.Installer.Installersへの追加を行う

ということである。

なので、プログラム的には以下となる。

// 名前空間にはProject.WindowsServiceを指定しなければならない。
namespace Project.WindowsService

open System.ServiceProcess
open System.Configuration.Install
open System.ComponentModel

 // System.Configuration.Install.Installerクラスを継承したサブクラスを実装する。
// その際、このクラスにはRunInstaller(true)の属性を指定してやらなければならない。
// さらに、このクラスはProject.WindowsServiceの名前空間に所属していなければならない。
[<RunInstaller(true)>]
type public MyProjectInstaller() =
    inherit Installer()

    let processInstaller =
        new ServiceProcessInstaller(
            Account = ServiceAccount.LocalSystem
        )

    let serviceInstaller1 =
        new ServiceInstaller(
            StartType = ServiceStartMode.Manual,
            ServiceName = "ServiceTest",
            DisplayName = "ServiceTest",
            Description = "Some string that descript this service desuwa."
        )

    do
        base.Installers.AddRange
            [|
                processInstaller :> Installer;
                serviceInstaller1 :> Installer
            |]

ここで、赤背景で塗ってある箇所は、MSDNにも真っ当に記載されていないポイントなので気を付ける必要がある。

併せて、System.Configuration.Installへの参照設定を追加してやる必要もある。

上記2つのプログラムを適当にコンパイルしてやって(一方はProject.WindowsServiceの名前空間を指定する必要があり、もう一方はmain関数が存在する必要があるから、モジュールにならざるを得ない。そのため、少なくともソースコードのファイルは2つに別れなければならないはず)、「installutil EXEファイル名」と指定してやれば、サービスに登録できるはず。

とりあえず、64bit環境なので管理者権限で起動したPowerShellから、以下のように実行してやる。

------------------------------------------------------------------
Windows PowerShell
Copyright (C) 2016 Microsoft Corporation. All rights reserved.
PS C:\Windows\system32> $Env:Path += ";C:\Windows\Microsoft.NET\Framework64\v4.0.30319"
PS C:\Windows\system32> cd D:\MO\F#\ServiceTest\ServiceTest\obj\Debug
PS D:\MO\F#\ServiceTest\ServiceTest\obj\Debug> installutil ServiceTest.exe
Microsoft(R) .NET Framework Installation utility Version 4.7.2046.0
Copyright (C) Microsoft Corporation. All rights reserved.

トランザクションのインストールを実行中です。
インストール段階を開始しています。
D:\MO\F#\ServiceTest\ServiceTest\obj\Debug\ServiceTest.exe アセンブリの進行状態については、ログ ファイルの内容を参照してください。
ファイルは D:\MO\F#\ServiceTest\ServiceTest\obj\Debug\ServiceTest.InstallLog にあります。
アセンブリ 'D:\MO\F#\ServiceTest\ServiceTest\obj\Debug\ServiceTest.exe' をインストールしています。
該当するパラメーター:
   logtoconsole =
   assemblypath = D:\MO\F#\ServiceTest\ServiceTest\obj\Debug\ServiceTest.exe
   logfile = D:\MO\F#\ServiceTest\ServiceTest\obj\Debug\ServiceTest.InstallLog
サービス 'ServiceTest' をインストールしています...
サービス 'ServiceTest' は正常にインストールされました。
EventLog ソース ServiceTest をログ Application に作成しています...
インストール段階が正常に完了しました。コミット段階を開始しています。
D:\MO\F#\ServiceTest\ServiceTest\obj\Debug\ServiceTest.exe アセンブリの進行状態については、ログ ファイルの内容を参照してください。
ファイルは D:\MO\F#\ServiceTest\ServiceTest\obj\Debug\ServiceTest.InstallLog にあります。
アセンブリ 'D:\MO\F#\ServiceTest\ServiceTest\obj\Debug\ServiceTest.exe' をコミットしています。
該当するパラメーター:
   logtoconsole =
   assemblypath = D:\MO\F#\ServiceTest\ServiceTest\obj\Debug\ServiceTest.exe
   logfile = D:\MO\F#\ServiceTest\ServiceTest\obj\Debug\ServiceTest.InstallLog
コミット段階が正常に終了しました。
トランザクション インストールが完了しました。
PS D:\MO\F#\ServiceTest\ServiceTest\obj\Debug>
------------------------------------------------------------------

そうすると、以下のようにサービスに登録される。

ソースに記載している通り、d:\a.txtにデバッグ用の文字列を出力させているので、「開始」-「一時停止」-「再開」-「停止」と操作してやると、以下のような出力が得られる。

------------------------------------------------------------------
main start
OnStart ArgCnt=0
OnPause
OnContinue
main end
------------------------------------------------------------------

また、登録したサービスを削除するには「installutil /u EXEファイル名」と指定してやればいい。




RAIDをやめた

2017/05/30

同じく記録だけこのしておく。

メインのPCは今までAdaptec RAID 2405でRAID 1Eにしていたのだが、やめてしまった。

RAIDにしていても、
  1. 個人用PCだと、メディア(HDDとかSSD)は数が少ない。相対的にそれ以外のパーツが多くて、障害発生原因はメディアばかりではないということになる。業務システムでの数の暴力とは条件が違いすぎる。
  2. 同一筐体にある同一RAIDグループに属するメディアは、なんとなく同時に壊れる気がする。同じ環境で同じような読み書きをしているのだから。だから、1つでも壊れると、全部交換したくなる。
  3. というか、メディア障害よりも、ソフトウェア的な問題のほうが多い。間違って消したとか。
  4. 実は個人用PCだと、しばらく使えなかったところで、死ぬわけではない。
  5. メディア障害が発生したら、壊れた奴だけ代えて業務続行という使い方をしない。そういう時はOSごと再インストールしている。
ということで、あまりRAIDのありがたみを感じなかった。

それよりもは、6に書いた通り、OSやソフトウェアはインストール媒体から再インストールして、ユーザデータはバックアップから戻すという使い方が多い。

結果、RAIDよりバックアップが重要だ、という方向に宗旨替えすることにした。


電源ボックスを交換した

2017/05/28

記録だけ残しておく。

メインマシンの電源ボックスを変えた。

今のところ何の問題もなく動き続けてくれてはいるのだが、買ってからおよそ7年半経過して、さすがになんとなく怪しい感じがするから、悪いけど引退してもらった。

買ったのはこれ。



SSR-750RMS

1万7千円ぐらいした。

特に絵はない。それに、今のところ、何事もなく動いているため、特に述べるべき感想もない。

そういえば、前の奴もSeasonicだった。外した時の写真を撮っておいたから、面白みもないがさらしておく。



外観はまぁ、ほこりが見えてますね、という程度か。 外から見ると、中に入っているファンがなんだかよくわからなくて、怪しい雰囲気を感じる。



San Ace 120と60でした。さすがに羽根の表面は汚れているが、肝心の中身のほうには、あまりちりやほこりが詰まっている感じがしない。



アップにしてみても、あまり汚れが目立たない。

本当はここで、中にぎっちりとほこりが詰まっていて、その中にゴキブリの卵や死体が点々と埋もれているような、あるいは、ふたを開けた途端にパンドラの箱を開けたがごとくこの世のありとあらゆる悪いものが噴出してくるような、そういう絵面を期待していたのだが、全然そんなことはなかった。

正直言って、面白くない。

7年半蓄えたお前のエネルギーはこの程度だったのかと。

F#での引数なしの関数の扱いについて

2017/05/16

なんとなく、F#での引数なしの関数の書き方がよくわからなくなってきたから、整理してみた。

1.単なる関数の場合

以下のプログラムを考える。

module  FTest
open System


// printfの戻り値であるUnit値を持つ定数になる
let func1 =     // unit
    printf "func1\n"


// unit値を引数にとる関数値になる
let func2 () =  // unit -> unit
    printf "func2\n"


[<EntryPoint>]
let main args =
    printf "main start\n"

    func1       // 意味なし
    func1       // 意味なし
    func2 ()    // ここで実行される
    func2 ()    // ここで実行される

    printf "main end\n"
    0


実行結果は以下となる。



上記では、func1自体は関数ではなくunit値の定数になっている。関数本体の実行はmainが呼ばれる前に行われてしまっている。

func2はunit値を引数に取る関数値である。C言語に例えるのなら関数ポインタの値であり、俺が思うところの引数なしの関数として振る舞ってくれる。

だから、func2については「func2 ()」を記述したところで、その関数の中身である「printf "func2\n"」が実行される。一方、func1はmain関数の中では実行されていない。

2.クラスのメソッドの場合
混乱するのが以下の場合だ。

module  FTest
open System


type CTest() =
    member this.func1 =
        printf "CTest.func1\n"


    member this.func2 () =
        printf "CTest.func2\n"


[<EntryPoint>]
let main args =
    printf "main start\n"
    let c = new CTest()

    c.func1       // ここで実行される
    c.func1       // ここで実行される
    c.func2 ()    // ここで実行される
    c.func2 ()    // ここで実行される

    printf "main end\n"
    0


これを実行すると下記となる。



func1の動きがさっきと違う。さっきと似た様なもんだろうと思うのだが、今度はmain関数の中で実行されている。何なのか?

まぁ、よくわからんが、たぶん自己識別子であるthisが暗黙の引数として扱われている、ということなのではないだろうか。だから「c.func1」が記述されたところで、初めて実行されると。

ということで、次にこうしてみた。

3.スタティックなメソッドの場合

module  FTest
open System


type CTest() =
    static member func1 =
        printf "CTest.func1\n"

    static member func2 () =
        printf "CTest.func2\n"


[<EntryPoint>]
let main args =
    printf "main start\n"
    let c = new CTest()

    CTest.func1       // ここで実行される
    CTest.func1       // ここで実行される
    CTest.func2 ()    // ここで実行される
    CTest.func2 ()    // ここで実行される

    printf "main end\n"
    0


実行するとこうなった。



予想外な結果である。俺としては、CTest.func1は、タイミングは別としても1回だけしか実行されないのではないかと考えたのだが、そうでもないらしい。

4.let束縛にする

なんかむかついたから、こうしてみた。

module  FTest
open System


type CTest() =
    let func1 = printf "CTest.func1\n"


    static member func2 () =
        printf "CTest.func2\n"


    member this.func3 = func1

[<EntryPoint>]
let main args =
    printf "main start\n"
    let c = new CTest()
    printf "new CTest() owari\n"

    CTest.func2 ()  // ここで実行される
    CTest.func2 ()  // ここで実行される
    c.func3         // 意味なし
    c.func3         // 意味なし

    printf "main end\n"
    0


実行結果はこうなる。



で、ここまでやって、ようやく気がついた。

2番目や3番目のfunc1のように、クラスのメソッドにした場合はなぜにメソッドが呼ばれるまで実行されないのか? それは極単純に「member……」と書くか「let……」と書くかの違いに応じて、扱いが変わっているのではないか? わからんけど。

だから、4番目の例では、newしたタイミングでlet束縛の「printf "CTest.func1\n"」が実行されて、「c.func3」は無視されていると思われる。

5.まとめ

引数なしの「member……」と「let……」の扱いが相違するのはなんとなく直観に反する気もするけど、とりあえず、引数なしの関数にはダミーの引数であるunit値を指定させたほうがいいらしい。

少なくともそうすれば、定数のつもりなのか、引数なしの関数のつもりなのかが明らかになるし、関数が実行されるタイミングを間違えることもなくなるだろう。

INTELとRealtecのNICを比べてみた 結果だけ書いておく

2017/04/24

勉強がてら、F#とXAMLで何か作ってみようということで、ネットワークの帯域を食いつぶすだけのベンチマークソフトをでっちあげてみた。



単に、TCPでつないで、全力でごみデータを送受信するだけ。よくあるベンチマークソフトで、今のところ何の独創性もない。

ところで、ここで1つ気になったことがある。とりあえず通信できるようになったところで、2台のマシンをつないでつながるか試したところ、なんか通信速度が安定しない。理屈の上では、上り下りともに1Gbps程度は出るはずなのだが、なんか、送信と受信のどちらか一方の帯域が低い。



上記だと、送信は概ね上限に達しているように見えるが、受信は79.4Mbpsで、理論値の10%にも達していない。また、常に送信は早くて受信が遅いということではなく、時々この力関係が入れ替わったりするから、よくわからない。

ロジック上は、TCPのクライアントとサーバとで、送受信ともに同じようなことをやっているわけで、とりわけ原因があるように見受けられない。まさかNICのチップが蟹さんだからなのか? と思って、いろいろやってみた。

まず構成は下記のようになっている。



PC1をクライアント側として使用する。こいつはオンボードのINTEL NICを使っておく。

PC2をサーバ側として、INTEL NICとRealtek NICを切り替えて使う。

そのうえで、HUBの間にかます場合とクロスで直結する場合を試してみる。

■パターン1 INTEL - Realtek HUBあり

PC1側



PC2側



理想的な性能が得られていない。


■パターン2 INTEL - INTEL HUBあり

PC1側



PC2側



なぜか予想以上に性能が出ない。速度的に、半二重として認識されているのか?

■パターン3 INTEL - Realtek 直結

PC1側



PC2側



さっきよりはいいが、やっぱり理論値には遠い。


■パターン4 INTEL - INTEL 直結

PC1側



PC2側



理論値に近い速度が出ている。

しかし、やっている間に気づいたが、何回か抜き差しすると、ちょっと性能が落ちたり上がったりする。Realtekでも、理論値程度の速度が出るのではないか? と思って何回かやってみたが、理想的な結果は得られなかった。

とりあえず、原因も対処法もまだ調べてない。何はともあれ、PlanexのHUBがかなりできない子だということだけはわかった。

フィードバックHUBのログアウトの方法

2017/04/15

下らないことに気が付いたから書いておく。

Windows 10でMicrosoftにゴルァを入れるのにフィードバックHUBというソフトを使うことができるのだが、これ、一度ログインするとログオフする術がない。

1.フィードバックHUBを起動すると、サインインとやらを求める画面が表示される。



2.サインインする方法を聞かれるので、とりあえずMicrosoftアカウントを選択して「続行」を押下する。



3.ユーザ名とパスワードを聞かれるので入力してやり、「サインイン」ボタンを押下する。



4.苦情を入力できる画面になる。





ここで問題になるのは、この画面をいくら探してもログオフする方法が見当たらないということである。なおかつ、フィードバックHUBを一度閉じて再起動すると、ログインの確認が行われずにいきなり、上記の画面が表示されてしまう。

気持ち悪いことこの上ない。

だから、ログオフする方法を探ってみた。

5.まず、フィードバックHUBの合同記号(「≡」←これ)を選択する。すると、左半分の文字が消える。(背景色と同じで区別がつかないが、おそらくメニューを表示する領域が広がるのだと思われる)



6.画面左下の、歯車の左側の領域を選択する。まさに衝撃的な設計だが、この何もない領域は選択可能なボタンとして機能するらしい。



7.サインアウトと書いてある文字を選択する。明らかに意図的なのだろうが、あえてわざわざ判読しづらい色で書かれているが、これは選択可能なオブジェクトである。右下に見えている「閉じる」ボタンに惑わされてはいけない。



8.無事にログオフできた。



フィードバックHUBを再起動しても、何も聞かれずいきなりログインするとかいうことがなくなる。

だが、上記の画面で「サインイン」ボタンを押下すると、下記のように、アカウントがモロ出しになってしまう。マワシの外れた力士がごとくだ。



しかも、この画面で「続行」ボタンを押下すると、パスワードを聞かれることもなくログインしてしまう。全く意味がない。

なので、アカウントの情報を削除する必要がある。

9.スタートメニューから「設定」を起動する。


10.「アカウント」を選択する。



11.左側のメニューから「メール&アプリのアカウント」を選択する



12.他のアプリで使われるアカウントというところに表示されている、さっき自分がログインするときに使ったアカウントを選択し「取り出す」ボタンを押下する。



「取り出す」ってなんだよ。

13.確認のメッセージが表示されるので「はい」を選択する。






14.さっきと同じく、フィードバックHUBを起動すると、サインインを求める画面が表示される。



15.上記で「続行」ボタンを押下しても、アカウントが表示されなくなる。




Creators Updateを入れてみた

2017/04/06

Windows 10のCreators Updateなるものが公開されたらしいので、失業者らしく暇つぶしにアップデートしてみた。

Creators Updateで気になるのは高DPIの対応が改善されたらしいということなので、まずはその観点で調べてみる。

俺が思う、高DPI対応がなってないソフトの筆頭がOpenOfficeなわけだが、実際起動してみると、こんなん悲しい表示になっている。



画面全体がぼやけて、眼鏡をはずして世界を見ているかのごとくだ。前向きにとらえればウインドウ全体にアンチエイリアシングがかかっているという……わけはない。

なので、Windows 10 アップグレードアシスタントという、いまだかつて役に立った事がないソフトを起動して、そこからアップデートを試みる。アップデートした後、もう一度OpenOfficeを起動する。



だが、何も変わっていないように見受けられる。これは、明示的に設定してやらなければ有効にならない機能らしい。
ということで、OpenOfficeのEXE本体を見つけ出してきて、プロパティの値を変えてみる。





そうすると、なんとなく表示がきれいになったように見える。



※というか、ここのblogにキャプチャした絵をアップロードすると、勝手にサイズを変更されて、ぼやけ具合の変化がわからなくなるから困る。こうしたらちょっとはわかりやすいだろうか?



だが、重大な問題がある。ウインドウを移動したり、ウインドウのサイズを変更しようとしたときに表示されるウインドウ枠が位置がむちゃくちゃである。



これは、ウインドウのタイトルバーをつかんで移動しようとしたときの絵だ。キャプチャに映っていないが、マウスカーソルは灰色のタイトルバーの上から少ししか動かしていない状態だ。そもそも、ウインドウの位置とサイズを同時に変えるすべはなく、通常の操作だけでこの画面を得ることは不可能であり、明らかに動きがおかしい。

まぁ、日本語で言ったところでアメリカ人相手に通じるとは思えず、なんの意味もないのだろうが、とりあえずフィードバック Hubというソフトから報告だけは上げておいてみた。


F#とXAMLでブツをナニすることを考える

2017/04/04

前回の投稿が5月29日なので、およそ10ヶ月ぶりの更新なのだが、この間いろいろあって、ついに仕事を辞めてしまった。これ以上はもう耐えられそうにない。生活には困るが、後悔はない。

というメモ書きと、もう1つ。いまさらながら、F#とXAMLでソフトを作る手順を調べたから、その結果だけ残しておく。

まず、F#でWPFとかXAMLで作る方法を検索すると、FsXamlという追加のパッケージみたいなやつを入れて作れ、といっているサイトがいくつかヒットする。

まぁ、個人でやっているだけだし、著作権て何ですか? おいしいんですか? という程度の認識しかないのだから、別にドコにナニをツッっこんでアッーなことをしてもいいのだが、やっぱり、得体の知れないものを無暗やたらに使うのは気分がよくない。

というか、最近?では、業務システムの開発でそれをやる奴が多くて困る。「このライブラリがないと動きません(キリッ ライセンス?サポート? 知りませんね。それは僕の仕事ではありません。気になるのなら、あなたが何とかすればいいんじゃないですかw?」とのたまい、GPL汚染がっつりなコードを製品に混入しようとする馬鹿が本気でいるから困る。もう、そういう尻拭いをするのに疲れた。

と、余談はいいとして、そもそも、Visual Studio 2017になってからなのかどうかは知らないが、なんか、プロジェクトの作成時にオンラインでテンプレートを検索してもFsXamlなるものが出てこないし、2015で登録してやってみても、なんかちゃんとコンパイルできないし。

ということで、すべて自前で対応する方法を模索してみた。

(1)まずは、新規にF#のプロジェクトを作成する。



コンソールアプリケーションを指定しているが、気にしない。後で変更する。

(2)参照設定を変更する
いきなりだが、必要なライブラリを追加する。少なくとも以下のものが必要。
・FSharp.Core
・mscorlib
・PresentationCore
・PresentationFramework
・System
・System.Core
・WindowsBase

このうち、PresentationCore・PresentationFramework・WindowsBaseの3つはデフォルトでは登録されていないから、明示的に指定してやる必要がある。





(3)アプリケーションのプロパティを変更する
出力の種類として「コンソール アプリケーション」という指定になっているものを、「Windows アプリケーション」に変更する。






ターゲット F# ランタイムとか、対象のフレームワークとか、気になる項目が多数あるが、たぶん、なんでもいい。

(4)XAMLのコードを追加する
ファイルは適当にプロジェクトに追加すればいい。

内容はこんな感じにしておく。



コードを示すとこんな感じ。

<Window
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="aaaa" Height="262" Width="500" >
<Canvas HorizontalAlignment="Left" Height="131" Margin="21,19,0,0" VerticalAlignment="Top" Width="237" >
<Button x:Name="button1" Content="Button1" Height="32" Canvas.Left="6" Canvas.Top="21" Width="97" />
<Button x:Name="button2" Content="Button2" Height="35" Canvas.Left="118" Canvas.Top="21" Width="119" />
<TextBox x:Name="TextBox1" Height="74" Canvas.Left="72" Canvas.Top="96" Width="103" />
</Canvas>
</Window>

あとここで、追加したXAMLのファイル(ここではMainWindow.xamlとする)のプロパティで、ビルドアクションという項目で「Resource」、出力ディレクトリにコピーという項目で「常にコピーする」を選択しておく。これを忘れると動かない。

(5)F#のコードを追加する。



コードを示すとこんな感じ

module MainApp
open System
open System.Windows
open System.Windows.Controls


//////////////////////////////////
// ウインドウ全体を示すクラス

type CMainWindow() =
   
    // XAMLのコードをリソースからロードする
    let m_Window =
        Application.LoadComponent(
            new System.Uri( "/FsTest3;component/mainwindow.xaml", UriKind.Relative )
        ) :?> Window    // System.Windows.Windowクラスに動的ダウンキャスト


    // コントールのオブジェクトを取得する
    let Button1 = m_Window.FindName( "button1" ) :?> Button
    let Button2 = m_Window.FindName( "button2" ) :?> Button
    let TextBox = m_Window.FindName( "TextBox1" ) :?> TextBox


    // main関数でRunしてやるために、Windowオブジェクトを公開している
    // このクラスのメンバでRunするようにすれば、公開する必要はない気がする

    member this.TheWindow with get() = m_Window


    // ウインドウ全体の初期化
    member this.Initialize =
        // ボタンのイベントハンドラを追加している
        // 単にCMainWindowのメンバを呼び出すようにしているだけ

        Button1.Click.AddHandler ( fun sender e -> this.OnButton1_Click sender e )
        Button2.Click.AddHandler ( fun sender e -> this.OnButton2_Click sender e )


    // ボタン1の押下時に呼び出される
    member this.OnButton1_Click sender e =
        TextBox.Text <- "OnButton1_Click"


    // ボタン2の押下時に呼び出される
    member this.OnButton2_Click sender e =
        TextBox.Text <- "OnButton2_Click"


//////////////////////////////////
// エントリポイント

[<STAThread>]
[<EntryPoint>]

let main(_) =
    // ウインドウのクラスを作って・・・
    let WinObj = new CMainWindow()

    // 初期化して・・・
    WinObj.Initialize

    // 実行してやる
    ( new Application() ).Run( WinObj.TheWindow )


ダサい。

コードとデザインの分離性とかなんとか考えると、Bindingとかいう方法を使った方がいいらしいのだが、それだとどうにもうまく実装することができなかった、というか、よくわからなくて面倒になった。

F#のコードとXAMLのデザインが分離されてないとか、いちいちイベントのハンドラを登録しないといけないとか、いろいろな面倒はあるが、それは結局、今までWin32APIやMFCでも泥臭くやってきたことだと思えば、まぁいいかという気がする。

1ミリも進歩がないけどな!


(6)実行する



こんなんでも、一応ボタンを押すとテキストボックスに文字列が設定されて、想定通りに動作していることが伺える。

あと、上のプログラムだと、CMainWindowの定義とmainの定義をひとまとめに書いてしまったが、俺の趣味からすればファイルを分けて作りたくなる。

つまり、XAML+C#の場合と同じように、XAMLのファイル1つに対してF#のソースが1本あって、かつ、その中にウインドウを管理するクラスが1つ定義されていれば、雰囲気だけでもMFCで作ってきたのと同じ枠組みで考えられるのではないかという気がする。

まぁ、1ミリも進歩がないけどな!