PLINQ(Parallel LINQ)の実行速度

.netのPLINQ(Parallel LINQ)による並列実行の効果がどれほどなのか理解していなかったのでテストしました。

Select、Where、AverageをLINQとPLINQで実行し、速度を確認します。


var items = Enumerable.Range(1, 100_000_000).ToArray();

var sw1 = Stopwatch.StartNew();
var results1 = items.Select(x => Math.Pow(x, 0.5)).ToArray();
Debug.Print($"Select LINQ {sw1.Elapsed}");

var sw2 = Stopwatch.StartNew();
var results2 = items.AsParallel().Select(x => Math.Pow(x, 0.5)).ToArray();
Debug.Print($"Select PLINQ {sw2.Elapsed}");

var sw3 = Stopwatch.StartNew();
var results3 = items.Where(x => Math.Pow(x, 0.5) >= 100).ToArray();
Debug.Print($"Where LINQ {sw3.Elapsed}");

var sw4 = Stopwatch.StartNew();
var results4 = items.AsParallel().Where(x => Math.Pow(x, 0.5) >= 100).ToArray();
Debug.Print($"Where PLINQ {sw4.Elapsed}");

var sw5 = Stopwatch.StartNew();
var results5 = items.Average(x => Math.Pow(x, 0.5));
Debug.Print($"Average LINQ {sw5.Elapsed}");

var sw6 = Stopwatch.StartNew();
var results6 = items.AsParallel().Average(x => Math.Pow(x, 0.5));
Debug.Print($"Average PLINQ {sw6.Elapsed}");

結果


Select LINQ 00:00:04.9831283
Select PLINQ 00:00:01.1474103

Where LINQ 00:00:04.7858871
Where PLINQ 00:00:00.7447430

Average LINQ 00:00:03.8039131
Average PLINQ 00:00:00.3454058

今回のテストプログラムの場合、4倍~11倍の速度改善になっています。
.AsParallel()を追加するだけで、これだけの速度改善になるので、処理が遅くて困った場合にはまずPLINQを試すのが良さそうです。

リモート環境の変化

こんにちは、mtjです。

最近リモートでライブ映像がリアルタイムで見れる等色々なものが遠隔でおこなるようになっています。
ネット環境の進化もありますが一番はクラウド環境でしょう。

昔であればストリーミングであれば想定人数に耐えれるだけのサーバーを用意してそれ用にネットワークを構築して等ハード面で様々な準備が必要でした。
現在はボタン1つで1日もせずにサーバーを増やせます。
足りなくなったら即増やし、不要になったら即破棄できサービスの運用もそういう意味では簡単に作って、破棄というのができるようになり便利になったような気がします。
今後はそのようなサービスに対してフットワークが軽い人が人気になるのではないのかと感じました
便利なサービスが出た場合に提案、対応しさらにクラウド環境を便利にしていく そういう時代になっていくのかなと感じました。

using declaration

先月研修でC#の勉強をしていたのですが、C#8.0からusingステートメントが、
以下のようにローカル変数宣言の先頭に追加する形で行えるようになっていたことを知りました。
(using declarationと呼ぶそうです)

// after 8.0
using var sw = new StreamWriter("hoge.txt");

// before 8.0
using (var sw = new StreamWriter("hoge.txt"))
{
}

8.0以前はその後の処理を記述する際に中括弧やインデントが必要だったのですが、
この形式だとネストを減らすことができてありがたいです。

ただ、Disposeの呼び出しがスコープの末尾なため、
記述の長いメソッドで利用するとリソースの解放が遅くなり、
場合によっては支障が出るのではないかと思ったのですが、
そもそもメソッドの長さが適切であれば問題ないことに気付きました。

メソッドの長さは適切に、ネストは浅く、シンプルなコードを書くよう心掛けたいです。

PlantUML を試してみました。

5月の連休を利用して、UML図の作成にPlantUMLを試してみました。
VSCode + 拡張機能の流行りの組み合わせです。
普段は別の専用ソフトを使っているので、
テキストベースで図を作成するのは、個人的には初めてです。

練習に社内の研修用ソフトのシーケンス図を作成してみると、
予想以上に簡単に作成することができました。
テキストベースなので要素の配置などレイアウトを考える必要もなく、
シンプルに書きたい事を書くことができる印象です。

ほんの些細な事でも考える事が少なくなると、
やりたい事がよりはっきり見えてくるのを実感しました。
プログラムのソースコードを書くときでも、
スコープは狭くする、ネストを深くしない、メソッドは小さく分ける…など、
余計な事を考える余地が少なくなるよう工夫したいと思います。

モータ制御のエミュレート

インフォテックの案件ではサーボモータなどをPCで制御することが良くあります。
今作成しているソフトもモータ制御を行うのですが、モータそのものやPCのモーションボードが無くてもソフトの動作検証が行えるように、エミュレート機能を社内ライブラリに組み込んであります。

今回はエミュレート機能をさらに強化し、移動速度に合わせてパルス出力数がカウントされたり、パルス出力状態や停止要因が正しく変化するようにしました。
この改造により、今までより細かく社内での動作検証が行えるようになりました。

下は3軸の原点復帰動作のエミュレートです。実機無しで状態が変化しています。

.Net Dictionaryの速度

先日大量のデータを処理集計するために Dictionaryに格納していたのですが
10万件を超えたあたりで急に速度が落ちることがありました。

Dictionaryのキーを任意作成のStructにしておりましたが 別にテストで作成したものでは1万、10万、100万はDictionaryの個数に比例して増加しておりました。
(1万件が80msecならば 10万件は800msecのような状態)

今回のデータ処理用に作成したものは10000件で100msec程度だったので 30万で3秒前後だと考えておりましたが実際には7時間でした。
遅い原因を調べるために.Netのソースを確認し結果的にはStructのHashCodeが重複しやすい物の場合は今回の様な結果になるのではないかと思いました。

DictionaryのContainKeyの内部で使用している部分

        public bool TryGetValue(TKey key, out TValue value)
        {
            if (key == null) throw new ArgumentNullException("key");
 
            int bucketNo, lockNoUnused;
 
            // We must capture the m_buckets field in a local variable. It is set to a new table on each table resize.
            Tables tables = m_tables;
            IEqualityComparer comparer = tables.m_comparer;
            GetBucketAndLockNo(comparer.GetHashCode(key), out bucketNo, out lockNoUnused, tables.m_buckets.Length, tables.m_locks.Length);
 
            // We can get away w/out a lock here.
            // The Volatile.Read ensures that the load of the fields of 'n' doesn't move before the load from buckets[i].
            Node n = Volatile.Read(ref tables.m_buckets[bucketNo]);
 
            while (n != null)
            {
                if (comparer.Equals(n.m_key, key))
                {
                    value = n.m_value;
                    return true;
                }
                n = n.m_next;
            }
 
            value = default(TValue);
            return false;
        }

GetBucketAndLockNoはこの通り

        private void GetBucketAndLockNo(
                int hashcode, out int bucketNo, out int lockNo, int bucketCount, int lockCount)
        {
            bucketNo = (hashcode & 0x7fffffff) % bucketCount;
            lockNo = bucketNo % lockCount;
 
            Assert(bucketNo >= 0 && bucketNo = 0 && lockNo < lockCount);
        }

上記Nodeの取り出しのバケットNoがhashcodeを使用しているのでカスタムのKeyを使用する場合はHashCode十分に分散するか考えて作成していかないといけないのかもしれません。
遅い原因はチェック中なのでHashCodeの件は間違っているかもしれません進展があれば再び書きたいと思います。

ちなみにコードはこちらで確認できます。

読みやすいコード

コードは理解しやすくなければならない。
「リーダブルコード」にもそう書かれています。

他の人が読みやすいかどうかというのはとても重要な事だと思います。
適切な変数名やメソッド名は理解を助けますし、
説明変数なども積極的に利用するようにしています。
書き方ひとつとっても、
例えば変数を宣言して anotherClass のみ遅延した初期化が必要な場合、

var myClass = new MyClass();
var yourClass = new YourClass();
AnotherClass anotherClass;

と書くよりは、

var myClass = new MyClass();
var yourClass = new YourClass();
var anotherClass = default(AnotherClass);

と書く方が、それぞれの変数の位置が揃っていて、
読みやすいのではないでしょうか。
気取らず、気の利いた美しいコードを目指したいと思います。

WindowsデスクトップアプリのGUIフレームワーク

WindowsデスクトップアプリのGUIフレームワーク(の中でXAMLを使用しているもの)について、以下のサイトに現在の状況が記載されており参考になりました。
https://www.kekyo.net/2021/02/23/7230

インフォテックの利用状況は、過去の案件はWindowsForms、新規案件はWPF、まれにUWPといったところです。
実運用でAvaloniaが使い物になるなら試してみたいですね。

リモートツールの良し悪し

こんにちは、mtjです。

コロナによりリモート管理ツールが色々社会で流行るようになってきました。
文字ベースのやり取りができるチャットツールまでは良いのですが リモート会議ツール等に関しては扱う個人の慣れに大きく影響されてしまいます。

リモート会議ツールに関しては
・音声
・画面共有の仕方
・各自発言者の認識

オフラインでは気にしなくても良いような点で不都合が発生します。
会議ツールの選定+事前確認内容の資料等を準備したほうが慣れてない人達にも解りやすい気がしました。

ちなみにマイクはUSB接続がおすすめです ジャック式のでも動きますがPCのデバイスに依存してしまうため満足に動かない、ノイズが酷い等損をする場合があります。

C#9.0 record

C#9.0でrecord型が追加されました。
まだ実務では使用していませんが、
今後C#でとても重要な機能になると思っています。

最も活躍できるのが、DDDのValueObjectでしょうか。
今まではそれらしい事をするには等値性を表現する為に、
Equals()やGetHashCode()をoverrideする必要がありましたが、
record型はコンパイラが自動で行ってくれます。

そして定義がとても簡単に行えます。
Microsoftの説明を引用すると、

public record Person {
 public string LastName { get; }
 public string FirstName { get; }
 public Person(string first, string last) => (FirstName, LastName) = (first, last);
}

と同じことが、

public record Person(string FirstName, string LastName) { }

と書けます。とても簡潔ですね。