アジョブジ星通信

進捗が出た頃に更新されるブログ。

MPMアルゴリズムのNSDFの求めかた

結論だけ知りたい人向け

NSDF の式

\displaystyle n'_t(\tau) = \frac{2r'_t(\tau)}{m'_t(\tau)} \quad (0 \leq \tau < W)

m'_t(\tau) について、前から求めると

\displaystyle
m'_t(\tau) = \begin{cases}
    2r'_t(0) & (\tau=0) \\
    m'_t(\tau-1) - x_{t+\tau-1}^2 - x_{t+W-\tau}^2 & (0 < \tau < W)
\end{cases}

後ろから求めると

\displaystyle
m'_t(\tau) = \begin{cases}
    0 & (\tau=W) \\
    m'_t(\tau+1) + x_{t+\tau}^2 + x_{t+W-\tau-1}^2 & (0 \leq \tau < W)
\end{cases}

となるので、好きな方でやってください。

はじめに

ピッチ検出アルゴリズムについて検索すると、まぁ大体このページ(音声の波形からピッチを検出するアルゴリズム - まめめも)に行き着くと思うのですが、 NSDF のインクリメンタルな求め方について特に説明もなく、クワイン用の well compressed なプログラムしか書いていないので、基の論文であるところの『A Smarter Way to Find Pitch』に書いてある式を変形しながら、本当にインクリメンタルに求まるのか検証していきます。

ここでは Normalized Square Difference Function の略として NSDF と言っていますが、 McLeod 氏の 2008 年の論文では specially-normalized autocorrelation function で略して SNAC と呼んでいますね。まぁいいや。

続きを読む

さぁ fixed を捨てて Unsafe だ

f:id:azyobuzin:20170929224302p:plain

早すぎる最適化が好きな人のための C# 7 の有効活用ガイドです。

ある構造体をそのまま byte 配列に突っ込みたくなるとき、ありますよね?構造体ならメンバーに名前がついていて書きやすい、でも相手が byte 配列だから 1 バイトずつ手書きするしかないのか……?そんなときにおすすめの技を紹介します。

達成目標

例を用意しましょう。 X Window Systemプロトコルは C 言語などでクライアントを実装しやすいように、適度にアライメントされたデータをやりとりします。しかもエンディアンもクライアント側が指定することができるので、クライアントはまさに構造体を直接送受信することができます。そこで、クライアントが X サーバに接続して、最初に送信するメッセージを構造体を使って中身を用意し、 byte 配列に書き込むことを目標にしていきましょう。

(この目標設定は、ちょうど X クライアントを C# で書いていたからなのですが、完成したころに公開します。公開しないかもしれません。)
公開してあります → X11Client.cs

続きを読む

それでも僕はWineでMonoを動かしたかった

Wine 上で動く Windows アプリを制御するにはどうしよう?と考えた結果、制御する側も Wine 上で動かせば Windows 上と同じように操作できるじゃないか!と思いやっていった記録。残念ながら、結局 Docker 上で動かすことはできなかったので、僕の意志を引き継いでくれる方、もしくは先行研究がありましたら教えてください。

というわけで、 Docker で動かすのを断念するまでに得た知見をまとめておきたいと思います。

環境

  • Ubuntu 17.04
  • Wine 2.14 win32 prefix
  • Mono 5.2.0 x86

wine-mono?それともスタンドアロンの Mono?

wine-mono は Mono のコアライブラリ + .NET Framework になりすますための mscoree.dll という構成になっています。つまり .NET の exe ファイルを起動しようとすると Mono を使ってくれる、という構成になります。

それに対して、 mono-project.com からダウンロードできる Mono の Windows 版は mono.exe を使ってアセンブリを実行する形をとります。つまり Linux で使っている Mono とまったく同じです。

これらふたつは共存することができるので、間接的に .NET Framework を必要とするアプリには wine-mono でうまくやっておいてもらいつつ、 .NET アセンブリだと最初からわかっているものは mono.exe で起動するという使い方ができるでしょう。

さて、最初は wine-mono で全部なんとかしようと思っていたのですが、厄介な問題にぶち当たりました。 wine-mono はアセンブリの厳密名を検証するようですが、回避するために App.config に <bindingRedirect> を書いても読み込んでくれないようです。というわけで、基本的には mono.exe を使うべきだということがわかりました。

続きを読む

I/O待ちのためのTaskとバックグラウンド処理のためのTask

ポエポエ~~

の一連のツイートのまとめでもしておこうかと。

I/O 待ちのための Task

I/O 処理を OS に投げて、完了を待機するのを抽象化した Task を「I/O 待ちのための Task」と呼んでいきます。身近かつ純粋な I/O 待ちを行う Task の例として、 FileStream を FileOptions.Asynchronous オプションをつけて開いたときの ReadAsync が挙げられます。

例として、 ReadAsync を await したときの挙動を見てみましょう。

async Task FileReadSample()
{
    const int bufSize = 10;

    // FileOptions を指定するために省略できないオプション大杉
    using (var fs = new FileStream(
        "test.txt",
        FileMode.Open,
        FileAccess.Read,
        FileShare.ReadWrite,
        bufSize,
        FileOptions.Asynchronous))
    {
        var buf = new byte[bufSize];

        var readBytes = await fs.ReadAsync(buf, 0, bufSize);

        Console.WriteLine(readBytes);
    }
}

「FileReadSample 続き」がどこで実行されるかは、このメソッドがどんな SynchronizationContext で呼び出されたかによりますが、とにかく OS からの非同期処理完了をスレッドプールが管理している I/O 完了ポートで待ち受け、 Task の完了を通知して、継続タスクを実行するという動きをします。

このとき ReadAsync 単体で見ると、 I/O 完了ポート用のスレッドが足りていれば、新しいスレッドを作成することも、スレッドプールのキューに新たにタスクを追加することもありません。つまり「I/O 待ちのための Task」は OS の非同期処理 API をラップし、継続タスクへの通知を行うだけのものといえます。

ただし、 await は SynchronizationContext が特別な場合(UIスレッドなど)を除き、続きをスレッドプールのキューに追加します。これはコールスタックが大きくなるのを防ぐためらしいですが、それが嫌なら ContinueWith で TaskContinuationOptions.ExecuteSynchronously を指定することで、完了通知を受け取ったスレッド上でそのまま続きが実行されます(本当?)。

より詳しく: Async訪ねて3000里

スレッドプールの有効利用

I/O 待ちのための Task は、スレッドプールを有効的に利用するために使います。

スレッドプールは短時間で終わる処理が大量にあるときに効率良くスレッドを利用する手段ですから、 I/O 待ちでスレッドプールのスレッドをブロックしてしまうのはスレッドプールの効率を落としてしまいます。そこで I/O を非同期で行い、完了を待っている間にキューに溜まった他の処理を実行できるように、I/O 待ちのための Task を利用します。

逆に、スレッドプール内で動いている Task でバックグラウンド処理のための Task を await するのは得策ではありません。なぜなら、そのまま続行できる処理をわざわざスレッドプールのキューに入れることになり、切り替えコストが発生するからです。

スレッドプールを有効利用することが求められるのは、web アプリケーションとライブラリです。

まず、 web アプリケーションでは、リクエストごとの処理がスレッドプールのキューに追加されます。ここでスレッドをブロックするようなコードを書いてしまうと、十分な性能を出すために多くのスレッドが必要となってしまいます。スレッドプールに割り当てるスレッドが多くなりすぎると、メモリ使用量がどんどん増えていってしまいますし、スレッドの管理コストも上がってしまいます。このような理由から、I/O 待ちのための Task を利用すると必要なスレッド数を減らせて、リソースを削減できます。

次にライブラリですが、ライブラリはあらゆる .NET アプリケーションから使われるので、 web アプリケーションから使われることを考えると同じことが言えます。

バックグラウンド処理のための Task

UI スレッドのフリーズを防ぐため、または並列処理のために処理を分割したものを「バックグラウンド処理のための Task」と呼んでいきます。バックグラウンド処理のための Task は、 CPU で大量の計算を行うときに Task.Run などを使って作成します。

「~Async」メソッドの罠

「~Async」という名前がついているメソッドは、一般的なライブラリの場合 I/O 待ちのための Task を表していることが多いです。理由は「スレッドプールの有効活用」で示した通りです。

I/O 待ちのための Task を返すメソッドが実際に非同期で処理するのは I/O だけです。だから、 I/O が発生する前に重い計算をしていても、それはメソッドを呼び出したスレッドで実行されているかもしれません。もし、 GUI アプリケーションでそのようなメソッドを UI をフリーズさせずに実行したいならば、

var taskResult = await Task.Run(async () =>
{
    var fooResult = await FooAsync();
    // 後処理
});
// taskResult を UI に反映したり

といったように、バックグラウンド処理のための Task に変換する必要があります。

また、 Task を返すメソッドだからといって、必ず非同期処理が行われるわけではありません。例えば、条件によって I/O が発生しないメソッドは、条件を満たすときに完了済みタスクを返します。 await は Awaiter の IsCompleted が true のとき、すなわち完了済みタスクが返ってきたときには、現在のスレッドのまま処理を続行します。このことを覚えておかないと、「メソッド内の1回目の await で ConfigureAwait(false) を指定したから、2回目からは要らないよね」とか言って死にます。

バックグラウンド処理のための Task の乱用防止

スレッドを切り替えずに済む処理をわざわざ Task にする必要はありません。処理を別スレッドに移譲する必要があるのは GUI アプリケーションくらいでしょう。ライブラリ開発者はこのことを頭に入れて、 I/O に関する部分だけを TaskAsync メソッドとして公開するべきです。そして、 GUI アプリケーション開発者は必要に応じて自分で重い処理を Task で包むようにします。このようにすることで、スレッドプールを有効に利用したコードが書けるのではないかと思います。

TargetFrameworksからビルドできそうなフレームワークだけビルドする

これから1ヶ月以上ブログ書いてなかった。というわけで、これの続き。

この記事では PCL や Xamarin 用のアセンブリdotnet build でビルドできるようにしましたが、これだと MS の不自由アセンブリや Xamarin がインストールされていないとエラーになってしまうので、なんとかして警告レベルに落としたいので、やっていきます。

まずは対処前の状況。こんな感じのプロジェクトをビルドしていきます。

<TargetFrameworks>netstandard1.1;netstandard1.5;portable45-net45+win8+wpa81;monoandroid10;monotouch10;xamarinios10</TargetFrameworks>

https://github.com/CoreTweet/CoreTweet/blob/1d53cb7888eda55e681ed865538d95c0eb394da8/CoreTweet/CoreTweet.csproj

するとこう。

PCL がコケたところでビルドが止まってしまっていますね。というか並列ビルドしてくれよ。

では、 TargetFrameworks からビルドできなさそうなフレームワークを消していくコードを追加していきましょう。やり方を言葉で表すとこんな感じ。

  1. まとめてビルドするやつ(DispatchToInnerBuilds ターゲット)の実行前にフック
  2. 次の手順をすべてのフレームワークに実行(といっても netstandard や netcoreapp は絶対ビルドできるし、次の手順には失敗するので素通りさせる)
    1. GetReferenceAssemblyPaths タスクを叩いてみる
    2. 結果が空でないなら TargetFrameworks に追加する

以上を全フレームワークに実行します。

というわけで実際のコードはこちら。

<Target Name="CheckAvailableFrameworks" BeforeTargets="DispatchToInnerBuilds">
  <ItemGroup>
    <!-- _TargetFramework という名前は DispatchToInnerBuilds 内で使われているので、被らないようにアンダーバー増やした -->
    <__TargetFramework Include="$(TargetFrameworks)" />
    <!-- netcoreapp もあるならそれもちゃんと Condition に書きましょう -->
    <_OldTargetFramework Include="@(__TargetFramework)" Condition="!$([System.String]::Copy('%(Identity)').StartsWith('netstandard'))" />
    <_NewTargetFramework Include="@(__TargetFramework)" Condition="$([System.String]::Copy('%(Identity)').StartsWith('netstandard'))" />
  </ItemGroup>
  <!-- TargetFramework プロパティをセットして CheckCompilability を呼び出す -->
  <!-- TargetFramework プロパティがセットされていると、うまいこと TargetFrameworkMoniker プロパティがセットされるのを利用していきたい -->
  <MSBuild Projects="$(MSBuildProjectFile)"
           Targets="CheckCompilability"
           Properties="TargetFramework=%(_OldTargetFramework.Identity)">
    <!-- 結果をとりあえずアイテム化しておいて -->
    <Output TaskParameter="TargetOutputs" ItemName="_NewTargetFramework" />
  </MSBuild>
  <PropertyGroup>
    <!-- 強引にプロパティに変換する -->
    <TargetFrameworks>@(_NewTargetFramework)</TargetFrameworks>
  </PropertyGroup>
</Target>

<Target Name="CheckCompilability" Returns="$(CompilableFramework)">
  <!-- ContinueOnError="true" によってエラーを警告化 -->
  <!-- これで完全勝利 -->
  <GetReferenceAssemblyPaths TargetFrameworkMoniker="$(TargetFrameworkMoniker)"
                             RootPath="$(TargetFrameworkRootPath)"
                             ContinueOnError="true">
    <Output TaskParameter="ReferenceAssemblyPaths" PropertyName="_ReferenceAssemblyPaths" />
  </GetReferenceAssemblyPaths>
  <PropertyGroup Condition="'$(_ReferenceAssemblyPaths)' != ''">
    <!-- 成功したときだけ Returns に追加されるってわけ -->
    <CompilableFramework>$(TargetFramework)</CompilableFramework>
  </PropertyGroup>
</Target>

これで警告になりました。めでたしめでたし。

新しい csproj に負けないレガシーな心を持ち続けて云々

Sdk="Microsoft.NET.Sdk" なプロジェクトファイルで使える小ネタ集です。

NETStandard.Library への参照をいじる

PackageReference を何ひとつ書かなくても NETStandard.Library を勝手にダウンロードしてくるのは、手軽にコードが書けるという点ではいいかもしれないけれど、やはり無駄なパッケージ参照があるというのは許せないものなので、消していきましょう。

DisableImplicitFrameworkReferences というプロパティがあり、これを true にするとデフォルトの参照が消えます。デフォルトの参照は以下の通り(targets ファイルを漁るとでてくる)で、これらが全部消えます。つまり .NET Framework なら mscorlib だけ、 .NET Core なら無です。

  • .NETStandard (Microsoft.NET.Sdk.DefaultItems.props)
    • NETStandard.Library 1.6.1
  • .NETCoreApp (Microsoft.NET.Sdk.DefaultItems.props)
  • .NETFramework (Microsoft.NET.Sdk.BeforeCommon.targets)
    • System
    • System.Data
    • System.Drawing (マジかよ)
    • System.Xml
    • System.Core
    • System.Runtime.Serialization
    • System.Xml.Linq
    • System.Numerics
    • System.IO.Compression.FileSystem

NETStandard.Library のバージョンは NetStandardImplicitPackageVersion プロパティを、 Microsoft.NETCore.App のバージョンは RuntimeFrameworkVersion プロパティを書き換えることで、自由に変更することができます。

netcoreapp1.0 をターゲットにすると Microsoft.NETCore.App のバージョンは 1.0.4 になるけど、 1.1.1 に変えても問題なく動くんだよな……。

サンプルコードとして、 NETStandard.Library を消して、 System.Console (とその依存パッケージ)だけを参照するプロジェクトです。

<Project Sdk="Microsoft.NET.Sdk">

  <PropertyGroup>
    <TargetFramework>netstandard1.4</TargetFramework>
    <DisableImplicitFrameworkReferences>true</DisableImplicitFrameworkReferences>
  </PropertyGroup>

  <ItemGroup>
    <PackageReference Include="System.Console" Version="4.3.0" />
  </ItemGroup>

</Project>

Portable Class Library や Xamarin クラスライブラリをビルドする

プロジェクトファイルにちょっと手を加えれば、インストールされているフレームワークなら何でもビルドできます。たぶん。

TargetFramework, TargetFrameworkIdentifier, TargetFrameworkVersion、あとプロファイル指定が必要なら TargetFrameworkProfile というプロパティを設定すれば、勝手にアセンブリ解決してビルドしてくれます。

例えば、いにしえのフレームワーク軍を相手する portable40-net40+sl5+win8+wp8+wpa81 ならこんな感じ

<Project Sdk="Microsoft.NET.Sdk">

  <PropertyGroup>
    <TargetFramework>portable40-net40+sl5+win8+wp8+wpa81</TargetFramework>
    <TargetFrameworkIdentifier>.NETPortable</TargetFrameworkIdentifier>
    <TargetFrameworkVersion>v4.0</TargetFrameworkVersion>
    <TargetFrameworkProfile>Profile328</TargetFrameworkProfile>
  </PropertyGroup>

  <ItemGroup>
    <Reference Include="System" />
  </ItemGroup>

</Project>

mscorlib 以外は勝手に参照されないので、必要に応じて Reference を書いてあげる必要があります。

Xamarin も同様にできますが、ファサードアセンブリを使うために ImplicitlyExpandDesignTimeFacades プロパティを true にしておく必要があるのと、 Visual Studio 2017 からアセンブリの場所が変わったので、それに対する対応を入れる必要があります。

Visual Studio 2015 までは C:\Program Files (x86)\Reference Assemblies\Microsoft\Framework に Xamarin のアセンブリがありましたが、 2017 では C:\Program Files (x86)\Microsoft Visual Studio\2017\Community\Common7\IDE\ReferenceAssemblies\Microsoft\Framework になりました。 Microsoft.NET.Sdk では前者のパスを探しにいくので、 2017 だけがインストールされている環境ではアセンブリが見つかりません。

その対策が入っている Xamarin.Android.Sdk.props をパクりつつ、 Xamarin クラスライブラリをつくるプロジェクトファイルはこんな感じになります。

<Project Sdk="Microsoft.NET.Sdk">

  <PropertyGroup>
    <TargetFramework>monoandroid70</TargetFramework>
    <TargetFrameworkIdentifier>MonoAndroid</TargetFrameworkIdentifier>
    <TargetFrameworkVersion>v7.0</TargetFrameworkVersion>
    <TargetFrameworkRootPath Condition="'$(VsInstallRoot)' != ''">$(VsInstallRoot)\Common7\IDE\ReferenceAssemblies\Microsoft\Framework\</TargetFrameworkRootPath>
    <ImplicitlyExpandDesignTimeFacades>true</ImplicitlyExpandDesignTimeFacades>
  </PropertyGroup>

  <ItemGroup>
    <Reference Include="Mono.Android" />
  </ItemGroup>

</Project>

Xamarin Android プロジェクトで、このプロジェクトを参照に加えて実行してみましたが、問題なく動作しました。

VsInstallRoot プロパティは Visual Studio についている MSBuild でしか設定されていないので、 .NET Core SDKMSBuild でビルドした場合には 2015 までのアセンブリの場所を見に行きます。

MySQL Connector/.NET の TreatTinyAsBoolean のバグと Dapper

MySQL Connector/.NET、長いので MySql.Data と呼びます。

バグについて

MySql.Data の接続文字列で使用できるオプションとして、「TreatTinyAsBoolean」または「Treat Tiny As Boolean」というものがあります。これはデフォルトで有効になっていて、このオプションが有効のときは TINYINT(1) のカラムを Boolean 型として扱います。

で、常に Boolean として扱ってくれるかというとそうではないという問題があります。 TINYINT(1) 型のカラムに null が入っていると、それ以降のレコードは Boolean ではなく SByte になります。これによってありがた迷惑な機能から、ただの迷惑な機能に昇格しましたね。おめでとう。

続きを読む