.NETアプリでマルチコアCPUを活かす

F#でレイトレーサーを作っていてArray.Parallelモジュールを使って処理を並列化したら、CPU使用率が40~50%くらいまでしか上がらない現象が発生しました。原因を調べたら.NETランタイムのGCの設定を変更することで解消できたので記事にまとめます。

現象

以下が並列化している個所のコードで、640 x 480の解像度の画像を生成する場合、あるピクセルの色を計算するrenderPixel関数が640 x 480 = 307,200回呼び出されます。また、renderPixel関数では最大1万回シーンオブジェクト(球など)との衝突判定と色の計算が行われ、そのときにベクトルの計算(和や内積など)を大量に行います。

let render scene (width : int) (height : int) =
    let coords =
        seq {
            for y in 0..(height - 1) do
                for x in 0..(width - 1) do
                    yield { X = x; Y = y }
        }
        |> Seq.toArray
    let data =
        coords
        |> Array.Parallel.map (fun coord -> renderPixel scene width height coord)
    { Width = width; Height = height; Data = data }

f:id:LocaQ:20180624013300p:plain

この状態だと私のPCでは1枚の画像を生成するのに約550秒(9分10秒)かかりました。

CPUを100%まで使い切れればより速く画像を生成できるので原因を調べることにしました。あと、せっかくマルチコアのCPUを使っているのに活かし切れていないのはもったいないと思うので。(正直こっちの理由の方が大きいです。)

また、F#のArray.Paralle.mapは内部でSystem.Threading.Tasks.Parallel.Forを使っています。なのでこの現象はF#固有の現象ではないです。(調べたら.NETのランタイムが原因でした。)

Parallel.map<'T,'U> Function (F#)

解決策

解決方法を先に書くと、.NETのGCのモードをサーバーGCというものに変更すると解決しました。

ガベージ コレクションの基礎 | Microsoft Docs

.NET Frameworkの場合

App.configgcServerという要素を追加してenabled属性の値をtrueにします。

<?xml version="1.0" encoding="utf-8" ?>
<configuration>
  <startup> 
    <supportedRuntime version="v4.0" sku=".NETFramework,Version=v4.6.1" />
  </startup>
  <runtime>
    <gcServer enabled="true"/>
  </runtime>
</configuration>

<gcServer>要素 | Microsoft Docs

gcServerはデフォルトでfalseワークステーションGC)です。

.NET Coreの場合

プロジェクトファイルにServerGarbageCollectionを追加して値をtrueにします。

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

  <PropertyGroup>
    <OutputType>Exe</OutputType>
    <TargetFramework>netcoreapp2.0</TargetFramework>
    <ServerGarbageCollection>true</ServerGarbageCollection>
  </PropertyGroup>

  ...

</Project>

project.json と csproj の比較 - .NET Core | Microsoft Docs

この設定をするとCPU使用率が100%まで上がるようになり、約202秒(3分22秒)、変更前より約2.7倍速く画像を生成できるようになりました。

f:id:LocaQ:20180624013320p:plain

改善した理由

ガベージ コレクションの基礎 | Microsoft Docs

このページの「ワークステーションとサーバーのガベージ コレクションの比較」に、

ワークステーションのガベージ コレクションにおける、スレッド処理とパフォーマンスについての注意点を次に示します。

・コレクションは、ガベージ コレクションをトリガーしたユーザー スレッドで、それと同じ優先順位で実行されます。 ユーザー スレッドは一般に通常の優先順位で実行されるため、その場合 (通常の優先順位のスレッドで実行された場合)、ガベージ コレクターの CPU 時間が他のスレッドと競合します。

...

サーバーのガベージ コレクションにおける、スレッド処理とパフォーマンスについての注意点を次に示します。

・コレクションは、 THREAD_PRIORITY_HIGHEST の優先順位で実行される複数の専用スレッドで実行されます。

・ヒープおよびガベージ コレクションを実行するための専用スレッドは CPU ごとに 1 つずつ用意され、複数のヒープのコレクションが同時に行われます。 各ヒープには小さなオブジェクト ヒープと大きなオブジェクト ヒープがあり、どのヒープもユーザー コードからアクセスできます。 異なるヒープのオブジェクトを相互に参照できます。

・複数のガベージ コレクション スレッドが連携して処理を行うため、同じサイズのヒープを処理した場合、サーバーのガベージ コレクションの方がワークステーションのガベージ コレクションよりも高速です。

...

と書かれていました。

レイトレーサーではベクトルの計算を大量に行うため、有効期間が短い(ジェネレーション0)ベクトルクラスのオブジェクトが大量に生成されます。(これはVisual Studioのプロファイラを使って確認しました。)

そのためgcServerを有効にする前はGCを1スレッドで行っていたためボトルネックになりCPU使用率が上がらず、gcServerを有効にした後は論理CPUの数(私のPCの場合は12)だけGCのスレッドが用意されてボトルネックが解消されてCPU使用率が上がったのだと思います。

その他

今回の現象について調べてる最中に知ったことが他にもあるので、せっかくなので書いておきます。

CPUグループ

CPUグループは64個の論理CPUをまとめたもので、1つのシステムで64個より多い論理CPUがある場合は複数のCPUグループが存在するみたいです。

Processor Groups (Windows)

また、How to Get Started with Multi-Core: Parallel Processing You Can Use – US ISV Evangelismに、

CLR only uses processor group 0 and doesn’t call any of the new Windows NUMA APIs. If you are a C# or VB developer, you’ll be able to use up to 64 processors. This provides plenty of processing power for the foreseeable future on commodity hardware. But rather than embed NUMA into your code, .NET Framework 4 provides some a new namespace that lets you take advantage of parallel processing power in your PC.

と書かれていました。CLR(共通言語ランタイム)はデフォルトで1つのCPUグループしか使わないようです。

なので、もし64個より多い論理CPUを持つシステムで.NETアプリを動かす場合、CLRで全てのCPUグループを使うようにする設定が必要みたいです。

.NET Framework の場合

c# - Unable to use all processors in .NET on AWS c5.18xlarge 72 vpu - Stack Overflow の回答によると、App.configThread_UseAllCpuGroups, GCCpuGroup, gcServerを有効にする設定をするらしいです。

<?xml version="1.0" encoding="utf-8" ?>
<configuration>
  <startup>
    <supportedRuntime version="v4.0" sku=".NETFramework,Version=v4.6.1" />
  </startup>
  <runtime>
    <Thread_UseAllCpuGroups enabled="true"/>
    <GCCpuGroup enabled="true"/>
    <gcServer enabled="true"/>
  </runtime>
</configuration>

<Thread_UseAllCpuGroups>要素 | Microsoft Docs

<GCCpuGroup>要素 | Microsoft Docs

.NET Core の場合

.NET CoreではServerGarbageCollectiontrueにして、さらに以下の2つの環境変数を追加すると同等の設定ができるようです。

coreclr/clr-configuration-knobs.md at master · dotnet/coreclr

Oculus Goの感想

ツイッターでOculus Goが動画視聴デバイスとしてすごく良いと評判だったので買ってみました。

f:id:LocaQ:20180517233107p:plain:w600 f:id:LocaQ:20180517233109p:plain:w600

5/8(火)の夜に注文して5/11(金)には不在届がポストに入っていて、12日の昼頃に受け取れたので海外から来た割には意外と早かったです。

セットアップ

セットアップはスマホにOculusアプリを入れて行う方法でした。特に難しいところはなく終わりましたが、1つ分からなくて調べたのがシリアル番号の確認でした。BluetoothスマホとOculus Goをペアリングするときにシリアル番号が表示されます。一応確認しようと思ってシリアル番号を探したんですが見つからなかったのでネットで調べました。Oculus Goの左側のバンドの裏側にQRコードあり、その下に印刷されてました。

この後メニューや設定をチェックしたりして動作を確認した後、バッテリーが半分以下だったので夕方まで充電しました。

Youtubeニコニコ動画Amazonプライム・ビデオ

普段ニコニコ動画YoutubeAmazonプライムをよく使ってるので、まずはOculus GoのブラウザでYoutubeニコニコ動画Amazonプライム・ビデオを観てみました。

ブラウザで動画を開いた後に全画面表示にすると、画面の周りが暗くなって動画だけが目の前に表示される状態になるんですが、これ映画館のスクリーンで観てるような迫力があってすごいです。特にAmazonプライムで映画を観たときは1人で映画館にいる気分になりました。Oculus Goをかぶるだけで映画館に来た気分になれるのはお手軽ですごく良いです。

スマホや4Kディスプレイのような高解像度の液晶で動画を観るのに慣れているとOculus Goでは多少ドット感がありますが、それでも目の前のスクリーンで観る迫力があるので楽しいです。ドット感については、Oculus Goのディスプレイの解像度は2560×1440でPSVRの1920×1080よりも高いので良くなっています。(リフレッシュレートはPSVRの方が高いですが。)

ただ、いくつか気になる点もあります。

  • 日本語入力ができない
  • 鼻の部分から光が入ってくる
  • バッテリーが結構はやくなくなる
  • 全画面表示にするとちょっと大きすぎて四隅が視界に入らない
  • コントローラーのポインタが結構横にずれる

日本語入力ができないのは動画を探すときに苦労します。なのでYoutubeではローマ字入力の途中で出てきた検索候補一覧を選択したり、検索結果の上部に出てくる「もしかして:〇〇」をクリックしたりしてます。これは結構不便なので改善して欲しいところです。

鼻の部分は指が1本余裕で入るくらい隙間があってここから光が入ってきて少し気になります。これはカーテン閉めて明かりを消すと解消できます。

また、バッテリーの減りは結構早く、2,3時間動画を観るとほぼなくなります。休日にがっつり観たい時は物足りなくなりそうです。 これはOculus Goに電源ケーブルを挿しっぱなしにして使うとある程度長持ちするようになります。ケーブルレスの利点がなくなりますが、私はイスに座って観ているのでケーブルは全然気になりませんでした。(ゲームをするときに邪魔になるかもしれません。)

あとはそれ程でもなくて、最初は気になりましたが使ってたら慣れてあまり不便に感じなくなりました。

ちなみにAmazonプライム・ビデオはシークバーが表示されないのでシークできませんでした。再生と一時停止・再開はできます。

Youtubeの360°・3D動画

2Dの動画以外にもYoutubeの360°で3Dな動画も観てみました。

www.youtube.com www.youtube.com www.youtube.com

360°全方向を見渡せるだけでなく、3Dなので本当に目の前に存在するように感じられます。PSVRを持っているので体験済みなんですがやっぱり凄いですね。解像度は4Kでも足りない感じですがそれでも十分楽しめます。

更に上の8K解像度の動画もありますが、Oculus Goのブラウザでは映像がまったく表示されませんでした。

また、VR180という形式の動画もYoutubeにありますが、対応してないようで正しく表示できません。ただ、Oculusの人が動いているようなのでその内対応するかもしれません。

まとめ

細かいところで気になるところはありますが対処できるレベルで、届いてから1週間経ちましたが毎日Oculus Goで動画観ています。評判通り良いデバイスだと思います。

アクリルキーホルダーケースを作る

何ヶ月か前に買ったアクリルキーホルダーを袋に入れたまま放置していたんですが、いい加減に飾らないともったいないなと思い、何かいい方法がないか探していたらよいサイトを見つけました。

nyyamato.blog.jp

こちらを参考に自分でも作ってみました。

材料

  • Poster Frame A4 (2個)
  • コルクボード
  • ピン
  • 蝶番(2個)
  • 両面テープ

Poster Frameとコルクボードとピンはダイソーで購入、蝶番はホームセンターで購入しました。

あとスタンドの製作に3Dプリンターを使います。

製作

まずはフレームを加工します。

f:id:LocaQ:20180127132902j:plain:w400

フレームを裏返して金具(枠に沿って10個あるやつ)を立てて背板を外します。手でやるとケガしかねないのでペンチでやると良いです。

f:id:LocaQ:20180127132932j:plain:w400 f:id:LocaQ:20180127132943j:plain:w400

そして金具を全て取ります。 金具はペンチで掴んで左右にぐりぐりしながら引っ張ると抜けます。ただ結構力が必要なので、抜けた時に勢い余って手をぶつけないように気を付けてやります。

f:id:LocaQ:20180127133007j:plain:w300 f:id:LocaQ:20180127133021j:plain:w300

全部抜きました。

f:id:LocaQ:20180127133055j:plain:w400

もう1つのフレームも同じように加工します。


次は2つのフレームを蝶番で繋げます。

フレームは同じ向きにしてそのまま重ねた状態にします。その状態で位置合わせをしてネジ穴を開けて蝶番で固定します。

f:id:LocaQ:20180127133729j:plain:w300 f:id:LocaQ:20180127133750j:plain:w300

私が蝶番に付属していたネジは長さが少し長くてフレームを貫通しましたが、これくらいなら許容範囲内です。 (そもそもホームセンターにこれ以上短いネジがありませんでしたが。)

f:id:LocaQ:20180127133910j:plain:w400

蝶番で繋げるとこんな風になります。

f:id:LocaQ:20180127133814j:plain:w300


次にコルクボードをフレームに合わせて切ります。 長辺はぴったりの長さだったのでそのままにして、短辺を1cmカットしました。

f:id:LocaQ:20180127133134j:plain:w400 f:id:LocaQ:20180127133324j:plain:w400

あとはフレームに両面テープを張ってコルクボードを貼り付けます。

f:id:LocaQ:20180127133421j:plain:w400 f:id:LocaQ:20180127133405j:plain:w400


次はもう1つのフレームに透明のプラスチックシートを貼り付けます。 プラスチックシートはフレームに付属していたものなのでそのまま貼り付けられます。

f:id:LocaQ:20180127134532j:plain:w400

シートのフィルムを剥がして

f:id:LocaQ:20180127134549j:plain:w300

フレームに両面テープを張って貼り付けます。

f:id:LocaQ:20180127134604j:plain:w400 f:id:LocaQ:20180127134635j:plain:w400


ここまでで一応ケースとして使える形までできました。

f:id:LocaQ:20180127135212j:plain:w400


このケースを飾る方法ですが、今回は台に置くタイプにしようと思います。

台に置いて立たせるためのスタンドを3Dプリンターで作ります。スタンドはケースが5℃傾くように作ります。

f:id:LocaQ:20180127140704p:plain:w400

出力したスタンドのバリなどをリューターで取ってフレームにネジで固定します。

f:id:LocaQ:20180127135820j:plain:w400 f:id:LocaQ:20180127135833j:plain:w400

ケースができました。

f:id:LocaQ:20180127135846j:plain:w400

2つのフレームを固定してないので前に傾けると開いちゃいますが、今回はそれでよしとします。

アクリルキーホルダーを飾る

ケースができたのでアクリルキーホルダーを飾ります。

まずはキーホルダーのチェーンを外します。

f:id:LocaQ:20180127140944j:plain:w400 f:id:LocaQ:20180127140952j:plain:w400 f:id:LocaQ:20180127141002j:plain:w400

ピンはダイソーのこんなやつを使います。

f:id:LocaQ:20180127141149j:plain:w300

コルクボードの上にキーホルダーを配置してピンで固定したら完成です。

f:id:LocaQ:20180127141403j:plain:w400

少し手抜きをしましたが簡単な工作でアクリルキーホルダーケースができました。

メインブラウザをVivaldiに乗り換えた

メインブラウザをGoogle ChromeからVivaldiに乗り換えました。12月の頭から使い始めたので約1ヶ月経ちました。

vivaldi.com

タブを数十個開くような使い方をしてるんですが、Chromeだと1個1個のタブも幅が小さくなってタイトルが読めなくなって使いずらいし、ブラウザのレスポンスが遅いなぁと感じてました。

Vivaldiはタブを横に配置できると聞いて、タブのタイトルが読めない問題を解決できるならと思って使い始めたら、不満がほぼなくなったので乗り換えました。

良い点

タブを縦に並べられる

f:id:LocaQ:20180105204622p:plain:w800

画像のようにタブを縦に並べて配置するとたくさんタブを開いてもタイトルを問題なく読めます。また、タブの配置は設定で上下左右に設定できるので、Chromeのように上部に並べることもできます。

パフォーマンスも(体感ですが)十分で、タブを67個開いた状態でプロセスは31個、メモリは約1GB、レスポンスも速いです。使っていないタブはサスペンドする?っぽいのでそのおかげかもしれません。

f:id:LocaQ:20180105210134p:plain:w800

マウスジェスチャー

Vivaldiマウスジェスチャーに標準で対応していて、さらにスタートページ(スピードダイヤル)や拡張機能の管理画面などのWebページ以外でも使えるのがすごく便利です。

実はChromeの前にOperaを使っていて、Chromeに乗り換えた時に残念だったのがこの点だったので地味に感動しました。

Chrome拡張機能が使える

Chrome拡張機能に対応しているようで、自分が使っている3つは問題なく使えています。

不満な点

大きな不満はないですがちょっと気になる程度のものが2点あります。どちらもその内アップデートで解消されそうです。

別のアプリからサイトを開いたときにウィンドウが勝手に最大化する

ツイッタークライアントなどでURLをクリックしたときにデフォルトブラウザでそのサイトが開くと思いますが、

  1. Vivaldiを最大化した状態で終了する
  2. 再度起動して"Win + →"で右半分に表示する
  3. 別のアプリでURLをクリックしてVivaldiでそのURLのサイトを開く

という手順を踏んだ時Vivaldiが勝手に最大化されてしまいます。回避するには、ウィンドウを右半分に表示した状態で一旦終了して再度起動した状態で同じことをすると起きません。

たまに固まる

Vivaldiに限りませんが、Chromeより少しだけ頻度が多い気がします。ただ忘れたころに1回固まるくらいなので気になるほどの頻度じゃないです。

まとめ

Vivaldiをインストールして設定を自分好みに変えて1ヶ月使いましたが、自分の使い方だとChromeでできたことは全てVivaldiでも問題なくでき、Chromeの不満が全部解消されたのでメインブラウザとして使うことにしました。

LattePandaを購入

秋月電子でLattePandaを買いました。おすすめされていたACアダプターとヒートシンクも一緒に購入。

f:id:LocaQ:20171118160553p:plain:w700

LattePandaは5V/2Aの電流を流せる電源が必要ですが、一緒に買ったACアダプターは5V/3Aのもので余裕があります。ヒートシンクはLattePanda公式のもののようです。

以下、セットアップでやったことを書きます。セットアップで必要なマウス・キーボード・HDMIケーブルは持っていたものを使います。

起動

付属のマニュアルに書いてある起動方法が少し変わっていました。

  1. ACアダプターのUSB端子をLattePandaに接続する
  2. 赤いLEDが光った後、消えるまで待つ
  3. LattePandaの電源スイッチを押す

1ではLattePandaの初期化を行うようです。

3で電源スイッチを押すと再び赤いLEDが光ってWindowsの起動が始まります。

Windows Update

まずはWindows 10のPCと同様にWindows Updateを実行しました。何回か実行するので結構時間がかかります。

日本語化

初期状態のLattePandaのWindows 10は英語なので下のサイトを見て日本語化しました。

外国語版 Windows 10 を日本語化する

TightVNCのインストール

Windows 10 HomeはWindowsリモートデスクトップのホストになれないので、別の方法でリモートデスクトップをできるようにする必要があります。私はLattePanda公式のドキュメントに書かれている通りにTightVNCをインストールしました。

Drivers - LattePanda Docs

クライアントのPCにもTightVNCのビューワーをインストールします。

Wi-Fiのセットアップ

LattePandaはWi-Fiに対応していますが2.4GHz帯を使うものです。しかし通信速度が欲しかったので、標準のWi-Fiアダプターではなく持っていたELECOMの5GHz専用のWi-Fiアダプターを使います。

11ac 867Mbps USB3.0小型無線LANアダプター(子機) - WDC-867SU3SBK

LattePandaの3つのUSBの内1つがUSB3.0に対応しているのでそこに接続します。

ここで注意するのがドライバーのセットアップです。

ELECOMのサイトでWindows 10用のものがダウンロードできますが、Windows 10標準のドライバーで接続できるのでインストールは不要です。インストールするとWindowsに認識されなくなって使えなくなります。なので、ELECOMのサイトのドライバーはインストールせずにアダプターをLattePandaに接続します。接続すると自動でドライバーがインストールされます。

インストールが完了したら、Windows 10標準の方法でWifiのセットアップをします。

不要ソフトのアンインストール

32GBしかストレージがないのでゲームなどの不要なソフトをアンインストールします。

ヒートシンク

LattePandaは結構熱くなるというのは聞いていたのでヒートシンクを載せる前にアップデート中のLattePandaの金属部分に指で触ってみたら、確かにずっと触っていられない程度には熱かったです。

で、ヒートシンクを載せる時に最初は表面の一番面積の広い部分(LattePandaのキャラクターが印刷されている所)に5個載せようとしてたんですが、一応公式サイトで調べたら表面の小さい金属部分に1個、裏面に4個載せてました。

f:id:LocaQ:20171118172722j:plain f:id:LocaQ:20171118172730j:plain

(2枚の画像は右のサイトから引用:Pure Copper Heatsink Pack for LattePanda (5 pcs) – LattePanda

思い込みで載せなくてよかったです。

でもヒートシンクなしの状態でどこが熱いか触って確かめてみたら、表面は公式通りの場所より広い方が熱かった気がしたのでそっちに載せました。

f:id:LocaQ:20171118160741p:plain:w700 f:id:LocaQ:20171118160710p:plain:w700

載せた後に触ってみたら表面のヒートシンクより横の狭い金属部分の方が熱かったので公式通りに載せた方がよかったかもしれません。