tech.guitarrapc.cóm

Technical updates

Nature Remo / Nature Remo mini Remo-2W1 の接続安定を試みる

台風21号の風がいい環境音で作業が捗っています。

さて、外出先からエアコンを消す、家に着く前に少しエアコンをつけておきたいなどというシーンがあります。 また、Google Home を使っていると電気機器の動作も声で制御したくなります。

ここ2年ほどNature Remoを使ってこれらの機器を操作していたのですが、ここ数か月ほど15min~8h経つと機器を操作できなくなっているのでそろそろ対処をします。

目次

環境

現在、Nature Remo / Nature Remo mini Remo-2W1 の2つを使っています。 もともと Nature Remo で発生していた時に、この機器だけの問題かと思い切り分けのためにNature Remo mini Remo-2W1を追加しました。(という題目でおもちゃを増やしました)

Amazon | Nature Remo | Nature, Inc.

Amazon | Nature スマートリモコン Nature Remo mini Remo-2W1

無線は、Aterm WG1400HPをブリッジ動作させて、2.4GHz でRemoとつないでいます。

ルータ/DHCPサーバーに、Yamaha RTX810 を利用しています。

症状

寝て起きたり、帰ってくるとRemo が赤く早い点滅を繰り返しており、RemoアプリやGoogle Homeの操作で機器を操作できなくなります。 Remoを再起動するとしばらく使えるのですが、いつのまにかまた使えなくなっているのが毎日続いています。

公式FAQを見る

公式は大事です。Remo のFAQから、この症状はNature Remoがクラウドと通信できていない状態とあります。

Nature Remoの赤点滅には2種類あります。赤く遅い点滅(1秒間に1回の点滅)と赤く早い点滅(1秒間に数回の点滅)です。

Nature Remoが赤く早い点滅(1秒間に数回の点滅)になっている場合は、Nature Remoがクラウドと通信できていない状態になります。お使いのWi-Fiルーターの推奨接続台数が、ご家庭のWi-Fi機器以上かをご確認の上、以下の手順を順番にお確かめ下さい。

  1. 問題が発生した以前にWi-Fiルーターの各種設定を変更してないかをご確認下さい。
  2. Nature Remoを付属のケーブルとアダプターを使い再起動してください。
  3. Wi-Fiルーターを再起動してください。

引用: Q4-2. Nature Remoが赤点滅して正常に動作しない — Nature

https://nature.global/jp/faq/023nature.global

  • ルーターやWifiに変更を加えていません。
  • Remo再起動で一時的に解消しますが、毎日再発します。
  • Wifiやルーターの再起動しますが解消しません。

Twitter やググっても特にその症状で今困っている人はいないことから、Remo サーバーではなく自宅の環境に起因してそうです。 Google Home以外にも、うちの機器はすべてWifiでつなげていますがどの機器も問題がでていないので、Remo特有のようです。

もう少し定番設定がありそうなものですが、公式FAQちょっと貧弱すぎな感じがあります。

Remoやめて他のを使おうかと探したのですが、Remo以上に魅力的なものがないので対処します。

考えうる対策

ぐぐったところ、いくつか考えられそうです。

  1. SSID Stealth を解除する
  2. IPを固定する

準備実施します。

SSID Stealth を解除する

長い間ステルスなので、何故これが対策になるのかわかりませんが、とりあえず従ってもいいので従います。 が、効果ありませんでした。

セキュリティ対策に使っているわけではないのですが、どうもSSIDがブロードキャストされていると使おうとする人がいるのは確かなので、安定したらステルスに戻す予定です。

IPを固定する

Nature Remo はDHCPで取得するので、端末のmacアドレスに対して固定IPを割り当てるように切り替えます。 Atermをブリッジで動かしているうちの場合はRTX810 がDHCPサーバーです。

Remoのmacアドレスを調べてRTX810にコンフィグを入れましょう。(Web UIは対応していません)

Nature Remoはセットアップ済みなので、macアドレスはアプリから確認できます。

https://nature.global/jp/faq/042nature.global

https://nature.global/jp/faq/049nature.global

RTX810に対してコンフィグを入れます。 RTXでDHCPでmacアドレスに対応して固定IPを割り当てるときは、 dhcp scope bind [DHCP_SCOPE_ID] [IP_FROM_DHCP_RANGE] [MAC] の文法を使います。

ちなみに、dhcp scope bind [DHCP_SCOPE_ID] [IP_FROM_DHCP_RANGE] ethernet [MAC] はNature Remoで固定するには有効に機能しなかったので気を付けてください。

www.rtpro.yamaha.co.jp

DHCPの設定はこんな感じでいいでしょう。

dhcp service server
dhcp server rfc2131 compliant except remain-silent
dhcp scope 1 192.168.11.110-192.168.11.150/24
dhcp scope bind 1 192.168.11.141 aa:bb:cc:dd:ee:ff
dhcp scope bind 1 192.168.11.142 11:22:33:44:55:66

設定したら Nature Remoを再起動してIPを取りに行かせます。 syslog で、指定したmacアドレスに対して指定したIPが割り振られたことを確認します。

2019/10/12 23:17:35: [DHCPD] LAN1(port1) Allocates 192.168.11.141: aa:bb:cc:dd:ee:ff
2019/10/12 23:18:10: [DHCPD] LAN1(port1) Allocates 192.168.11.142: 11:22:33:44:55:66

経過

とりあえず投入して30min経ちましたが2台ともに安定しているように見えます。 一晩待って安定するか見てみましょう。

そういえば、今のGoogle Homeは影響ないのでIP固定していませんが、初期のGoogle Homeでも言われていたのを思い出しました。 Nature Remo以外の機器は何の影響もないので、DHCPで影響あるのは手間もありますが品質の違いを感じてちょっと残念です。

10/13 一晩経っても安定しているのでこれでよさそうです。

10/14 残念ながらだめなようです。赤い点滅はでないものの、アプリからの操作、Google Homeからの操作を行っても信号が発信されない。(発信するときに青い点滅があるのでわかる) 再起動で動くものの、繰り返し発生しているので他の原因がありそう。

追加対応

継続して発生するので、どうしたものかとぐぐっていたら、FaceBook Group にNature Remoのユーザーコミュニティがありました。

www.facebook.com

似たような相談がないか見ていると、YAMAHA SoundBar YAS-109 のコントロールアプリで不安定になるとの情報がありました。 確かにYAMAHA公式にもあり、自分も使っているので可能性はありそうです。

yamaha.custhelp.com

ということで、SoundBar Controller アプリを終了してNature Remoを再起動して様子を見てみましょう。

経過2

2日経過して、とりあえず安定しています。 Yamaha YAS-109 は使わない状態で放置.... 文鎮。

経過3

確定したので、Switch Bot に切り替えました。

経過4

SoundBar Controller Ver.1.0.6 で改善したらしい。もう移行済みだよ!

本件、2019年12月18日公開のSoundBar Controller Ver.1.0.6を適用いただくことで改善いたします。

servo で gRPC(MagicOnion)サーバーを公開する

2019/7/29 に servo に関する記事をみて、gRPC っていけるのかなということで MagicOnion で試していました。

qiita.com

記事にするのを忘れてたので書いておきます。

目次

結論

問題なくok

大事なのは、MagicOnion のサーバーを localhost で待つこと (127.0.0.1 とかはダメ)

ssh -R 12345:localhost:12345 serveo.net

サーバーの待ち受け

new ServerPort(config.GetValue<string>("MAGICONION_HOST", "localhost"), 12345, ServerCredentials.Insecure))

クライアントは、[serveo.net:12345](http://serveo.net:12345/) でok

this.channel = new Channel("serveo.net", 12345, ChannelCredentials.Insecure);

何がうれしいのか

Web と同じです。 gRPC (MagicOnion) のサーバーを立てました。といっても社内などローカルネットワークだとつなぎにくいのですね。 そんなときservo でサーバーを公開していれば、クライアントから簡単にアクセスができます。

あとは、localhost でのネットワークキャプチャってめんどくさいという感想なのですが、必ず外にアクセスするのでキャプチャはしやすいです。

Fiddler にしても localhost.fiddler とか指定する必要ありますし。

  • ipv4.fiddler
  • ipv6.fiddler
  • localhost.fiddler

Monitor traffic to localhost from IE or .NET | Progress Telerik Fiddler

Wireshark なら、3.0 で Support loopback trafic (“Npcap Loopback Adapter” will be created)でインストールして、Npcap Loopback Adapter でキャプチャ。

troushoo.blog.fc2.com

TeamsId2BitwardenConverter でTeamsId から Bitwardenへの移行を再度行う

TeamsId から Bitwarden への移行をまたやってました。

tech.guitarrapc.com

作っておいてよかったということでサクッとやったのですがいくつか修正しています。

目次

TeamsId2BitwardenConverter

移行に使ったのは例によって自作ツール。

github.com

個人のデータを移行する

次の例は、TeamsIdのデータをBitwarden のPersonalへ移行する例です。(Bitwardenには Organizationもある)

gist.github.com

必要なのは、Bitwardenのフォルダ構造の定義JSONと、TeamsIdの export csv です。

あとは、teamsIdのcsvを teamsidPersonalCsv に指定して、bitwardenのフォルダ構造json を bitwardenFolderDefinitionJson に指定すれば変換されます。

static void Personal(string outputPath, string teamsidPersonalCsv, string bitwardenFolderDefinitionJson)
{
    var folderDefinition = DeserializeFolderJson<BitwardenFolderDefinition>(bitwardenFolderDefinitionJson);

    // convert teamsid to bitwarden
    var teamsIdDatas = new CsvParser(teamsidPersonalCsv).Parse<TeamsIdDefinition4>();
    var bitwardenItems = new BitwardenConverter(folderDefinition).Convert(teamsIdDatas, defaultGroup: "TestGroup");

    // serialize bitwarden import data
    var importData = new BitwardenDefinition
    {
        folders = folderDefinition.folders,
        items = bitwardenItems,
    };
    importData.WriteJson(outputPath);
}

使いにくかった処理をいくつか修正しました。

  • 前の実装、完全にTeamsIdのカラム数を12に決め打ちしてて、カラム数が満たないときにエラー出てたので ITeamsIdDefinition を実装すればいいようにしました。書かなくていいようにカラム数分の定義を1-12まで用意しておいたので、楽になるはず。
  • 出力先フォルダも自動生成するようにしました。(わすれてました)
  • Group埋め込みとかEmail/UserNameの分岐がなかったので足しました。(ひどい

あと、ついでにCIを Appveyor から CircleCIに変更しました。 Appveyor、残り GolangとC#とPowerShellでいくつかCI回しているので全部CircleCIに移行予定。

Golang は GitHub Actionsでも動かしているので、CircleCIじゃなくてもいいかな。

まとめ

とりあえず数回移行に使って問題なかったので便利に使ってます。(移行中にデータを確認するので100回以上使ってる)

TeamsId、相変わらず非ascii (日本語も) だめなのでもう使わない.....

Bitwarden webが少々不安感あるので、k8sでクラスタ組もうかなぁ。

.NET Core 3の Single-file executables を生成する

.NET Core 3.0 では、単一バイナリ(Single-file executables)が生成可能になりました。

github.com

今回はどのようにSingle Executable生成するのか、普段は .NET Core 2.1 でビルドしたいときの分け方、dotnet global tool とビルドを分けること、GitHubリリースへのCIからの配置を見てみます。

目次

TL;DR

  • Single-file executables はプラットフォーム依存が必要。
  • 従来のビルドに /p:PublishSingleFile=true を付ければ単一バイナリが生成できる。
  • ランタイム込みでビルドするなら PublishTrimmed を有効にしてビルドするとサイズが小さくできる。
  • DotNet Global Toolsと共存できないので注意。
  • CIも問題ないので使っていこう。

リポジトリ

今回の記事の内容に該当するソースを置いておきます。

記事中細かいものは都度 gist で提示します。

github.com

.NET Coreアプリケーションを利用するときの従来の展開方法

Single-file executablesを考える前に、従来どのようにアプリケーションをビルド、展開していたか振り返ってみます。

.NET Core なアプリケーションを使うときにはランタイムが必要です。これに対応して、.NET Core 2.2まではランタイムを利用する方法が3つありました。

.NET Core アプリケーション展開 - .NET Core | Microsoft Docs

  • フレームワークに依存する展開 : Framework-dependent deployments (FDD)
  • フレームワークに依存する実行可能ファイル : Framework-dependent executables (FDE)
  • 自己完結型の展開 : Self-contained deployments (SCD)

FDD

多くの場合は FDDでdotnet publish を行って、実行するコンテナや環境に .NET Core Runtime だけ入れていると思います。

ビルドするときに、RIDも--self-containeも指定しません。

dotnet publish -c Release

f:id:guitarrapc_tech:20190818192136p:plain
FDDでのビルド(4ファイル)

実行環境がWindows/Linux/macOSといった様々なプラットフォームであっても環境に依存することなく同じライブラリが使えます。 実行にはdotnet ユーティリティを使います。 ランタイムを含まずアプリケーション/依存ライブラリのみ生成されるので、デプロイ時のサイズも小さくなります。 .NET Core Runtime は後方互換性があるので、最新のランタイムで以前のバージョンも使えたりします。

一方で、アプリケーションが必要とする.NET Core ランタイム以降のバージョンが実行環境にインストールされてないといけません。 .NET Coreが後方互換性のない変更を入れた場合に影響を受ける可能性があります。

FDE

FDDでは実行に dotnet ユーティリティが必要でした。FDEを使うことで、直接実行可能ファイルを呼び出してアプリケーションを実行できます。

ビルドするときに、RIDを指定しつつ --self-containedをfalseにします。

dotnet publish -c Release -r <RID> --self-contained false

f:id:guitarrapc_tech:20190818192808p:plain
FDE(5ファイル)

FDEはFDD同様にランタイムを含まずアプリケーション/依存ライブラリのみ生成されるので、デプロイ時のサイズも小さくなります。 dotnet ユーティリティを使わず起動できます。(Windowsなら .exe、macOS/Linuxなら拡張子なしのファイルが生成されます)

一方で、アプリケーションが必要とする.NET Core ランタイム以降のバージョンが実行環境にインストールされてないといけません。 FDDと異なり、プラットフォーム向けにビルドしているので、アプリをそれぞれ発行しないといけません。

SCD

SCDでdotnet publishを行うと、ビルドパッケージを持っていくだけで使いたい時に利用できます。

ビルドするときに、RIDを指定しつつ --self-containedをtrueにします。

dotnet publish -c Release -r <RID> --self-contained true

f:id:guitarrapc_tech:20190818192318p:plain
SCDでのビルド(217ファイル)

FDDでは実行環境のランタイムの有無で動作できるか依存していました。SCDであれば、そのアプリケーションの動作する.NET Coreランタイムバージョンが含まれているので動作を保証できます。 動作するホストと異なる.NET Coreのバージョンでアプリケーションを組んでいても動作させることができます。

一方で、アプリケーションに実行するプラットフォームごとのランタイムを含むことになるため、サイズが大きくなり、プラットフォームごとにビルドを分ける必要があります。 また、.NET Coreのネイティブ依存関係は展開されないのでホストに入っている前提となります。

core/prereqs.md at master · dotnet/core

課題

マルチプラットフォームで動くこととその前提はわかりました。 従来のビルドでは複数のバイナリファイルが生成されますが、それでどのような課題があるのでしょうか。

個人的な経験では、ファイルが複数あることで前処理、後処理が増えたり考えることが増えると感じます。 CLIやWebアプリケーションを作って動かすときを考えます。

  • わかりにくさ: FDDにおいて実行するためのバイナリと依存バイナリの区別が初見では区別つかない。
  • ファイルコピーの面倒: 複数ファイルをコピーする必要がある。
  • 構造維持の面倒: フォルダ構造を持っていれば、フォルダの構造 + ファイルをコピーする必要がある。
  • 前のファイル状態との差分の面倒: 上書きや入れ替え時に実行ファイルや依存ライブラリの差分に気を付ける必要がある。
  • 展開・利用の手間: 複数のファイル、フォルダだと、利用してもらうときに一度のダウンロードで済むようにtar/zipなどで1ファイルに固めて、利用時に展開する手間が生じる。

いずれもコンテナ内でビルドしてCOPY-FROMでランタイムコンテナに移すとしても、それなりに面倒に感じます。

Single-file executables が解決すること

「単一ファイルをコピー(ダウンロード)して実行する」が可能になります。 そのため、GitHub ReleaseやS3/Blob/GCSなどからのダウンロードして実行する。という利用が楽になりました。

Single-file executables の展開方法

Single-file executables には、.NET Core 3.0以上が必要です。

Single-file executables はプラットフォームごとにビルドが必要です。一方で、ランタイムを含めるか含めないかは選択できます。つまり、FDE、SCDが可能で、FDDができません。

FDE

ランタイムをホストに依存させる場合、通常のビルドはdotnet publish -c Release -r <RID> --self-contained false でした。これをSingle-file executablesにするには、/p:PublishSingleFile=true を追加します。

dotnet publish -r RID --self-contained=false /p:PublishSingleFile=true

Windows、macOS、Linux それぞれ次のようになります。

dotnet publish -r win-x64 --self-contained=false /p:PublishSingleFile=true
dotnet publish -r osx-x64 --self-contained=false /p:PublishSingleFile=true
dotnet publish -r linux-x64 --self-contained=false /p:PublishSingleFile=true

生成されたバイナリは直接実行が可能です。

この時のバイナリサイズはごくごく小さくなります。

f:id:guitarrapc_tech:20190818234439p:plain
162KB

SCD

ランタイムを込みでビルドする場合、通常はdotnet publish -c Release -r <RID> でした。これをSingle-file executablesにするときも、-p:PublishSingleFile=true (あるいは /p:PublishSingleFile=true )を追加します。

dotnet publish -r RID /p:PublishSingleFile=true

Windows、macOS、Linux それぞれ次のようになります。

dotnet publish -r win-x64 /p:PublishSingleFile=true
dotnet publish -r osx-x64 /p:PublishSingleFile=true
dotnet publish -r linux-x64 /p:PublishSingleFile=true

生成されたバイナリは直接実行が可能です。

なお、ランタイム込みで生成したバイナリは60MB超えと大きいです。

f:id:guitarrapc_tech:20190818234550p:plain
67463KB

SCDの場合、-p:PublishTrimmed=true を付けることで不要なDLLを抑制してファイルサイズを25MB程度まで小さくできます。(FDEでは使えない自己完結型ビルド専用のオプションです)

dotnet publish -c Release -r win-x64 -p:PublishSingleFile=true -p:PublishTrimmed=true

f:id:guitarrapc_tech:20190818234943p:plain
25925KB

Single-file executables を生成する

単純にSingle-file executablesを試すならBashかPowerShellで次のコマンドを実行します。

gist.github.com

$ ./bin/out/SingleFileExe.exe
Hello World!

次のような csproj と .cs が生成されています。

gist.github.com

このcsprojで重要なのは、TargetFrameworknetcoreapp3.0 を指定することです。 ほかに目立った変化はありません。

dotnet publish -r win10-x64 --self-contained=false /p:PublishSingleFile=true とビルドのたびに引数をずらずら指定するのが面倒な場合、csprojの PropertyGroupにあらかじめ指定するといいでしょう。 たとえば、毎度ランタイム込みの Single-file executablesをするということであれば、あらかじめPublishSingleFilePublishTrimmedを指定しておきます。

  <PropertyGroup>
    <TargetFramework>netcoreapp2.1</TargetFramework>
    <OutputType>Exe</OutputType>
    <PublishSingleFile>true</PublishSingleFile>
    <PublishTrimmed>true</PublishTrimmed>
  </PropertyGroup>

これで、ビルドコマンドはdotnet publish -c Release -r win-x64 のみでよくなります。

なお、PublishTrimmedはSCDの時にしか使えないので、条件付けしておくのもいいでしょう。

  <PropertyGroup Condition="'$(SelfContained)' == 'true'">
    <PublishTrimmed>true</PublishTrimmed>
  </PropertyGroup>

また、pdbを Single-file executablesに含める場合は、次のプロパティを追加しておくといいでしょう。

  <PropertyGroup>
    <IncludeSymbolsInSingleFile>true</IncludeSymbolsInSingleFile>
  </PropertyGroup>

全体でみるとこうなります。

gist.github.com

続いて、しばらく使ってみて出てきたユースケースごとに困りごとを解消していきます。

普段は .NET Core2.1 は開発して配布時にのみビルドする

最新の .NET Core 3.0 preview 8 は Go Liveしていますが、普段の開発はまだまだ .NET Core 2.1や 2.2 が多いでしょう。

Announcing .NET Core 3.0 Preview 8 | .NET Blog

Visual Studio 的にデフォルトの2.1が多いように思います。

ということは、普段は 2.1 でまだ開発しておいて、GitHub Release にだけ ランタイム込みのSingle-file executablesを置きたいということがあるでしょう。

この場合は、csprojを次のように定義すると -p:PublishSingleFile=true を指定したときだけ.NET Core 3.0でSingle-file executablesが生成されます。(ランタイム込みなので、 PublishTrimmedは含ませています。)

gist.github.com

dotnet global tools との分離

私はいくつかdotnet global toolsをリリースしています。 ふと、global toolしているプロジェクトで Single-file executables の対応ビルドをしようとしてみます。

gist.github.com

ビルド時に次のエラーが出ます。

$ dotnet publish -r win-x64 -p:PublishSingleFile=true

C:\Program Files\dotnet\sdk\3.0.100-preview7-012821\Sdks\Microsoft.NET.Sdk\targets\Microsoft.NET.ILLink.targets(142,5): error MSB4018: The "ComputeManagedAssemblies" task failed unexpectedly. [D:\git\guitarrapc\dotnet-lab\singleexecutable\GlobalToolSingleExecutable\GlobalToolSingleExecutable\GlobalToolSingleExecutable.csproj]
C:\Program Files\dotnet\sdk\3.0.100-preview7-012821\Sdks\Microsoft.NET.Sdk\targets\Microsoft.NET.ILLink.targets(142,5): error MSB4018: System.IO.FileNotFoundException: Could not find file 'D:\git\guitarrapc\dotnet-lab\singleexecutable\GlobalToolSingleExecutable\GlobalToolSingleExecutable\obj\Debug\netcoreapp3.0\win-x64\GlobalToolSingleExecutable.exe'. [D:\git\guitarrapc\dotnet-lab\singleexecutable\GlobalToolSingleExecutable\GlobalToolSingleExecutable\GlobalToolSingleExecutable.csproj]
C:\Program Files\dotnet\sdk\3.0.100-preview7-012821\Sdks\Microsoft.NET.Sdk\targets\Microsoft.NET.ILLink.targets(142,5): error MSB4018: File name: 'D:\git\guitarrapc\dotnet-lab\singleexecutable\GlobalToolSingleExecutable\GlobalToolSingleExecutable\obj\Debug\netcoreapp3.0\win-x64\GlobalToolSingleExecutable.exe' [D:\git\guitarrapc\dotnet-lab\singleexecutable\GlobalToolSingleExecutable\GlobalToolSingleExecutable\GlobalToolSingleExecutable.csproj]

dotnet global toolsと Single-file executablesは共存できないため、もし同一プロジェクトでやりたい場合は、条件を付けて分岐するのがいいでしょう。

幸い、PublishSingleFileプロパティがあるので、これを使うと間違いがなく独自プロパティの定義が不要です。

gist.github.com

CircleCIでビルドしたSingle-file executablesをGitHubにリリースする

さて、ビルドはしたもののそれをリリースするのにCIを使うことが多いと思います。私も、Circle CI で ghrを使ってGitHubリリースを行っています。

github.com

GitHub リリースに、プラットフォーム別にバイナリを置いておくと利用しやすいのでそのようにビルドを組んでみます。

gist.github.com

ghrは同一ディレクトリにあるバイナリをまとめてリリースに挙げてくれます。 そこで、csproj の<AssemblyName>MySQLToCsharp_$(RuntimeIdentifier)</AssemblyName> で生成されるバイナリごとにRIDを付けて重複しないようにします。 あとは、CircleCIで .NET Core 3.0コンテナでビルドして、Goコンテナからリリースを行えばokです。

バイナリにバージョンを含めない場合は次のようになります。

f:id:guitarrapc_tech:20190819003319p:plain
ghrでGitHub Releaseにバイナリをリリースする

余談

なお、dotnet core で公式にサポートされるまでは、ILMerge や Costura、warp 、monoのmkbundle などがあり、Single-file Publishに関するデザインでも考慮されています。

designs/design.md at master · dotnet/designs

github.com

github.com

github.com

www.mono-project.com

Ref

docs.microsoft.com

docs.microsoft.com

docs.microsoft.com

https://github.com/dotnet/designs/blob/master/accepted/single-file/design.mdgithub.com

www.hanselman.com

www.hanselman.com

devblogs.microsoft.com

PowerShell で1から100までの偶数の和を求めるワンライナー

PowerShell でどういうやり方がいいかを少し考えてみます。

「1から100の偶数の和を求めるワンライナー」まとめ - Qiita

というのがあり、Twitter でつぶやいたのですが、一応まとめておきます。

目次

TL;DR

リスト作ってもいいなら、メソッド方式で。作りたくないなら パイプラインで。 bitwise やシフト演算が最速と思いきや、普通に% (剰余) がいいです。

PowerShell でSIMD 活用ってどうやるといいのかが気になります。

算出方法

どれでもどうぞ。

gist.github.com

いずれも求められますが、大きな違いは2つです。(8つあるのは、2 x 4通りです)

  • フィルター方法がパイプライン or メソッド
  • 偶数の算出が 剰余(modulo) or 除算(division) or ビット演算(bitwise) or シフト演算(shift)

シーケンスの生成

フィルター方法の選択でメモリと実行速度に違いがでます。

  • パイプライン | を使うことで、1~100のメモリ域を確保しないので使用メモリが減る一方で、実行速度は落ちます。
  • メソッド(シーケンス).Where{}を使うことで、1~100のメモリ域を確保するため使用メモリが増える一方で、実行速度は上がります。

リスト作る必要ないならパイプラインがいいですね。

算出方法とベンチマーク

偶数の算出は、どれを選ぶかで実行速度が違いが出ます。

  • modulo

PowerShellでも算術演算子 % を使えます。奇数は剰余が1、偶数は0です。 よく書きます。

  • bitwise

8ビットで考えます。奇数は20 が常に1なので 1とand(論理積)を演算すれば常に1になります。偶数なら0です。 こっちのほうが早い時には使います。

00000001    
00000001   (00000001 is 1)
       &
--------
00000001

C系の(x & 1) == 0 をPowerShellに翻訳すると ($_ -band 1) -eq 0 になります。

  • division

残術演算子 / を使って2で割って、intで小数点を破棄してかけなおすと元に戻るかです。 明らかに無駄なので普段書きません。

  • shift

偶数の1ビット目が0であるため、右シフトして桁を落として左シフトで0を入れた時に元の値になれば偶数、そうでなく1少なくなれば奇数です。

00000011 (3)
00000001 (>> 1)
00000010 (<< 1)
--------
00000010 (2)

C系の ((3 >> 1) << 1) == 3 をPowerShellに翻訳すると (3 -shr 1) -shl 1 -eq 3 となります。

速度を見てみましょう。計算回数が少なければ早いので、オペレータのコストとJITでの最適化がかかるかがポイントです。

PowerShell は一回一回のベンチマークのずれが激しいので、10000回実行した算術平均をとって一回当たりの実行速度を見てみます。*1

BenchMark(Method) Times Avg(ms)
bitwise 1000 0.542
division 1000 0.521
modulo 1000 0.489
shift 1000 0.520
Benchmark(Pipeline) Times Avg(ms)
bitwise 1000 1.353
division 1000 1.486
modulo 1000 1.330
shift 1000 1.359

gist.github.com

余談 : クラス構文

PowerShellでは、同じ処理でもクラス構文にすると、dllからの呼び出しになるため高速化する傾向にあります。

といっても、偶数判定だけクラス構文にすると遅くなります。

Benchmark(Method) Times Avg(ms)
shift 1000 1.287
class 1000 1.088

gist.github.com

全体をクラス構文にして、インスタンスメソッド、スタティックメソッドでどうなるか見てみると早くなっていないことがわかります。

Benchmark(Class) Times Avg(ms)
bitwise 1000 0.536
division 1000 0.562
modulo 1000 0.533
shift 1000 0.559
Benchmark(Static) Times Avg(ms)
bitwise 1000 0.538
division 1000 0.553
modulo 1000 0.521
shift 1000 0.544

gist.github.com

この程度だと速度差つかないですね。(PowerShell 5.1 / 6.2)

*1:これでも差が出るのでウォームアアップがあるとよりいいですね