- 投稿日:2019-04-11T23:49:56+09:00
エラーが起こった時にリトライする を一般化したコードを書いてみる
エラーが起こった時にリトライする を一般化したコードを書いてみる
そのままです!
いろんなケースはあると思うのですがリトライ処理を毎回書くのも面倒くさいので, 一般化してみましょう。
書いてみましょう。エラー用メソッド
非同期用asyncも用意しました
何も入れないでコンソールを流すと成功何か入れると例外が出ます。/// <summary> /// エラーを出すメソッド /// </summary> public void Foo() { Console.WriteLine("何も入れないと成功するよ"); var l = Console.ReadLine(); if(string.IsNullOrWhiteSpace(l)) { Console.WriteLine("完了したよ"); return; } throw new Exception("エラー出たよ"); } /// <summary> /// エラーを出すメソッドAsync /// </summary> public async Task<int> FooAsync() { Console.WriteLine("何も入れないと成功するよ Task"); var l = Console.ReadLine(); if (string.IsNullOrWhiteSpace(l)) { Console.WriteLine("完了したよ"); return 0; } throw new Exception("エラー出たよ"); }一番単純なリトライ
エラーが出たら再帰呼び出しするのが一番簡単なエラーでしょう
/// <summary> /// エラーをしたらリトライする /// </summary> public void Simple() { try { Foo(); } catch { Simple(); } }さらにリトライをするかどうかのチェックを入れるとこうなります
/// <summary> /// エラーをしたらconditionをチェックしてリトライする /// </summary> public void SimpleWithCondition() { try { Foo(); } catch { if (Condition()) { SimpleWithCondition(); } } }Conditionはこんな感じ
/// <summary> /// エラーが出た後リトライできるかのチェック /// </summary> /// <returns></returns> public bool Condition() { Console.WriteLine("エラー出たけどリトライする?(空行だとリトライ)"); var l = Console.ReadLine(); if (string.IsNullOrWhiteSpace(l)) { Console.WriteLine("リトライするよ"); return true; } Console.WriteLine("停止するよ"); return false; }FooをFooAsyncに置き換えてみる
/// <summary> /// SimpleWithConditionの非同期 /// </summary> public async Task WithConditionAsync() { try { await FooAsync(); } catch { if (Condition()) { await WithConditionAsync(); } } }ConditionもTask…は芸がないのでIObservableにしてみる
/// <summary> /// SimpleWithConditionの非同期 /// ConditionはIObservable /// </summary> public async Task WithConditionObservableAsync() { try { await FooAsync(); } catch { ConditionAsObservable().Subscribe(async b => { if (b) { await WithConditionObservableAsync(); } }); } }ConditionAsObservableはこんな感じ
/// <summary> /// エラーが出た後リトライできるかのチェック /// </summary> /// <returns></returns> public IObservable<bool> ConditionAsObservable() { return Observable.Create<bool>(observer => { Console.WriteLine("エラー出たけどリトライする?(空行だとリトライ) Observable "); var l = Console.ReadLine(); if (string.IsNullOrWhiteSpace(l)) { Console.WriteLine("リトライするよ"); observer.OnNext(true); observer.OnCompleted(); } else { Console.WriteLine("停止するよ"); observer.OnNext(false); observer.OnCompleted(); } return Disposable.Empty; }); }さらにFooAsyncの結果を戻したい
Conditionをawaitにして…
/// <summary> /// SimpleWithConditionの非同期 /// ConditionはIObservableで値を返したい /// </summary> public async Task<int> ValueTaskWithConditionObservableAsync() { try { return await FooAsync(); } catch { if (await ConditionAsObservable()) { return await ValueTaskWithConditionObservableAsync(); } return 0; } }これのFooAsyncとConditionAsObservable,default値を外に出して一般化する
/// <summary> /// SimpleWithConditionの非同期 /// ConditionはIObservableで値を返したい /// FooとConditionはさらに抽象化したい /// </summary> public async Task<T> ValueTaskWithConditionObservableAbstractionAsync<T>(Func<Task<T>> foo,Func<Task<bool>> condition,T @default = default) { try { return await foo(); } catch { if (await condition()) { return await ValueTaskWithConditionObservableAbstractionAsync(foo,condition,@default); } return @default; } }なぜFuncを引数にしているかというとリトライするため
コード全体
using System; using System.Diagnostics; using System.Linq; using System.Reactive.Disposables; using System.Reactive.Linq; using System.Reactive.Subjects; using System.Reactive.Threading.Tasks; using System.Security.Cryptography.X509Certificates; using System.Threading.Tasks; namespace ErrorRetrys { class Program { static void Main(string[] args) { Subject<int> s = new Subject<int>(); s.Subscribe(Console.WriteLine, e => Console.WriteLine(e.Message)); var p = new Program(); p.Start().Wait(); } public async Task Start() { Console.WriteLine("どれを実行する?"); var k = Console.ReadLine(); switch (k.FirstOrDefault()) { case '1': Foo(); break; case '2': Simple(); break; case '3': SimpleWithCondition(); break; case '4': await WithConditionAsync(); break; case '5': await WithConditionObservableAsync(); break; case '6': var v = await ValueTaskWithConditionObservableAsync(); Console.WriteLine(v); break; case '7': var v7 = await ValueTaskWithConditionObservableAbstractionAsync(async ()=>await FooAsync(),()=>ConditionAsObservable().ToTask(),100); Console.WriteLine(v7); break; default: Console.WriteLine("この中から選んでね" + Environment.NewLine + @" case '1': Foo(); case '2': Simple(); case '3': SimpleWithCondition(); case '4': await WithConditionAsync(); case '5': await WithConditionObservableAsync(); case '6': await ValueTaskWithConditionObservableAsync(); case '7': await ValueTaskWithConditionObservableAbstractionAsync(FooAsync(),ConditionAsObservable().ToTask(),100); "); await Start(); return; } Console.WriteLine("もう一回するなら空行を入れて"); if (string.IsNullOrWhiteSpace(Console.ReadLine())) { await Start(); } } /// <summary> /// エラーを出すメソッド /// </summary> public void Foo() { Console.WriteLine("何も入れないと成功するよ"); var l = Console.ReadLine(); if(string.IsNullOrWhiteSpace(l)) { Console.WriteLine("完了したよ"); return; } throw new Exception("エラー出たよ"); } /// <summary> /// エラーを出すメソッドAsync /// </summary> public async Task<int> FooAsync() { Console.WriteLine("何も入れないと成功するよ Task"); var l = Console.ReadLine(); if (string.IsNullOrWhiteSpace(l)) { Console.WriteLine("完了したよ"); return 0; } throw new Exception("エラー出たよ"); } /// <summary> /// エラーが出た後リトライできるかのチェック /// </summary> /// <returns></returns> public bool Condition() { Console.WriteLine("エラー出たけどリトライする?(空行だとリトライ)"); var l = Console.ReadLine(); if (string.IsNullOrWhiteSpace(l)) { Console.WriteLine("リトライするよ"); return true; } Console.WriteLine("停止するよ"); return false; } /// <summary> /// エラーが出た後リトライできるかのチェック /// </summary> /// <returns></returns> public IObservable<bool> ConditionAsObservable() { return Observable.Create<bool>(observer => { Console.WriteLine("エラー出たけどリトライする?(空行だとリトライ) Observable "); var l = Console.ReadLine(); if (string.IsNullOrWhiteSpace(l)) { Console.WriteLine("リトライするよ"); observer.OnNext(true); observer.OnCompleted(); } else { Console.WriteLine("停止するよ"); observer.OnNext(false); observer.OnCompleted(); } return Disposable.Empty; }); } /// <summary> /// エラーをしたらリトライする /// </summary> public void Simple() { try { Foo(); } catch { Simple(); } } /// <summary> /// エラーをしたらconditionをチェックしてリトライする /// </summary> public void SimpleWithCondition() { try { Foo(); } catch { if (Condition()) { SimpleWithCondition(); } } } /// <summary> /// SimpleWithConditionの非同期 /// </summary> public async Task WithConditionAsync() { try { await FooAsync(); } catch { if (Condition()) { await WithConditionAsync(); } } } /// <summary> /// SimpleWithConditionの非同期 /// ConditionはIObservable /// </summary> public async Task WithConditionObservableAsync() { try { await FooAsync(); } catch { ConditionAsObservable().Subscribe(async b => { if (b) { await WithConditionObservableAsync(); } }); } } /// <summary> /// SimpleWithConditionの非同期 /// ConditionはIObservableで値を返したい /// </summary> public async Task<int> ValueTaskWithConditionObservableAsync() { try { return await FooAsync(); } catch { if (await ConditionAsObservable()) { return await ValueTaskWithConditionObservableAsync(); } return 0; } } /// <summary> /// SimpleWithConditionの非同期 /// ConditionはIObservableで値を返したい /// FooとConditionはさらに抽象化したい /// </summary> public async Task<T> ValueTaskWithConditionObservableAbstractionAsync<T>(Func<Task<T>> foo,Func<Task<bool>> condition,T @default = default) { try { return await foo(); } catch { if (await condition()) { return await ValueTaskWithConditionObservableAbstractionAsync(foo,condition,@default); } return @default; } } } }
- 投稿日:2019-04-11T21:38:02+09:00
Unity:ログの出力(GUI編)
はじめに
本記事は、Unity:ログの出力(GUI編) に詳細がありますが便利な機能だったのでQiitaにも記載いたします。
実行結果
ソースコード
using System; using System.Collections.Generic; using System.Text; using UnityEngine; public class DebugGui : MonoBehaviour { public FpsCounter fpsCounter; public LogStorage logStorage; public DebugGuiStyle style; private GUIStyle textStyle; void Awake() { textStyle = GUIStyle.none; textStyle.wordWrap = false; textStyle.normal.textColor = Color.white; textStyle.margin = new RectOffset(); textStyle.padding = new RectOffset(); } void OnGUI() { int areaCount = 0; if (fpsCounter.enabled) { fpsCounter.Update(); areaCount += 1; } if (logStorage.enabled) { logStorage.Update(); areaCount += logStorage.list.Count; } if (0 < areaCount) { float margin = style.margin; float padding = style.padding; int fontSize = style.fontSize; // box area float width = Screen.width - (margin * 2); float height = (areaCount * Mathf.Ceil(fontSize * 1.1f)) + (padding * 2); Rect boxArea = new Rect(Screen.width - width - margin, Screen.height - height - margin, width, height); GUI.Box(boxArea, ""); // draw area Rect drawArea = new Rect(boxArea.x + padding, boxArea.y + padding, boxArea.width - padding * 2, boxArea.height - padding * 2); GUILayout.BeginArea(drawArea); { textStyle.fontSize = fontSize; if (fpsCounter.enabled) GUILayout.Label("FPS:" + fpsCounter.fps.ToString("0.0"), textStyle); if (logStorage.enabled) foreach (string str in logStorage.list) GUILayout.Label(str, textStyle); } GUILayout.EndArea(); } } public void Log(string text) { if (logStorage.enabled == false) return; logStorage.Add("I", text); } public void Log(string format, params object[] paramList) { if (logStorage.enabled == false) return; logStorage.Add("I", format, paramList); } public void LogError(string text) { if (logStorage.enabled == false) return; logStorage.Add("E", text); } public void LogError(string format, params object[] paramList) { if (logStorage.enabled == false) return; logStorage.Add("E", format, paramList); } public void LogWarning(string text) { if (logStorage.enabled == false) return; logStorage.Add("W", text); } public void LogWarning(string format, params object[] paramList) { if (logStorage.enabled == false) return; logStorage.Add("W", format, paramList); } #region Inner Class [Serializable] public class DebugGuiStyle { public float margin = 10; public float padding = 4; public int fontSize = 13; } [Serializable] public class FpsCounter { public bool enabled = true; public float interval = 3.0f; internal float fps = 0.0f; private int frameCount = 0; private float prevTime = 0.0f; public void Update() { ++frameCount; float time = Time.realtimeSinceStartup - prevTime; if (interval <= time) { fps = frameCount / time; frameCount = 0; prevTime = Time.realtimeSinceStartup; } } } [Serializable] public class LogStorage { public bool enabled = true; public int max = 5; public int lengthLimit = 150; public bool alsoUnityLog = true; internal List<string> list = new List<string>(); public void Update() { if (max < list.Count) list.RemoveRange(0, list.Count - max); } public void Add(string level, string text) { StringBuilder sb = new StringBuilder(); sb.Append(DateTime.Now.ToString("yyyy/MM/dd HH:mm:ss.fff")); sb.Append(" ["); sb.Append(level); sb.Append("] "); sb.Append(text.Substring(0, Math.Min(lengthLimit, text.Length))); string output = sb.ToString(); list.Add(output); if (alsoUnityLog) { if (level == "I") Debug.Log(output); if (level == "W") Debug.LogWarning(output); if (level == "E") Debug.LogError(output); } } public void Add(string level, string format, params object[] paramList) { string text = string.Format(format, paramList); Add(level, text); } } #endregion }使い方・パラメータの説明
こちらの記事「Unity:ログの出力(GUI編)」をご参照ください。
その他
その他のログの記事も以下に記載ありますのでご興味あれば見てください!!
- 投稿日:2019-04-11T14:46:31+09:00
C#のChartでグラフを書いて画像にする
こんな感じの画像になる
ソース
using (Chart chart = new Chart()) { chart.Size = new Size(640, 480); chart.BackColor = Color.LightGray; { Series sin = new Series("sin"); chart.Series.Add(sin); Series cos = new Series("cos"); chart.Series.Add(cos); sin.ChartType = SeriesChartType.Line; cos.ChartType = SeriesChartType.Point; for (int i = 0; i <= 360; i += 5) { sin.Points.AddXY(i, Math.Sin(i * Math.PI / 180.0)); cos.Points.AddXY(i, Math.Cos(i * Math.PI / 180.0)); } } { Legend legend = new Legend(); chart.Legends.Add(legend); legend.Docking = Docking.Top; legend.BackColor = Color.Lime; } { ChartArea chartArea = new ChartArea(); chart.ChartAreas.Add(chartArea); chartArea.AxisX.Minimum = 0; chartArea.AxisX.Maximum = 360; chartArea.AxisX.Interval = 45; chartArea.AxisY.Minimum = -1; chartArea.AxisY.Maximum = +1; chartArea.AxisY.Interval = 0.2; chartArea.BackColor = Color.SkyBlue; } { Title title = new Title(); chart.Titles.Add(title); title.Text = "wave"; title.Font = new Font("MS ゴシック", 20, FontStyle.Bold, GraphicsUnit.Pixel); title.BackColor = Color.Orange; } using (Bitmap bmp = new Bitmap(chart.Width, chart.Height)) { chart.DrawToBitmap(bmp, new Rectangle(Point.Empty, chart.Size)); bmp.Save("chart.png"); } }
- 投稿日:2019-04-11T10:38:47+09:00
【C#】DynamicJsonを使った JSON API リクエスト
DynamicJsonを使った JSON API リクエスト
VisualStudio2010 (古い…)C# アプリで JSON API を叩きたい!
ディクショナリ(ハッシュ)パラメータのみ対応、
複雑な構成はここでは考えません・・・環境
開発PC: Windows 10
VisualStudio: 2010
DynamiCJson: 1.2.0
C#VS2010 プロジェクトにインストール
プロジェクトのパッケージマネージャコンソールを開いてコマンド実行
パッケージマネージャコンソールInstall-Package DynamicJson -Version 1.2.0https://www.nuget.org/packages/DynamicJson/
使い方説明など
https://github.com/neuecc/DynamicJson
関数用意
SampleJson.cs/// <summary> /// ディクショナリからJSON文字列生成 /// </summary> /// <param name="dict">パラメータディクショナリ</param> /// <returns>JSON文字列</returns> public static string DictionaryToJson(IDictionary<string, string> dict) { // パラメータディクショナリから json 文字列生成 var entries = dict.Select(d => string.Format("\"{0}\": \"{1}\"", d.Key, string.Join(",", d.Value))); return "{\n" + string.Join(",\n", entries) + "\n}"; }API呼び出し用メソッド
- JsonWebApi
SampleJson.cs/// <summary> /// JSONリクエストAPI /// </summary> /// <param name="url">あて先URL</param> /// <param name="spDic">パラメータディクショナリ</param> /// <param name="reqParam">リクエストパラメータ</param> /// <param name="resParam">レスポンスパラメータ</param> /// <returns>結果DynamicJson</returns> public static dynamic JsonWebApi(string url, IDictionary<string, string> spDic, ref string reqParam, ref string resParam) { // WebRequest 生成 var req = WebRequest.Create(url); //Console.WriteLine("Uri:" + req.RequestUri.AbsoluteUri); var spJson = ""; byte[] spData = { }; reqParam = ""; resParam = ""; // リクエスト用 json 文字列生成 spJson = DictionaryToJson(spDic); Console.WriteLine("JsonWebApi JSON:" + spJson); reqParam = spJson; spData = Encoding.UTF8.GetBytes(spJson); req.Method = "POST"; req.ContentType = "application/json"; req.ContentLength = spData.Length; dynamic jsonResp = new DynamicJson(); jsonResp.Result = ""; jsonResp.Status = ""; jsonResp.Code = ""; jsonResp.Desc = ""; try { using (Stream reqStream = req.GetRequestStream()) reqStream.Write(spData, 0, spData.Length); using (var res = req.GetResponse()) using (var s = res.GetResponseStream()) { // 結果読み取り string respString = ""; using (var reader = new StreamReader(s, Encoding.UTF8)) respString = reader.ReadToEnd(); Console.WriteLine("JsonWebApi response:" + respString); resParam = respString; // json パース var j = DynamicJson.Parse(respString); if (j.IsDefined("Result")) { jsonResp = j; } if (j.IsDefined("Status")) { jsonResp.Status = j.Status; } jsonResp.Code = "200"; jsonResp.Desc = respString; } } // エラーハンドリング catch (WebException webex) { switch (webex.Status) { case WebExceptionStatus.Timeout: case WebExceptionStatus.NameResolutionFailure: default: Console.WriteLine("WebException has been occurred:" + webex.Message + ":" + reqParam + ":" + resParam); break; } var exres = (HttpWebResponse)webex.Response; int statusCode = 0; if (exres != null) { var status = exres.StatusCode; statusCode = (int)status; Console.WriteLine("StatusCode Error:" + statusCode.ToString() + ":" + reqParam + ":" + resParam); } jsonResp.Result = "WEB_EXCEPTION"; jsonResp.Status = "WEB_EXCEPTION"; jsonResp.Code = statusCode.ToString(); jsonResp.Desc = webex.Message; if (req != null) req.Abort(); if (exres != null) exres.Close(); } finally { if (req != null) req.Abort(); } return jsonResp; }実行してみる
Program.csstatic void Main(string[] args) { string reqParam = ""; string resParam = ""; string url = "https://jsonplaceholder.typicode.com/posts"; SortedDictionary<string, string> dic = new SortedDictionary<string, string>(); dic.Add("Result", "SUCCESS"); dic.Add("Status", "OK"); var response = JsonWebApi(url, dic, ref reqParam, ref resParam); Console.WriteLine(response); }以上、お疲れ様でした!
- 投稿日:2019-04-11T02:47:03+09:00
【Unity】UnsafeUtility基礎論【入門者向け】
金子みすゞ氏が今日生まれたので初投稿です。
この記事を読む前に @mao_ さんの 【Unity】UnsafeUtilityについて纏めてみるをお読みください。
タイトルに反してUnsafeUtility.Mallocについてのみ解説していますが、良い資料であると思われます。
なぜNativeContainerを自作しなければならないのか
Burst Job内部では参照型をnewすることはできません。故に参照型をフィールドに持つ構造体をnewできません。
Blittableな構造体のみが内部でnewできるのです。
NativeArray<T>はどうでしょうか? これはランタイム実行時には内部的にはポインタと配列長を持っていますのでBlittable型です。
しかし、エディタ上ではメモリリークを監視するためにDisposeSentinelという参照型のフィールドを把持しているため非Blittable型なのです。
故に我々はBurst Job内でNativeArrayをnewできません。
NativeContainerを自作することでのみ我々はBurst Job内でコレクションを扱えるようになります。Unity.Collections.LowLevel.UnsafeUtilityの持つ様々な便利機能を概説していきます。
MallocとFreeは @mao_ さんの記事を読んで、どうぞ。以下の関数毎に性能測定コードを作成しましたが、そのコードはgistを参照してください。
そして関数毎に推奨するか非推奨であるかを記載していますが、これは個人の見解です。
ご意見を歓迎いたします。検証環境
- Unity2019.2.0a9
- Unity Test Runner Editor Mode
- Windows 10 Home(x64)
- Intel Core i7-8750H CPU @2.20GHz
- RAM 16.00GB
void* AddressOf<T>(ref T output) where T : unmanaged
これは与えられた参照を元にしてそのポインタを得るメソッドです。
つまり純C#で記述すると以下のような擬似コードとなります。void* AddressOf<T>(ref T output) where T : unmanaged { fixed(void* ptr = &output) { return ptr; } }結論:不使用を推奨
実際の所void*が返されてもあまり使い途はありません。IntPtrまたはT*型であってほしいものです。
ならばもうfixedステートメントを素直に記述したほうが良いのかもしれませんね。
性能を比較してみましょう。
測定コードを以下に記載します。[Test] public void AddressOfSpeed_OftenFix_Test() { var array = new Guid[1024]; Guid* _0, _256; const int COUNT = 10000000; watch.Reset(); watch.Start(); for (int i = 0; i < COUNT; i++) { fixed (Guid* ptr = array) { _0 = ptr + 0; _256 = ptr + 256; } } watch.Stop(); UnityEngine.Debug.Log(watch.ElapsedMilliseconds); watch.Reset(); watch.Start(); for (int i = 0; i < COUNT; i++) { fixed (Guid* ptr = &array[0]) { _0 = ptr + 0; _256 = ptr + 256; } } watch.Stop(); UnityEngine.Debug.Log(watch.ElapsedMilliseconds); watch.Reset(); watch.Start(); for (int i = 0; i < COUNT; i++) { _0 = (Guid*)UnsafeUtility.AddressOf(ref array[0]); _256 = (Guid*)UnsafeUtility.AddressOf(ref array[256]); } watch.Stop(); UnityEngine.Debug.Log(watch.ElapsedMilliseconds); }結果として
- fixed(Guid* ptr = &array)
- 56ms
- fixed(Guid* ptr = &array[0])
- 45ms
- (Guid*)AddressOf(ref array[0])
- 114ms
となりました。
性能的にあまり推奨できません。int AlignOf<T>()
C++11のalignof演算子相当のAPIです。
メモリアライメントは効率的なメモリアクセスを実現するために知るべき値ですので存在価値はあります。結論:常に4を返す存在、今後に期待
void CopyObjectAddressToPtr(object target, void* dest)
このメソッドはボックス化された構造体またはPin留めされた参照型について、その中身をdestの指す先に書き込みます。
[Test] public void CopyObjectAddressToPtrTest() { var guid = (object)(Guid.NewGuid()); Guid* ptr = (Guid*)UnsafeUtility.Malloc(sizeof(Guid), 4, Allocator.Temp); UnsafeUtility.CopyObjectAddressToPtr(guid, ptr); UnityEngine.Debug.Log(ptr->ToString()); UnsafeUtility.Free(ptr, Allocator.Temp); }結論:不使用を推奨
そもそもボックス化を起こすなという話ではありますが、PUN2など既存のUnityのアロケーションに無頓着なライブラリを使う限りに置いてボックス化は避けられません。
これは必要悪と言えるでしょうが、C#コードベタ書きの方が早いので使わないほうがよろしいでしょうね。
性能測定コードは以下の通りです。[Test] public void CopyObjectAddressToPtrSpeedTest() { const int COUNT = 100000; var array = new object[1024]; var ptr = (Guid*)UnsafeUtility.Malloc(sizeof(Guid), 4, Allocator.Temp); for (int i = 0; i < array.Length; i++) array[i] = Guid.NewGuid(); watch.Reset(); watch.Start(); for (int i = 0; i < COUNT; i++) { for (int j = 0; j < array.Length; j++) { UnsafeUtility.CopyObjectAddressToPtr(array[j], ptr); } } watch.Stop(); UnityEngine.Debug.Log(watch.ElapsedMilliseconds); watch.Reset(); watch.Start(); for (int i = 0; i < COUNT; i++) { for (int j = 0; j < array.Length; j++) { *ptr = (Guid)array[j]; } } watch.Stop(); UnityEngine.Debug.Log(watch.ElapsedMilliseconds); UnsafeUtility.Free(ptr, Allocator.Temp); }
- *ptr = (Guid)array[j];
- 448ms
- UnsafeUtility.CopyObjectAddressToPtr(array[j], ptr);
- 2154ms
void CopyPtrToStructure<T>(void* ptr, out T output) where T : unmanaged
このAPIはポインタの内容を構造体にコピーします。
結論:不使用を推奨
性能比較コード[Test] public void CopyPtrToStructureSpeedTest() { const int COUNT = 100000; Guid* ptr = stackalloc Guid[1024]; Guid id; watch.Reset(); watch.Start(); for (int i = 0; i < COUNT; i++) { for (int j = 0; j < 1024; j++) { id = ptr[j]; } } watch.Stop(); UnityEngine.Debug.Log(watch.ElapsedMilliseconds); watch.Reset(); watch.Start(); for (int i = 0; i < COUNT; i++) { for (int j = 0; j < 1024; j++) { UnsafeUtility.CopyPtrToStructure(ptr + j, out id); } } watch.Stop(); UnityEngine.Debug.Log(watch.ElapsedMilliseconds); }
- id = ptr[j];
- 322ms
- UnsafeUtility.CopyPtrToStructure(ptr + j, out id);
- 717ms
素のC#で書く方が倍以上に早いです。
void CopyStructureToPtr<T>(ref T input, void* ptr) where T : unmanaged
構造体をポインタの指す先にコピーします。
結論:不使用を推奨
性能比較コード[Test] public void CopyStructureToPtrSpeedTest() { const int COUNT = 100000; var id = Guid.NewGuid(); var ptr = stackalloc Guid[1024]; watch.Reset(); watch.Start(); for (int i = 0; i < COUNT; i++) { for (int j = 0; j < 1024; j++) { ptr[j] = id; } } watch.Stop(); UnityEngine.Debug.Log(watch.ElapsedMilliseconds); watch.Reset(); watch.Start(); for (int i = 0; i < COUNT; i++) { for (int j = 0; j < 1024; j++) { UnsafeUtility.CopyStructureToPtr(ref id, ptr + j); } } watch.Stop(); UnityEngine.Debug.Log(watch.ElapsedMilliseconds); }
- ptr[j] = id;
- 390ms
- UnsafeUtility.CopyStructureToPtr(ref id, ptr + j);
- 685ms
int EnumToInt(T enumValue) where T : struct, Enum
このAPIはジェネリクスで表現された列挙型を数値に変換することができます。
結論:絶対使用推奨
性能比較コード[Test] public void EnumToIntSpeedTest() { const int COUNT = 100000; var enums = stackalloc AttributeTargets[] { AttributeTargets.All, AttributeTargets.Assembly, AttributeTargets.Class, AttributeTargets.Constructor, AttributeTargets.Delegate, AttributeTargets.Enum, AttributeTargets.Event, AttributeTargets.Field, AttributeTargets.GenericParameter, AttributeTargets.Interface, AttributeTargets.Method, AttributeTargets.Module, AttributeTargets.Parameter, AttributeTargets.Property, AttributeTargets.ReturnValue, AttributeTargets.Struct, }; var array = stackalloc int[16]; watch.Start(); for (int i = 0; i < COUNT; i++) for (int j = 0; j < 16; j++) array[j] = (int)enums[j]; watch.Stop(); UnityEngine.Debug.Log(watch.ElapsedMilliseconds); watch.Reset(); watch.Start(); for (int i = 0; i < COUNT; i++) for (int j = 0; j < 16; j++) array[j] = Conv0(enums[j]); watch.Stop(); UnityEngine.Debug.Log(watch.ElapsedMilliseconds); watch.Reset(); watch.Start(); for (int i = 0; i < COUNT; i++) for (int j = 0; j < 16; j++) array[j] = Conv1(enums[j]); watch.Stop(); UnityEngine.Debug.Log(watch.ElapsedMilliseconds); watch.Reset(); watch.Start(); for (int i = 0; i < COUNT; i++) for (int j = 0; j < 16; j++) array[j] = Conv2(enums[j]); watch.Stop(); UnityEngine.Debug.Log(watch.ElapsedMilliseconds); watch.Reset(); watch.Start(); for (int i = 0; i < COUNT; i++) for (int j = 0; j < 16; j++) array[j] = UnsafeUtility.EnumToInt(enums[j]); watch.Stop(); UnityEngine.Debug.Log(watch.ElapsedMilliseconds); } private static int Conv0<T>(T val) where T : Enum => (int)(object)val; private static int Conv1<T>(T val) where T : Enum => Convert.ToInt32(val); private static int Conv2<T>(T val) where T : struct, IConvertible => val.ToInt32(null);
- (int)enums[j]
- 4ms
- (int)(object)enums[j]
- 134ms
- Convert.ToInt32(enums[j])
- 373ms
- enums[j].ToInt32(null)
- 349ms
- UnsafeUtility.EnumToInt(enums[j])
- 19ms
元となる列挙型が具象型としてわかっている場合を対照例として記載しました。
具象型に対して直接(int)するのは当たり前ですが最速です。
それに対してジェネリクスに列挙型を数値に変換する手法を比べてみます。
BOX化してからintに変換する手法はボックス化分性能の劣化が激しいですね。
ConvertクラスのToInt32は輪をかけて遅いです。
全ての列挙型が暗黙のうちに実装しているIConvertibleインターフェースのメソッドToInt32(IFormatProvider)もかなり遅いです。
それに対してUnsafeUtility.EnumToIntは流石に直接int型変換に比べると桁一つ遅いですが、他の手法よりは圧倒的に速いです。間違いなくUnity環境ではEnumToIntメソッドを使用するべきです。
int GetFieldOffset(FieldInfo field)
構造体のフィールドについてその先頭からのバイトオフセットを返します。
結論:不使用を推奨
性能比較コード[Test] public void GetFieldOffsetSpeedTest() { var t = typeof(ValueTuple<Guid, Guid, Guid, Guid>); const int COUNT = 1000000; var fieldInfo = t.GetField("Item3"); watch.Reset(); watch.Start(); for (int i = 0; i < COUNT; i++) System.Runtime.InteropServices.Marshal.OffsetOf<ValueTuple<Guid, Guid, Guid, Guid>>("Item3"); watch.Stop(); UnityEngine.Debug.Log(watch.ElapsedMilliseconds); watch.Reset(); watch.Start(); for (int i = 0; i < COUNT; i++) System.Runtime.InteropServices.Marshal.OffsetOf(t, "Item3"); watch.Stop(); UnityEngine.Debug.Log(watch.ElapsedMilliseconds); watch.Reset(); watch.Start(); for (int i = 0; i < COUNT; i++) UnsafeUtility.GetFieldOffset(fieldInfo); watch.Stop(); UnityEngine.Debug.Log(watch.ElapsedMilliseconds); }
- Marshal.OffsetOf>("Item3")
- 230ms
- Marshal.OffsetOf(typeof(ValueTuple), "Item3")
- 227ms
- UnsafeUtility.GetFieldOffset(fieldInfo)
- 593ms
なぜ文字列からリフレクションしているSystem.Runtime.InteropServices.Marshal.OffsetOfに対して既にFieldInfoの段階までリフレクションを済ませているUnsafeUtility.GetFieldOffsetが負けるのでしょうかね?
bool IsBlittable<T>() where T : unmanaged
型引数がBlittable型であるかどうか判別します。
結論:使用推奨
私の知る限りに置いて.NETのAPIでその型がBlittable型であるかどうかを調べる方法はないはずです。
bool IsUnmanaged<T>() where T : unmanaged
型引数がunmanagedな構造体であるかどうか判別します。
結論:使用推奨
.NET Coreと.NET Standard 2.1に存在するSystem.Runtime.CompilerServices.RuntimeHelpers.IsReferenceOrContainsReferencesメソッドを使用すればunmanaged型であるか判別できます。
しかし上記APIはUnity環境では現在使用できません。bool IsValidAllocator(Unity.Collections.Allocator allocator)
NativeArrayをnewするために必要な列挙型Allocatorについて、その種類によっては実際にはメモリアロケーションが行われません。
メモリアロケーションが起きるかどうかを判別するメソッドです。結論:糖衣関数
Allocator.NoneとAllocator.Invalidはfalseを返します。
思いっきり糖衣関数ですね。void* Malloc(long size, int align, Allocator allocator)とvoid Free(void* ptr, Allocator allocator)
C++領域からメモリを切り出してきます。はやいです。
結論:使用推奨
AllocHGlobalとAllocCoTaskMem どちらを使うべきか?を参考にしつつSystem.Runtime.InteropServices.MarshalクラスのAllocHGlobalとAllocCoTaskMemを使用した例も性能比較コードに追加しています。
性能比較コード[Test] public void MallocFreeSpeedTest() { const int COUNT = 160000; const int SIZE = 1 << 14; var ptrs = (void**)UnsafeUtility.Malloc(sizeof(void*) * COUNT, 4, Allocator.Temp); watch.Reset(); watch.Start(); for (int i = 0; i < COUNT; i++) ptrs[i] = UnsafeUtility.Malloc(SIZE, 4, Allocator.Temp); watch.Stop(); UnityEngine.Debug.Log(nameof(Allocator.Temp) + " : " + watch.ElapsedMilliseconds); watch.Reset(); watch.Start(); for (int i = 0; i < COUNT; i++) UnsafeUtility.Free(ptrs[i], Allocator.Temp); watch.Stop(); UnityEngine.Debug.Log(nameof(Allocator.Temp) + " : " + watch.ElapsedMilliseconds); watch.Reset(); watch.Start(); for (int i = 0; i < COUNT; i++) ptrs[i] = UnsafeUtility.Malloc(SIZE, 4, Allocator.TempJob); watch.Stop(); UnityEngine.Debug.Log(nameof(Allocator.TempJob) + " : " + watch.ElapsedMilliseconds); watch.Reset(); watch.Start(); for (int i = 0; i < COUNT; i++) UnsafeUtility.Free(ptrs[i], Allocator.TempJob); watch.Stop(); UnityEngine.Debug.Log(nameof(Allocator.TempJob) + " : " + watch.ElapsedMilliseconds); watch.Reset(); watch.Start(); for (int i = 0; i < COUNT; i++) ptrs[i] = UnsafeUtility.Malloc(SIZE, 4, Allocator.Persistent); watch.Stop(); UnityEngine.Debug.Log(nameof(Allocator.Persistent) + " : " + watch.ElapsedMilliseconds); watch.Reset(); watch.Start(); for (int i = 0; i < COUNT; i++) UnsafeUtility.Free(ptrs[i], Allocator.Persistent); watch.Stop(); UnityEngine.Debug.Log(nameof(Allocator.Persistent) + " : " + watch.ElapsedMilliseconds); UnsafeUtility.Free(ptrs, Allocator.Temp); var ptrs2 = (IntPtr*)UnsafeUtility.Malloc(sizeof(IntPtr) * COUNT, 4, Allocator.Temp); watch.Reset(); watch.Start(); for (int i = 0; i < COUNT; i++) ptrs2[i] = System.Runtime.InteropServices.Marshal.AllocCoTaskMem(SIZE); watch.Stop(); UnityEngine.Debug.Log(nameof(System.Runtime.InteropServices.Marshal.AllocCoTaskMem) + " : " + watch.ElapsedMilliseconds); watch.Reset(); watch.Start(); for (int i = 0; i < COUNT; i++) System.Runtime.InteropServices.Marshal.FreeCoTaskMem(ptrs2[i]); watch.Stop(); UnityEngine.Debug.Log(nameof(System.Runtime.InteropServices.Marshal.FreeCoTaskMem) + " : " + watch.ElapsedMilliseconds); watch.Reset(); watch.Start(); for (int i = 0; i < COUNT; i++) ptrs2[i] = System.Runtime.InteropServices.Marshal.AllocHGlobal(SIZE); watch.Stop(); UnityEngine.Debug.Log(nameof(System.Runtime.InteropServices.Marshal.AllocHGlobal) + " : " + watch.ElapsedMilliseconds); watch.Reset(); watch.Start(); for (int i = 0; i < COUNT; i++) System.Runtime.InteropServices.Marshal.FreeHGlobal(ptrs2[i]); watch.Stop(); UnityEngine.Debug.Log(nameof(System.Runtime.InteropServices.Marshal.FreeHGlobal) + " : " + watch.ElapsedMilliseconds); UnsafeUtility.Free(ptrs2, Allocator.Temp); }Mallocについて
- Temp
- 206ms
- TempJob
- 203ms
- Persistent
- 211ms
- AllocCoTaskMem
- 225ms
- AllocHGlobal
- 252ms
メモリ領域確保についてはAllocator毎の差はあまり見られません。
わずかにAllocHGlobalが遅いですが、大した差はないと言えるでしょう。Freeについて
- Temp
- 2ms
- TempJob
- 108ms
- Persistent
- 134ms
- FreeCoTaskMem
- 150ms
- FreeHGlobal
- 249ms
大きく差が着いたのはメモリ解放に於いてです。この結果は個人的には予想外でした。
Allocator.TempのFreeは爆速です。
2桁速いです。
そしてHGlobalはおよそMallocと同程度の時間を掛けてFreeしています。あまり使わないほうがよろしいでしょう。void MemClear(void* destination, long size)
ポインタの指す先のsize分の領域を0クリアします。
結論:使用推奨
性能比較コード[Test] public void MemClearSpeedTest() { var array = new Guid[1024]; const int COUNT = 1000000; watch.Reset(); watch.Start(); for (int i = 0; i < COUNT; i++) Array.Clear(array, 0, array.Length); watch.Stop(); UnityEngine.Debug.Log(watch.ElapsedMilliseconds); watch.Reset(); fixed (void* ptr = &array[0]) { watch.Start(); for (int i = 0; i < COUNT; i++) for (int j = 0; j < 1024; j++) array[j] = default; watch.Stop(); } UnityEngine.Debug.Log(watch.ElapsedMilliseconds); watch.Reset(); fixed (void* ptr = &array[0]) { watch.Start(); for (int i = 0; i < COUNT; i++) UnsafeUtility.MemClear(ptr, sizeof(Guid) << 10); watch.Stop(); } UnityEngine.Debug.Log(watch.ElapsedMilliseconds); }
- Array.Clear(array, 0, array.Length)
- 554ms
- 1024要素にarray[j] = default
- 2472ms
- UnsafeUtility.MemClear(ptr, sizeof(Guid) << 10)
- 149ms
Array.Clearは型を見て0クリアしているためかそこまで速くありません。
素直にMemClearを使うのが一番でしょう。int MemCmp(void* ptr1, void* ptr2, long size)
メモリ領域のbyte単位での大小比較を行います。
結論:使用推奨
実際は等値性比較で使うことが多いと思います。かなり高速に動作するので役立ちます。
void MemCpy(void* dest, void* src, long size)とvoid MemMove(void* dest, void* src, long size)
コピー元とペースト先の領域に被りが存在しない前提でコピーを行います。
領域が重なる場合はMemMoveを使用してください。結論:絶対使用推奨
C#大統一理論で知られる @neuecc 氏の以前行ったパフォーマンステストをご参考までにどうぞ。
性能比較コード[Test] public void MemCpySpeedTest() { const int COUNT = 1 << 10; const int size = 1 << 18; var ptr = stackalloc byte[size]; var ptr2 = stackalloc byte[size]; var p = (long*)ptr; var p2 = (long*)ptr2; watch.Reset(); watch.Start(); const int V = size >> 3; for (int i = 0; i < COUNT; i++) for (int j = 0; j < V; j++) p[j] = p2[j]; watch.Stop(); UnityEngine.Debug.Log(watch.ElapsedMilliseconds); watch.Reset(); watch.Start(); for (int i = 0; i < COUNT; i++) for (int j = 0; j < size; j++) ptr[j] = ptr2[j]; watch.Stop(); UnityEngine.Debug.Log(watch.ElapsedMilliseconds); watch.Reset(); watch.Start(); for (int i = 0; i < COUNT; i++) Buffer.MemoryCopy(ptr, ptr2, size, size); watch.Stop(); UnityEngine.Debug.Log(watch.ElapsedMilliseconds); watch.Reset(); watch.Start(); for (int i = 0; i < COUNT; i++) UnsafeUtility.MemCpy(ptr2, ptr, size); watch.Stop(); UnityEngine.Debug.Log(watch.ElapsedMilliseconds); }
- long copy
- 86ms
- byte copy
- 608ms
- System.Buffer.MemoryCopy
- 284ms
- UnsafeUtility.MemCpy
- 6ms
256KBを1024回コピーするだけのテストですが、その差は歴然たるものです。
いやはやどうしてここまで差がついたのでしょうね?
やはりC++レイヤでコピペを行うのが最速ということなのでしょうか。
そしてlongコピーがMemoryCopyより速いのにもかなり困惑しています。
なんにせよUnsafeUtility.MemoryCopyが最速ではあります。void MemCpyReplicate(void* destination, void* source, int size, int count)
ポインタの指す先の領域にコピー元からcount回size分コピーを行います。
結論:使用推奨
性能比較コード[Test] public void MemCpyReplicateSpeedTest() { const int LOOP_COUNT = 1 << 10; const int COUNT = 1 << 14; var ptr = stackalloc Guid[COUNT]; var id = Guid.NewGuid(); var idptr = &id; watch.Reset(); watch.Start(); for (int i = 0; i < LOOP_COUNT; i++) for (int j = 0; j < COUNT; j++) ptr[j] = id; watch.Stop(); UnityEngine.Debug.Log(watch.ElapsedMilliseconds); watch.Reset(); watch.Start(); for (int i = 0; i < LOOP_COUNT; i++) for (int j = 0; j < COUNT; j++) UnsafeUtility.MemCpy(ptr + j, idptr, sizeof(Guid)); watch.Stop(); UnityEngine.Debug.Log(watch.ElapsedMilliseconds); watch.Reset(); watch.Start(); for (int i = 0; i < LOOP_COUNT; i++) UnsafeUtility.MemCpyReplicate(ptr, idptr, sizeof(Guid), COUNT); watch.Stop(); UnityEngine.Debug.Log(watch.ElapsedMilliseconds); }
- Guid copy
- 55ms
- MemCpy
- 260ms
- MemCpyReplicate
- 8ms
細かいデータの反復的コピーを行う場合UnsafeUtility.MemCpyだとオーバーヘッドが大きすぎるとわかりました。
void MemCpyStride(void* destination, int destinationStride, void* source, int sourceStride, int elementSize, int count)
現時点でUnity公式リファレンスに堂々と間違いが記載されていますので気を付けましょう!
あとなぜかUnity2018.1とか2で動作しないことがありました。このメソッドは良い感じにスライドしながら要素をコピペしていきます。
具体的には巨大構造体配列の一部のフィールドを別の巨大構造体配列の一部のフィールドにコピペする時に使えるでしょう。結論:使用推奨
これはちょっと対応する.NETのAPIが思い当たりませんでしたので使用例を示します。
サンプルコード[Test] public void MemCpyStrideTest() { const int COUNT = 1 << 16; const int size = 1 << 10; var dest = stackalloc Guid[size]; var src = stackalloc Stride0[size]; watch.Reset(); watch.Start(); for (int i = 0; i < COUNT; i++) for (int j = 0; j < size; j++) dest[j] = src[j].X; watch.Stop(); UnityEngine.Debug.Log(watch.ElapsedMilliseconds); watch.Reset(); watch.Start(); for (int i = 0; i < COUNT; i++) // Stride0のフィールド X を destにコピペする UnsafeUtility.MemCpyStride(dest, sizeof(Guid), src, sizeof(Stride0), sizeof(Guid), size); watch.Stop(); UnityEngine.Debug.Log(watch.ElapsedMilliseconds); } struct Stride0 { public Guid X, Y, Z, W; public long A, B, C, D, E, F, G, H; }
- フィールドべた書きコピペ
- 257ms
- MemCpyStride
- 155ms
void* PinGCArrayAndGetDataAddress(Array target, out ulong gcHandle)とvoid* PinGCObjectAndGetAddress(object target, out ulong gcHandle)とvoid ReleaseGCObject(ulong gcHandle)
PinGCArrayAndGetDataAddressは引数に与えた配列を固定し、その配列の先頭要素のポインタを返り値にします。
PinGCObjectAndGetAddressは引数に与えたオブジェクトを固定し、そのポインタを戻り値にします。
この固定状態を解除するにはgcHandleに対してReleaseGCObjectを行ってください。結論:使用推奨
性能比較コード[Test] public void PinSpeedTest() { const int COUNT = 1 << 20; var array = new Guid[1 << 10]; var intptrs = (System.Runtime.InteropServices.GCHandle*)UnsafeUtility.Malloc(sizeof(System.Runtime.InteropServices.GCHandle) * COUNT, 4, Allocator.Temp); watch.Reset(); watch.Start(); for (int i = 0; i < COUNT; i++) intptrs[i] = System.Runtime.InteropServices.GCHandle.Alloc(array, System.Runtime.InteropServices.GCHandleType.Pinned); watch.Stop(); UnityEngine.Debug.Log("GCHandle.Alloc : " + watch.ElapsedMilliseconds); watch.Reset(); watch.Start(); for (int i = 0; i < COUNT; i++) intptrs[i].Free(); watch.Stop(); UnityEngine.Debug.Log("GCHandle.Free : " + watch.ElapsedMilliseconds); UnsafeUtility.Free(intptrs, Allocator.Temp); var ptrs = (void**)UnsafeUtility.Malloc(sizeof(void*) * COUNT, 4, Allocator.Temp); var handles = (ulong*)UnsafeUtility.Malloc(sizeof(ulong) * COUNT, 4, Allocator.Temp); watch.Reset(); watch.Start(); for (int i = 0; i < COUNT; i++) ptrs[i] = UnsafeUtility.PinGCArrayAndGetDataAddress(array, out handles[i]); watch.Stop(); UnityEngine.Debug.Log("PinGCArrayAndGetDataAddress : " + watch.ElapsedMilliseconds); watch.Reset(); watch.Start(); for (int i = 0; i < COUNT; i++) UnsafeUtility.ReleaseGCObject(handles[i]); watch.Stop(); UnityEngine.Debug.Log("ReleaseGCObject : " + watch.ElapsedMilliseconds); UnsafeUtility.Free(handles, Allocator.Temp); UnsafeUtility.Free(ptrs, Allocator.Temp); }比較対象のAPIとしてSystem.Runtime.InteropServices.GCHandleを使用しました。
配列内部のポインターを今回は利用できるようにするため、GCHandle.Allocの第2引数にGCHandleType.Pinnedを渡すのがミソです。
しかし、Unity環境ではUnsafeUtilityの方が速いのでGCHandleを使う必要は薄いです。Pin留め
- System.Runtime.InteropServices.GCHandle.Alloc
- 88ms
- UnsafeUtility.PinGCArrayAndGetDataAddress
- 63ms
Release
- GCHandle.Free
- 41ms
- UnsafeUtility.ReleaseGCObject
- 37ms
T ReadArrayElement<T>(void* source, int index)とT ReadArrayElementWithStride<T>(void* source, int index, int stride)とvoid WriteArrayElement<T>(void* destination, int index, T value)とvoid WriteArrayElementWithStride<T>(void* destination, int index, int stride, T value)
結論:不使用を推奨
性能比較コード[Test] public void ReadWriteArrayElementSpeedTest() { const int COUNT = 1 << 30; const int size = 1 << 10; var array = stackalloc int[size]; var array2 = stackalloc ReadWrite0[size]; int x = 0; watch.Reset(); watch.Start(); for (int i = 0; i < COUNT; i++) x = array[16]; watch.Stop(); UnityEngine.Debug.Log(watch.ElapsedMilliseconds); watch.Reset(); watch.Start(); for (int i = 0; i < COUNT; i++) x = UnsafeUtility.ReadArrayElement<int>(array, 16); watch.Stop(); UnityEngine.Debug.Log(watch.ElapsedMilliseconds); watch.Reset(); watch.Start(); for (int i = 0; i < COUNT; i++) x = array2[16].a; watch.Stop(); UnityEngine.Debug.Log(watch.ElapsedMilliseconds); watch.Reset(); watch.Start(); for (int i = 0; i < COUNT; i++) x = UnsafeUtility.ReadArrayElementWithStride<int>(array2, 16, sizeof(ReadWrite0)); watch.Stop(); UnityEngine.Debug.Log(watch.ElapsedMilliseconds); watch.Reset(); watch.Start(); for (int i = 0; i < COUNT; i++) array[16] = x; watch.Stop(); UnityEngine.Debug.Log(watch.ElapsedMilliseconds); watch.Reset(); watch.Start(); for (int i = 0; i < COUNT; i++) UnsafeUtility.WriteArrayElement(array, 16, x); watch.Stop(); UnityEngine.Debug.Log(watch.ElapsedMilliseconds); watch.Reset(); watch.Start(); for (int i = 0; i < COUNT; i++) array2[16].a = x; watch.Stop(); UnityEngine.Debug.Log(watch.ElapsedMilliseconds); watch.Reset(); watch.Start(); for (int i = 0; i < COUNT; i++) UnsafeUtility.WriteArrayElementWithStride<int>(array2, 16, sizeof(ReadWrite0), x); watch.Stop(); UnityEngine.Debug.Log(watch.ElapsedMilliseconds); } struct ReadWrite0 { public int a, b, c, d, e, f, g, h; }
- 要素ベタコピペ
- 2438ms
UnsafeUtility.ReadArrayElement
- 6503ms
要素ベタコピペ
- 2751ms
UnsafeUtility.ReadArrayElementWithStride
- 6393ms
要素ベタコピペ
- 2531ms
UnsafeUtility.WriteArrayElement
- 6133ms
要素ベタコピペ
- 2334ms
UnsafeUtility.WriteArrayElementWithStride
- 7109ms
基本的に構造体のフィールドに関して複数の型で共通のフィールドを持つよう言語的に強制することはC#7.3の現時点ではできません。
ゆえに今回のようなフィールドの読み取りは個別的具象型に対して行うと見做して良いでしょう。
その場合べた書きが速いですね。特にUnsafeUtilityを使わずともよいでしょう。int SizeOf<T>()
型引数で指定した型のサイズを返します。
結論:不使用を推奨
unsafeコンテキストではsizeof演算子がジェネリクス型引数に対して使用可能です。
特に使う必要はないでしょう。全体の結論
以上!
全UnsafeUtilityメソッドの解説と性能検証でした!
案外性能出ていないAPIも沢山ありますが、それでも絶対に使うべき有能APIもありましたね。
Unity Technologiesは良い仕事をしたと思います。
- 投稿日:2019-04-11T01:22:24+09:00
ASP.NET Core Web API で CRUD & Swagger/OpenAPI でテスト
はじめに
前回 ASP.NET Core Web API で Entity Framework Core を使って SQL Server から単一テーブルのデータを取得する API を作ったので、今回は他の CRUD も作っていきたいと思います。動作確認しやすいように、Swagger/OpenAPI も導入します。
Swagger/OpenAPI 導入
では Swagger/OpenAPI を導入していきましょう。MS のドキュメントでは2種類のライブラリが紹介されてますが、今回は開発が活発そうな NSwag の方を使います。
パッケージマネージャーコンソールから以下のコマンドを実行。
> Install-Package NSwag.AspNetCore
Startup.csで NSwag を有効化。(OpenAPI 3.0)Startup.csusing Microsoft.AspNetCore.Builder; using Microsoft.AspNetCore.Hosting; using Microsoft.AspNetCore.Mvc; using Microsoft.EntityFrameworkCore; using Microsoft.Extensions.Configuration; using Microsoft.Extensions.DependencyInjection; using SimpleWebApi.Data; using NSwag.AspNetCore; //追加 namespace SimpleWebApi { public class Startup { public Startup(IConfiguration configuration) { Configuration = configuration; } public IConfiguration Configuration { get; } public void ConfigureServices(IServiceCollection services) { services.AddDbContext<BookContext>(options => options.UseSqlServer(Configuration.GetConnectionString("DefaultConnection"))); services.AddMvc().SetCompatibilityVersion(CompatibilityVersion.Version_2_2); services.AddOpenApiDocument(); //追加 } public void Configure(IApplicationBuilder app, IHostingEnvironment env) { if (env.IsDevelopment()) { app.UseDeveloperExceptionPage(); } else { app.UseHsts(); } app.UseSwagger(); //追加 app.UseSwaggerUi3(); //追加 app.UseHttpsRedirection(); app.UseMvc(); } } }/swagger にアクセスして API の一覧(まだ1個しかないけど)が表示されることを確認。
これで UI から簡単に API のテストができるようになったので、CRUD API を順番に作っていきます。
GET(単一リソース)
前回はテーブルのレコードすべてを返す API を作ったので、今回は特定のレコードのみ返す API を作ります。
Controllers/BooksController.csusing System.Collections.Generic; using System.Linq; //追加 using Microsoft.AspNetCore.Mvc; using SimpleWebApi.Data; using SimpleWebApi.Entities; namespace SimpleWebApi.Controllers { [Route("api/[controller]")] [ApiController] public class BooksController : ControllerBase { //省略// //追加// [HttpGet("{id}")] public ActionResult<Book> Get(int id) { var book = context.Books.FirstOrDefault(b => b.BookId.Equals(id)); return book; } } }Swagger UI でテスト実行。
Create(新規作成)
新しいレコードを追加するPOST メソッドを追加します。
Controllers/BooksController.csusing System.Collections.Generic; using System.Linq; using Microsoft.AspNetCore.Mvc; using SimpleWebApi.Data; using SimpleWebApi.Entities; namespace SimpleWebApi.Controllers { [Route("api/[controller]")] [ApiController] public class BooksController : ControllerBase { //省略// //追加// [HttpPost] public ActionResult<Book> Create(Book book) { context.Books.Add(book); context.SaveChanges(); return CreatedAtAction(nameof(Get), new { id = book.BookId }, book); } } }Swagger UI でテスト実行。
bookId は 0 にするか、リクエストボディに含めずに実行すればちゃんと連番で ID を生成してくれます。
Upudate(更新)
レコードを更新する PUT メソッドを追加します。
Controllers/BooksController.csusing System.Collections.Generic; using System.Linq; using Microsoft.AspNetCore.Mvc; using Microsoft.EntityFrameworkCore; //追加 using SimpleWebApi.Data; using SimpleWebApi.Entities; namespace SimpleWebApi.Controllers { [Route("api/[controller]")] [ApiController] public class BooksController : ControllerBase { //省略// //追加// [HttpPut] public IActionResult Update(int id, Book book) { context.Entry(book).State = EntityState.Modified; context.SaveChanges(); return NoContent(); } } }Swagger UI でテスト実行。
DELETE(削除)
レコードを削除する DELETE メソッドを追加します。
Controllers/BooksController.csusing System.Collections.Generic; using System.Linq; using Microsoft.AspNetCore.Mvc; using Microsoft.EntityFrameworkCore; using SimpleWebApi.Data; using SimpleWebApi.Entities; namespace SimpleWebApi.Controllers { [Route("api/[controller]")] [ApiController] public class BooksController : ControllerBase { //省略// //追加// [HttpDelete("{id}")] public IActionResult Delete(int id) { var book = context.Books.FirstOrDefault(b => b.BookId.Equals(id)); context.Books.Remove(book); context.SaveChanges(); return NoContent(); } } }ソースコード
- 投稿日:2019-04-11T00:27:09+09:00
[.NET] 単体テストがもりもり書ける!モック化の枠組み(Moq + Autofac)
モックライブラリ Moq とIoCコンテナ Autofac を使用してモック化する例です。
単体テストの基底クラスにモックの生成、検証を抽出することで、Verify 漏れを防ぎ、素早く、すっきりテストコードを書くことができます。■プロダクションコード
テスト対象クラス (System Under Test)
public class Target { /// <summary> /// DIコンテナ。 /// </summary> internal IContainer DiContainer { get; set; } /// <summary> /// コンストラクタ。 /// </summary> public Target() { var containerBuilder = new ContainerBuilder(); /* Resolve のたびにインスタンスを生成する場合 builder.Register(c => new DependOnComponent("Production_PerDependency")).InstancePerDependency(); */ // テスト対象オブジェクト内で一つのインスタンスを共用する場合 containerBuilder.Register(c => new DependOnComponent()).SingleInstance(); this.DiContainer = containerBuilder.Build(); } /// <summary> /// テスト対象メソッド。 /// </summary> /// <returns></returns> public string GetText() { // DIコンテナから依存オブジェクトを取得する。 // ※SingleInstance の場合、ショートカット的にプロパティを定義してもよい。 var component = this.DiContainer.Resolve<DependOnComponent>(); return component.GetText(); } }依存コンポーネント(Depended-On Component)
public class DependOnComponent { private readonly string text = "Production"; public DependOnComponent() { } public DependOnComponent(string text) { this.text = text; } public virtual string GetText() { return this.text; } }■単体テスト
テストの基底クラス
DIコンテナの管理、モックオブジェクトの生成、管理、検証(Verify)を担います。
ほかのDIコンテナに切り替えるときは、個々のテストコードは変えずに基底クラスを差し替えます。MSTest例/// <summary> /// 単体テストの基底クラス。 /// </summary> public abstract class AutofacTestBase { /// <summary> /// モックオブジェクトのリスト。 /// </summary> private List<Mock> mocks; /// <summary> /// 単体テスト用にモックオブジェクトを提供するDIコンテナのビルダー。 /// </summary> internal ContainerBuilder DiContainerBuilder { get; private set; } /// <summary> /// テストの初期処理。 /// </summary> [TestInitialize] public void BaseTestInitialize() { this.DiContainerBuilder = new ContainerBuilder(); this.mocks = new List<Mock>(); } /// <summary> /// テストの終了処理。 /// </summary> [TestCleanup] public void BaseTestCleanup() { // モックが期待どおりに呼び出されたことを検証する。 foreach (var mock in this.mocks) { mock.VerifyAll(); } } /// <summary> /// Mock インスタンスを生成する。 /// </summary> /// <typeparam name="T"></typeparam> /// <returns></returns> protected Mock<T> CreateMock<T>() where T : class { return CreateMock<T>(false); } /// <summary> /// Mock インスタンスを生成する。 /// </summary> /// <typeparam name="T"></typeparam> /// <param name="callBase"></param> /// <returns></returns> protected Mock<T> CreateMock<T>(bool callBase) where T : class { var mock = new Mock<T> { CallBase = callBase }; this.DiContainerBuilder.RegisterInstance(mock.Object); this.mocks.Add(mock); return mock; } /// <summary> /// テスト用のDIコンテナを取得する。 /// </summary> /// <returns></returns> protected IContainer GetDiContainer() { return this.DiContainerBuilder.Build(); } }テストクラス
モックをDIコンテナに詰めて渡すので、依存コンポーネントをモックに差し替えるためだけに Target を継承した Testable クラスを用意する必要はありません。
MSTest例[TestClass] public class TargetTest : AutofacTestBase { [TestMethod] public void GetText_Mocking() { // モック設定 var componentMock = CreateMock<DependOnComponent>(); componentMock.Setup(c => c.GetText()).Returns("UnitTest"); // テスト対象オブジェクトを生成(モック用のDIコンテナを設定) var target = new Target { DiContainer = GetDiContainer() }; // テスト対象メソッドを実行 string text = target.GetText(); // 戻り値の検証 // ※モックの検証は基底クラスで自動的に行われる。 Assert.AreEqual("UnitTest", text); } }
Unity 版は こちら をご覧ください。
- 投稿日:2019-04-11T00:23:21+09:00
Visual Studio 2019 で Abstract Factory を作ってみる
新しくなった Visual Studio 2019 で気になったことを書きながら GoF デザインパターンの Abstract Factory を作ってみます。
- 新機能は Visual Studio 2019 の新機能 | Microsoft Docs に記載されています
- もっと詳しい情報は Visual Studio 2019 リリース ノート | Microsoft Docs に記載されています
- 各エディションは Visual Studio 製品の比較 | Visual Studio に記載されています
- GoF デザインパターンは .NET Design Patterns in C# and VB.NET - Gang of Four (GOF) - doFactory.com からの引用になります
- 書籍からデザインパターンを学びたい場合は結城浩の 増補改訂版 Java 言語で学ぶデザインパターン入門 がおすすめです
気になったこと
- C# 8.0 は (デフォルト状態だと) まだです
- .NET Core 3.0 もまだです
- プロジェクトの LangVersion を自分で変更するか、 .NET Core 3.0 プレビューを使用すれば使えるようになります
- リリースに備えて C# 8.0 の新機能 - C# によるプログラミング入門 | ++C++; // 未確認飛行 C で勉強しましょう
- 色んな新しいプロジェクトを作るときに .NET Core を優先するようになりました
- 2017 の頃から思っていたことで、 Windows の GUI アプリじゃない限り .NET Framework は使わなくてもよい考えが加速しました
- Community と Professional が殆ど同じものになりました
- CodeLens が Community 版でも使用できるようになっています
- ただし Community 版のライセンス条項は 2017 と変わらないので、エンタープライズの人達はきちんと買ってね ♡
- Ctrl+Q (新しい検索エクスペリエンス) はとても便利になりました
- 2017 のときはワンテンポ遅くてどうしても使いづらかったのですが、爆速になっています
- Ctrl+. がパワーアップしました。それと未使用の変数とかも教えてくれるようになりました (これは 2017 から?)
- でもやっぱり ReSharper (Alt+Enter) のほうが頭がよいので ReSharper を入れたほうがよいです
- if とか for とかの statement とか method の色が変わりました
- 2017 のときは string とか int の基本型と同じ青色だったり、普通の黒色でした
- Color theme (Blue) は 2017 より全体的に薄いので、白文字が少し見づらくなっています
GoF デザインパターン - Abstract Factory
簡単なコンソールアプリのコードを書いても 2017 からの進化はよくわかりませんでした。
UML class diagram
Output
Code
Program.csclass Program { static void Main(string[] args) { AbstractFactory factory1 = new ConcreteFactory1(); var client1 = new Client(factory1); client1.Run(); AbstractFactory factory2 = new ConcreteFactory2(); var client2 = new Client(factory2); client2.Run(); } }AbstractFactory.csabstract class AbstractFactory { public abstract AbstractProductA CreateProductA(); public abstract AbstractProductB CreateProductB(); }ConcreteFactory1.csclass ConcreteFactory1 : AbstractFactory { public override AbstractProductA CreateProductA() { return new ProductA1(); } public override AbstractProductB CreateProductB() { return new ProductB1(); } }ConcreteFactory2.csclass ConcreteFactory2 : AbstractFactory { public override AbstractProductA CreateProductA() { return new ProductA2(); } public override AbstractProductB CreateProductB() { return new ProductB2(); } }AbstractProductA.csabstract class AbstractProductA { }AbstractProductB.csabstract class AbstractProductB { public abstract void Interact(AbstractProductA a); }ProductA1.csclass ProductA1 : AbstractProductA { }ProductA2.csclass ProductA2 : AbstractProductA { }ProductB1.csclass ProductB1 : AbstractProductB { public override void Interact(AbstractProductA a) { Console.WriteLine($"{GetType().Name} interacts with {a.GetType().Name}"); } }ProductB2.csclass ProductB2 : AbstractProductB { public override void Interact(AbstractProductA a) { Console.WriteLine($"{GetType().Name} interacts with {a.GetType().Name}"); } }Client.csclass Client { private readonly AbstractProductA _abstractProductA; private readonly AbstractProductB _abstractProductB; public Client(AbstractFactory factory) { _abstractProductB = factory.CreateProductB(); _abstractProductA = factory.CreateProductA(); } public void Run() { _abstractProductB.Interact(_abstractProductA); } }
- 投稿日:2019-04-11T00:22:43+09:00
C#でMarkdownパーサーアプリを作った
使い方
コマンドライン引数にmdファイルのパスを渡す
Markdowner.exe mk.mdするとMartkdowner.exeがあるフォルダーにmk.md.htmlファイルができる
ビルド手順
※ターゲットは.NetFrameworkを指定する
- ツール->Nugetパッケージマネージャー->パッケージマネージャーコンソールを開く
- でてきたコンソールに以下のコマンドを打つ
PM> Install-Package javascriptengineswitcher.v8.native.win-x86 PM> Install-Package JavaScriptEngineSwitcher.V8 PM> Install-Package markedソースコード
using JavaScriptEngineSwitcher.Core; using JavaScriptEngineSwitcher.V8; using System; using System.IO; using System.Text; namespace Markdowner { class Program { static void Main(string[] args) { if (args.Length == 0) { Console.WriteLine("args zero"); Console.ReadKey(); return; } string markdown = ""; try { markdown += File.ReadAllText(args[0], Encoding.GetEncoding("sjis")); var parser = new MarkdownParser(new V8JsEngine()); var html = parser.Transform(markdown); File.WriteAllText(args[0] + ".html", html, Encoding.GetEncoding("sjis")); Console.WriteLine(html); } catch (Exception e) { Console.WriteLine(e); } Console.ReadKey(); } } public class MarkdownParser : IDisposable { private IJsEngine _jsEngine; private bool _disposed; public MarkdownParser(IJsEngine jsEngine) { _jsEngine = jsEngine ?? throw new ArgumentNullException("jsEngine"); _jsEngine.ExecuteResource("Scripts.marked.js", GetType()); } public string Transform(string markdown) { return _jsEngine.CallFunction<string>("marked", markdown); } public void Dispose() { if (_disposed) return; _disposed = true; if (_jsEngine == null) return; _jsEngine.Dispose(); _jsEngine = null; } } }落ち葉ひろい
パーサー部分はライブラリにおまかせ
C#でmarkdownパーサーやろうとしてぐぐったら、出てくる情報が古いらしくそのままでは動かなかった
string markdown="";と分けてるのはここでCSS読み込みとかするかなと思ったから
文字コードはちゃんと指定しないとうまくいかなかった
- 投稿日:2019-04-11T00:22:43+09:00
C#でmarkdownパーサーアプリを作った
使い方
コマンドライン引数にmdファイルのパスを渡す
Markdowner.exe mk.mdするとMartkdowner.exeがあるフォルダーにmk.md.htmlファイルができる
ビルド手順
※ターゲットは.NetFrameworkを指定する
- ツール->Nugetパッケージマネージャー->パッケージマネージャーコンソールを開く
- でてきたコンソールに以下のコマンドを打つ
PM> Install-Package javascriptengineswitcher.v8.native.win-x86 PM> Install-Package JavaScriptEngineSwitcher.V8 PM> Install-Package markedソースコード
using JavaScriptEngineSwitcher.Core; using JavaScriptEngineSwitcher.V8; using System; using System.IO; using System.Text; namespace Markdowner { class Program { static void Main(string[] args) { if (args.Length == 0) { Console.WriteLine("args zero"); Console.ReadKey(); return; } string markdown = ""; try { markdown += File.ReadAllText(args[0], Encoding.GetEncoding("sjis")); var parser = new MarkdownParser(new V8JsEngine()); var html = parser.Transform(markdown); File.WriteAllText(args[0] + ".html", html, Encoding.GetEncoding("sjis")); Console.WriteLine(html); } catch (Exception e) { Console.WriteLine(e); } Console.ReadKey(); } } public class MarkdownParser : IDisposable { private IJsEngine _jsEngine; private bool _disposed; public MarkdownParser(IJsEngine jsEngine) { _jsEngine = jsEngine ?? throw new ArgumentNullException("jsEngine"); _jsEngine.ExecuteResource("Scripts.marked.js", GetType()); } public string Transform(string markdown) { return _jsEngine.CallFunction<string>("marked", markdown); } public void Dispose() { if (_disposed) return; _disposed = true; if (_jsEngine == null) return; _jsEngine.Dispose(); _jsEngine = null; } } }落ち葉ひろい
パーサー部分はライブラリにおまかせ
C#でmarkdownパーサーやろうとしてぐぐったら、出てくる情報が古いらしくそのままでは動かなかった
string markdown="";と分けてるのはここでCSS読み込みとかするかなと思ったから
文字コードはちゃんと指定しないとうまくいかなかった












