読者です 読者をやめる 読者になる 読者になる

matarilloの雑記

GitHubに公開したソフトウェアなどについて書きます。

C# (ASP.NET Core) でもisuconやってみたい

えーと今日はもう12/28ですが、この記事は .NET Core Advent Calendar 2016の10日目の記事 兼 ASP.NET Advent Calendar 2016 の17日目の記事だということにさせてください。。。

isuconというのは「いい感じにスピードアップコンテスト」の略です。公式ブログによれば

ISUCONとは

お題となるWebサービスを決められたレギュレーションの中で限界まで高速化を図るチューニングバトル、それがISUCONです。過去の実績も所属している会社も全く関係ない、結果が全てのガチンコバトルです。

というものです。雑に捕捉すると、既定の時間内に、与えられたサーバー、ミドルウェア、アプリケーションをチューニングして、時には(レギュレーションの範囲内で)全とっかえして、ベンチマークのスコアを上げるという大会です。

アプリケーションは運営側が参考実装したものが提示されます。直近の大会(isucon6)のオンライン予選では、Perl/Ruby/Python/PHP/Node.js/Go/Scalaでの実装が提示されていましたが、実際に参加したチームを見るとC++やJavaで再実装したチームがあったりするわけです。

まあそんな感じで楽しそうなので、.NET Coreで参加できないかなーなんて思ったりするわけです。

過去問はgithubで公開されている上、matsuuさんがいろんな環境用にportしています。とりあえず今回はisucon4の予選問題でちょっと試してみました……が、なかなか時間がかかりました。

matsuuさんが作成されたAnsible Playbookをベースにしたのですが、こちらはCentOS6ベース。ところが.NET CoreはCentOS7.1以降をサポートとあります。「と言ってもCentOS6でビルドすれば動くんじゃ?」と思って試してみたら、まあそんな簡単なものではなく。結局あきらめてCentOS7ベースにしました*1。その際に、ちょっとうまく動かなかったところがあったので、フォークして雑に上書きしました

インストールには CentOS 7 Minimal ISOを使いました。

rootでログインして、以下、雑に上書きした https://github.com/matarillo/ansible-isucon を動かします。

$ yum install -y epel-release git
$ yum install -y ansible
$ git clone https://github.com/matarillo/ansible-isucon.git
$ cd ansible-isucon/isucon4-qualifier
$ ansible-playbook -i local playbook.yml

もし、途中の 02_supervisord.yml あたりで Could not find the requested service "'supervisord'": といったエラーが出てたら、systemctl daemon-reloadを実行した上で、ansible-playbookを再実行してみてください。(追記: ansible側を修正して対応しました。)

さて、このAnsible Playbookを動かすと、isucon4の公式リポジトリをフォークして雑にASP.NET MVC Coreアプリケーションを追加した https://github.com/matarillo/isucon4 が組み込まれます……が、まだ中途半端な出来です。

  • .NET Core SDK 1.0 Preview 4 build 004233 を手で入れてください (追記: ansible側を修正して対応しました。)
  • .NET Coreアプリケーションを手で起動してください

前者はもう単純に手抜きです。時間があったら対応したいです。(追記: ansible側を修正して対応しました。)後者は、公式の仕組み (supervisord) でサービス化しようとしたのですが、残念ながら500エラーが解消できませんでした。こちらも対応したいですが、正式版リリースとどっちが早いかな…

ともあれ、.NET Coreアプリケーションを動かすには、ISUCON4 予選当日マニュアル を参考に、

  • supervisord を止める
  • /etc/supervisord.conf を修正して、すべてのアプリケーションautostart=false にする
  • supervisord を再開する

とした後*2

$ cd webapp/csharp
$ dotnet restore
$ dotnet run

でアプリケーションを動かします。

動いたかどうかはブラウザからポート80*3にアクセスするのが一番いいのですが、CentOS 7の初期状態ではファイヤーウォールがポート80をブロックしているはずなので、『CentOS7のfirewalldをまじめに使うはじめの一歩(systemdも少し)』 などを参考にポートを開けましょう。

さて、この状態で動いているアプリケーションですが、公式の参考実装を移植した体になっているので、チューニングは一切されていません。

https://github.com/matarillo/isucon4/tree/master/qualifier/webapp/csharp

  • Controllers
    • HomeController.cs ... Index, Login, MyPage, Reportの4つのアクションを実装。ロジック自体は Logicクラスに移譲
  • Infrastructure
    • IDbFactory.cs ... DbConnectionのファクトリーインターフェース
    • MySqlDbFactory.cs ... MySql.Data に依存した実装
  • Models
    • Logic.cs ... アプリケーションロジックの本体。データベースアクセスはDapperを利用。
    • LoginLog.cs ... login_log テーブルの行に対応するクラス
    • Threshold.cs ... 環境変数で与えられる「ユーザーがロックされるログイン失敗回数」「IPアドレスがBANされるログイン失敗回数」に対応するクラス
    • User.cs ... users テーブルの行に対応するクラス
  • Views
    • Home ... Index, MyPageのcshtmlテンプレート
    • Shared ... テンプレートの共通レイアウト
  • Program.cs ... アプリケーションサーバー設定(png/cssの位置指定、ポート8080の利用)
  • Startup.cs ... ASP.MVC Coreのルーティング、DIの設定など

私の環境でベンチマークを動かしてみたら、こんな感じでした。

csharp

Pythonの参考実装だとこんな感じ。

python

ほぼ差がありません。

アプリケーションに手を入れるとしたら、データアクセスが中心でしょうね。

*1:6と7ではいろんなところの仕組みが違うのですが、無視しました

*2:もしアプリケーションのプロセスがまだ生きていたら killやpkillで強制終了させる

*3:リバースプロキシとしてnginxが動いている

シンタックスハイライトにF#やPowerShellが追加されたらしい

はてなブログ開発ブログでの公式発表はまだだけど、 お題「シンタックス・ハイライト機能で対応してほしい言語」 の結果ということらしい。

実験した感じだと、PowerShellについては ps1 で、F#なら fsharp を指定すればいいみたい。

ps1

param([switch]$WhatIf = $false)

if($PSVersionTable.PSVersion.Major -lt 2) {
    Write-Warning "posh-git requires PowerShell 2.0 or better; you have version $($Host.Version)."
    return
}

if(!(Test-Path $PROFILE)) {
    Write-Host "Creating PowerShell profile...`n$PROFILE"
    New-Item $PROFILE -Force -Type File -ErrorAction Stop -WhatIf:$WhatIf > $null
}

if(!(Get-Command git -ErrorAction SilentlyContinue)) {
    Write-Warning 'Could not find git command. Please create a git alias or add %ProgramFiles%\Git\cmd to PATH.'
    return
}

$installDir = Split-Path $MyInvocation.MyCommand.Path -Parent
if(!(. (Join-Path $installDir "CheckVersion.ps1"))) {
    return
}

# Adapted from http://www.west-wind.com/Weblog/posts/197245.aspx
function Get-FileEncoding($Path) {
    $bytes = [byte[]](Get-Content $Path -Encoding byte -ReadCount 4 -TotalCount 4)

    if(!$bytes) { return 'utf8' }

    switch -regex ('{0:x2}{1:x2}{2:x2}{3:x2}' -f $bytes[0],$bytes[1],$bytes[2],$bytes[3]) {
        '^efbbbf'   { return 'utf8' }
        '^2b2f76'   { return 'utf7' }
        '^fffe'     { return 'unicode' }
        '^feff'     { return 'bigendianunicode' }
        '^0000feff' { return 'utf32' }
        default     { return 'ascii' }
    }
}

$profileLine = ". '$installDir\profile.example.ps1'"
if(Select-String -Path $PROFILE -Pattern $profileLine -Quiet -SimpleMatch) {
    Write-Host "It seems posh-git is already installed..."
    return
}

Write-Host "Adding posh-git to profile..."
@"

# Load posh-git example profile
$profileLine

"@ | Out-File $PROFILE -Append -WhatIf:$WhatIf -Encoding (Get-FileEncoding $PROFILE)

Write-Host 'posh-git sucessfully installed!'
Write-Host 'Please reload your profile for the changes to take effect:'
Write-Host '    . $PROFILE'

fsharp

open Suave
open Suave.Http
open Suave.Http.Applicatives
open Suave.Http.Successful
open Suave.Web

let app =
  choose
    [ GET >>= choose
        [ path "/hello" >>= OK "Hello GET"
          path "/goodbye" >>= OK "Good bye GET" ]
      POST >>= choose
        [ path "/hello" >>= OK "Hello POST"
          path "/goodbye" >>= OK "Good bye POST" ] ]

startWebServer defaultConfig app

シンタックス・ハイライト機能はソーシャルコーディングで拡充したほうがよいかも

お題「シンタックス・ハイライト機能で対応してほしい言語」

正直にいえば、はてなgithubリポジトリーか何かにシンタックスハイライトのとこだけ公開してもらって、みんなでプルリクぶんなげるほうがいいと思うんだよね。

はてなの中の人が興味ない言語とか、世の中にはたくさんあるわけで。

参考までに、Qiitaは Pygmentsを 使ってる ことがはっきりしてるんで、気に入らなければコントリビュートすればいいんだよね。

まあ、それはそれとして、サポートを希望する言語として F# を挙げておくね。 (←そもそもキーワードリンクもされてない。Elixirのパイプライン演算子に影響を与えた言語だよ!)

指定するのはファイルタイプってなってるけど、C#cs だから、F#は fs になるのかな。 どうせなら、csharpとかfsharpでも対応されるといいのに。

ファイルタイプ cs を指定したサンプル。(C# もVer6など最新バージョンに対応するといいのにね)

public async Task<int> Hoge() {
    await Task.Delay(1000);
    return 12;
}

ファイルタイプ fs を指定したサンプル。これがハイライトされないんで、現状はしかたなく ocaml を指定してごまかしている。

open Suave
open Suave.Http
open Suave.Http.Applicatives
open Suave.Http.Successful
open Suave.Web

let app =
  choose
    [ GET >>= choose
        [ path "/hello" >>= OK "Hello GET"
          path "/goodbye" >>= OK "Good bye GET" ]
      POST >>= choose
        [ path "/hello" >>= OK "Hello POST"
          path "/goodbye" >>= OK "Good bye POST" ] ]

startWebServer defaultConfig app

できれば、はてなブログじゃなくてはてなグループの方もアップデートしてもらえると、そっちにF#のコードを載せるときにうれしいのだけど。

(追記).NET/Windows系技術者から見ると、F#もPowerShellも、まあずいぶん放置されてるという感じで、たぶんはてなの中の人にはこれらのユーザーは存在しない、少なくともほっといていいレベルと思われてると理解していますよ。 他にもBooNemerleなんてのも、Pygmentsにはあるけどはてなにはない(そもそも言語として認識されてる気がしない).NET系言語ですね。まあPygmentsにあるからといって、さすがにVisual FoxProは対応しなくていい気がしますが。

割と些末なI18N活動

こないだ2件ほどPRを出したのでそれについてメモしておきます。

StephenStrickland/GcmSharp i18N: add a support for multi-byte characters.

WebRequest.ContentLengthに文字列のLengthを設定してたので、それはSystem.Encoding.UTF8.GetByteCount()を使ってねというだけのもの。受け入れられてマージ済み。

dahlbyk/posh-git Allow to use UTF8 characters on a branch name

posh-gitでブランチ名に日本語を使うとプロンプトが化けるので、git statusコマンドを発行する前後で[Console]::OutputEncodingをutf8にしてまた戻すというダーティハック。

実はこれ、良い解決法ではぜんぜんなくて、PowerShellを操作するとすぐに日本語のところがおかしくなる。(というか、マルチバイト文字の出力がダブってしまう)

f:id:matarillo:20151006210604p:plain

上の状態でEnterを2回たたくと、下の状態になる。

f:id:matarillo:20151006210603p:plain

(Warningが出てるのは、フォントがラスターフォントのままだったからです)

ともあれPRを元に議論が開始して、よりよい解決法が見つかればという思いでPRを送ったのだけど、問題が伝わらなかったのか、今のところ放置されている。後日またPRを更新したいなと思う。

coreclr Book of the Runtime日本語訳プロジェクトに協力中です。

coreclr関係もろもろ - ものがたり

そんなわけで、coreclrやらcorefxやらroslynやらの調べ物をしようと思って日々過ごしているわけですが、最近気付いたもので、coreclrのリポジトリの中に、"Book of the Runtime" (BotR) と呼ばれるドキュメント集があって、.NETランタイムの内部設計についていろいろ解説してあるのです。これはなかなか興味深い。ランタイムの各コンポーネントの担当者がまとめた文章を集めたものであるらしく、コードの詳細までは踏み込まないけど、内部実装の理解がそれなりに得られるものだと思います。

というわけで、少しずつ読み解きながら翻訳してみたいと思っています。翻訳したドキュメントは、このリポジトリに置いておきたいと思います。翻訳を手伝っていただける方は随時募集しています(既にわたしだけのプロジェクトではないです)。

というわけで翻訳を手伝っています。

github.com

とりあえず現在は、型システムのチャプターをやっつけて、スレッドのチャプターに着手しています。

適当な日本人の名前や住所をランダムに作れる gimei を C# に port した。

willnet/gimei - GitHub

gimei は、日本人の名前や、日本の住所をランダムに返すライブラリです。テストの時などに使います。似たようなライブラリにfakerがあります。fakerはとても優れたライブラリで、多言語対応もしていますが、ふりがな(フリガナ)は流石に対応していません。gimei ふりがな(及びフリガナ)に対応しています。

Big Sky :: 適当な日本人の名前や住所をランダムに作れる gimei を golang に port した。

オリジナルは ruby gems です。


というわけで、gimeiおよびgo-gimeiを真似してC#で書いてみました。

matarillo/dot-gimei - GitHub

.NET port of gimei

NuGet Gallery | dot-gimei

PM> Install-Package dot-gimei

gimeiおよびgo-gimeiと違うとこ:

  • データはDLLに埋め込んでしまいました。
  • そうはいっても差し替えたいこともあるだろうから、Gimei.Generatorクラスを作っておきました。こいつのコンストラクタTextReader を食わせてください。
  • IsMaleプロパティとIsFemaleプロパティは bool? 型です。(ざわ……ざわ……)
  • 新しく Gender プロパティを追加しました。型は GenderIdentity 列挙体です。
    • 政治的に使いにくいAPI
using System;
using DotGimei;

class Program
{
    public static void Main(string[] args)
    {
        var name = Gimei.NewName();
        Console.WriteLine(name);                // 斎藤 陽菜
        Console.WriteLine(name.Kanji);          // 斎藤 陽菜
        Console.WriteLine(name.Hiragana);       // さいとう はるな
        Console.WriteLine(name.Katakana);       // サイトウ ハルナ
        Console.WriteLine(name.Last.Kanji);     // 斎藤
        Console.WriteLine(name.Last.Hiragana);  // さいとう
        Console.WriteLine(name.Last.Katakana);  // サイトウ
        Console.WriteLine(name.First.Kanji);    // 陽菜
        Console.WriteLine(name.First.Hiragana); // はるな
        Console.WriteLine(name.First.Katakana); // ハルナ
        Console.WriteLine(name.IsMale);         // false

        var male = Gimei.NewMale();
        Console.WriteLine(male);          // 小林 顕士
        Console.WriteLine(male.IsMale);   // true
        Console.WriteLine(male.IsFemale); // false

        var address = Gimei.NewAddress();
        Console.WriteLine(address);                     // 岡山県大島郡大和村稲木町
        Console.WriteLine(address.Kanji);               // 岡山県大島郡大和村稲木町
        Console.WriteLine(address.Hiragana);            // おかやまけんおおしまぐんやまとそんいなぎちょう
        Console.WriteLine(address.Katakana);            // オカヤマケンオオシマグンヤマトソンイナギチョウ
        Console.WriteLine(address.Prefecture);          // 岡山県
        Console.WriteLine(address.Prefecture.Kanji);    // 岡山県
        Console.WriteLine(address.Prefecture.Hiragana); // おかやまけん
        Console.WriteLine(address.Prefecture.Katakana); // オカヤマケン
        Console.WriteLine(address.Town);                // 大島郡大和村
        Console.WriteLine(address.Town.Kanji);          // 大島郡大和村
        Console.WriteLine(address.Town.Hiragana);       // おおしまぐんやまとそん
        Console.WriteLine(address.Town.Katakana);       // オオシマグンヤマトソン
        Console.WriteLine(address.City);                // 稲木町
        Console.WriteLine(address.City.Kanji);          // 稲木町
        Console.WriteLine(address.City.Hiragana);       // いなぎちょう
        Console.WriteLine(address.City.Katakana);       // イナギチョウ

        var prefecture = Gimei.NewPrefecture();
        Console.WriteLine(prefecture); // 青森県
    }
}

「Web系企業が教えてくれないWindowsではじめるWebプログラミング」の想定読者

SlashDotのACさんたちとか、Web系企業のひととかとは、前提が違うと思うんですよ。 スタートとゴールが未定義だと議論が発散するから意味ないんですよね。 なので、いま私が念頭に置いていることをメモっておきますよ。

スタート

  • PCのWebブラウザ(種類は問わない)で不自由なくWebブラウジングできる人
  • ぐぐれる人(Web検索して情報を得ることができ、かつその情報の有効性について何らかの判断ができる)が望ましい
  • 何らかの手続き型言語で何らかのプログラムを書いたことあって、ループとか関数とかコンパイルとか黒い画面とかそういうので怯まない人が望ましいけど、必須ではない(そのあたりから解説してもいい)

ゴール

具体的には、以下のようなことを一通りカバーする

  • ブラウザ(HTTPクライアント)とWebサーバがやっていることがHTTP/1.0くらいのレベルでイメージできる
  • HTMLとXMLの最大公約数的なシンタックス(開始タグ終了タグ属性コメントエスケープあたり、木構造の理解が望ましい)がわかる
  • 静的HTMLとCSSをすげえ簡単なレベルで書ける
  • anchorタグでリンクしている2-3枚のHTMLをlocalhost上の何らかのWebサーバに置いて、ブラウザからアクセスしてリンクをたどって確認できる
  • formタグのある静的HTMLと、それを受け取って動的HTMLを返す何らかのプログラムをlocalhost上の何らかのWebAppサーバに置いて、ブラウザからアクセスしてフォームにポストして確認できる(GET/POST両方やれるべき)
  • DOMがどういうものかイメージできる
  • JavaScriptが要求されてロードされて実行される流れがなんとなく把握できてる
  • bodyのloadとbuttonのclickあたりにJavaScriptイベントハンドラを仕掛けたHTML+JavaScriptを書ける
  • クッキーとセッション状態がどういうときに使われるか、簡単な例を説明できる
  • クッキーとセッション状態を使った単純なWebアプリケーションを書ける

また、以下のことは範囲外とする

  • データ永続化と取得
  • セキュリティ(これはインターネットに向けてサービスやアプリを公開する前に叩き込む必要があるけど、教えることが多くて困る)
  • 特定の言語プラットフォームやフレームワークを前提とする知識
  • HTML5などのモダンWeb標準
  • AltJSやモダンJavaScript