トップ回答者
.NET C#上での配列を含んだ共用体の作成方法

質問
-
目的
Serialport送受信時の配列コピーを最小限にしたい
⇒Int[]とbyte[]は同値アドレスを指してデータを送受したい。
コードイメージ
using System; using System.Runtime.InteropServices; using System.Windows.Forms; namespace WindowsFormsApp2 { public partial class Form1 : Form { public Form1() { InitializeComponent(); timer1.Start(); } [Serializable()] [StructLayout(LayoutKind.Explicit)] class DataPackets { [FieldOffset(0)] [MarshalAs(UnmanagedType.ByValArray, SizeConst = 5)] public int[] IntArray = new int[5]; [FieldOffset(0)] [MarshalAs(UnmanagedType.ByValArray, SizeConst = 20)] public byte[] ByteArray = new byte[20]; } DataPackets dataPackets = new DataPackets(); //使用例 //送信 private void timer1_Tick(object sender, ventArgs e) { //所定のINT配列のデータを dataPackets.IntArray = new int[] { 256, 257, 258, 259, 260 }; //同アドレスのバイト配列として送信 serialPort1.Write(dataPackets.ByteArray, 0, 20); } public event EventHandler<int[]> PacketsReceivedEvent; //受信 private void serialPort1_DataReceived(object sender, System.IO.Ports.SerialDataReceivedEventArgs e) { serialPort1.Read(dataPackets.ByteArray, 0, 20); PacketsReceivedEvent?.Invoke(this, dataPackets.IntArray); } } }
上記コードの問題点
コンストラクト終了時、int[5]がByte[20]と認識されている。
byte[]⇔Int[]の方法としてマーシャリングや、シリアライズがあることは承知の上で、
データのコピーが重そうなので、より簡便な方法を探しています。
よろしくお願いします。
回答
-
.NETではガベージコレクタによりメモリ管理が行われており、全てのオブジェクトには8バイト/12バイトのヘッダーが含まれています。そしてそのヘッダーによりint[]やbyte[]が識別されています。ですので、SerialPort.ReadやSerialPort.Writeがbyte[]を要求する以上はこれを用意する以外に方法はありません。せめてBuffer.BlockCopyを使うとかその程度です。
一応、次期バージョンでSpan<T>構造体が導入され、int[] / byte[]のようなメモリの読み替えも可能になりそうですが、肝心のSerialPortクラスはこれに対応しない気がしています。
- 回答としてマーク fushy 2018年3月5日 21:52
-
はいその通りです。.NETは不必要なコピーが発生することも、ヘッダー管理コストが生じることも把握した上で、それを上回るメモリー速度を前提として設計されています。本当にパフォーマンスが必要となる機能、例えばEncoding.GetStringなどではbyte*を扱うことでコピーを抑止できるようにも設計されています。ですが、SerialPortクラスはもちろん例えばTCP/IPを提供するSocketクラスですらbyte*は受け付けていません。
ですので「データのコピーが重そう」という考えは改めた方がいいかと。
- 回答としてマーク fushy 2018年3月5日 21:51
すべての返信
-
.NETではガベージコレクタによりメモリ管理が行われており、全てのオブジェクトには8バイト/12バイトのヘッダーが含まれています。そしてそのヘッダーによりint[]やbyte[]が識別されています。ですので、SerialPort.ReadやSerialPort.Writeがbyte[]を要求する以上はこれを用意する以外に方法はありません。せめてBuffer.BlockCopyを使うとかその程度です。
一応、次期バージョンでSpan<T>構造体が導入され、int[] / byte[]のようなメモリの読み替えも可能になりそうですが、肝心のSerialPortクラスはこれに対応しない気がしています。
- 回答としてマーク fushy 2018年3月5日 21:52
-
はいその通りです。.NETは不必要なコピーが発生することも、ヘッダー管理コストが生じることも把握した上で、それを上回るメモリー速度を前提として設計されています。本当にパフォーマンスが必要となる機能、例えばEncoding.GetStringなどではbyte*を扱うことでコピーを抑止できるようにも設計されています。ですが、SerialPortクラスはもちろん例えばTCP/IPを提供するSocketクラスですらbyte*は受け付けていません。
ですので「データのコピーが重そう」という考えは改めた方がいいかと。
- 回答としてマーク fushy 2018年3月5日 21:51
-
私もしばらく、組込みをやっていましたが、
組込みの世界と、Windows等の世界は別と考えた方が良いと思います。 PCの場合、見た目、わずかなコードでも、裏で沢山のモジュールが動いていることが多いです。 .NETだと、(CPUの)アセンブラコードが出力されないですね。(その分、遅い)
一方、組込みに比べ、圧倒的に速いCPUである事が多く、少々、効率が悪くても速い事が多いです。
また、アーキテクチャの違いで何が速いのか、異なる事が多いですね。 以前、良くあったのが、byteとfloatの多用。 確かに (昔の?) 組込みで使われるCPUでは、メモリ効率と速度で使う事が多かったですが、PCの場合、逆に遅くなる事もあり、要注意でした。
メモリバカ食いについても、無駄に使う必要はないですが、C#などでは、自動で後始末してくれるので、任せた方が、結果的に良い事が多いと思います。 (最近のWindows、何もしなくても 1G以上のメモリを使ってます。)個人的には、変に凝って、メンテナンス効率を下げるより、分かり易く書くべきと思っています。
(過去には、インラインアセンブラを Cコードに戻して、速くした事もあり)ご存知かも知れませんが、参考までに。