none
.NET C#上での配列を含んだ共用体の作成方法 RRS feed

  • 質問

  • 目的

     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[]の方法としてマーシャリングや、シリアライズがあることは承知の上で、

    データのコピーが重そうなので、より簡便な方法を探しています。

    よろしくお願いします。


    2018年3月5日 12:38

回答

  • データのコピーが重そうなので、より簡便な方法を探しています。
    全くの気のせいです。シリアルポートの転送速度がせいぜい115,200bit/s程度なのに対し、DRAMの転送速度は161,061,273,600bit/s(DDR3-2400)といった具合に桁違いです。メモリのコピーコストを気にするよりは簡便なソースコードを目指すことをお勧めします。
    • 回答としてマーク fushy 2018年3月5日 21:54
    2018年3月5日 12:53
  • .NETではガベージコレクタによりメモリ管理が行われており、全てのオブジェクトには8バイト/12バイトのヘッダーが含まれています。そしてそのヘッダーによりint[]やbyte[]が識別されています。ですので、SerialPort.ReadやSerialPort.Writeがbyte[]を要求する以上はこれを用意する以外に方法はありません。せめてBuffer.BlockCopyを使うとかその程度です。

    一応、次期バージョンでSpan<T>構造体が導入され、int[] / byte[]のようなメモリの読み替えも可能になりそうですが、肝心のSerialPortクラスはこれに対応しない気がしています。

    • 回答としてマーク fushy 2018年3月5日 21:52
    2018年3月5日 14:09
  • はいその通りです。.NETは不必要なコピーが発生することも、ヘッダー管理コストが生じることも把握した上で、それを上回るメモリー速度を前提として設計されています。本当にパフォーマンスが必要となる機能、例えばEncoding.GetStringなどではbyte*を扱うことでコピーを抑止できるようにも設計されています。ですが、SerialPortクラスはもちろん例えばTCP/IPを提供するSocketクラスですらbyte*は受け付けていません。

    ですので「データのコピーが重そう」という考えは改めた方がいいかと。

    • 回答としてマーク fushy 2018年3月5日 21:51
    2018年3月5日 21:50

すべての返信

  • データのコピーが重そうなので、より簡便な方法を探しています。
    全くの気のせいです。シリアルポートの転送速度がせいぜい115,200bit/s程度なのに対し、DRAMの転送速度は161,061,273,600bit/s(DDR3-2400)といった具合に桁違いです。メモリのコピーコストを気にするよりは簡便なソースコードを目指すことをお勧めします。
    • 回答としてマーク fushy 2018年3月5日 21:54
    2018年3月5日 12:53
  • 返信有難うございます。

    たしかにそうなのですが、たかが受信程度で2回3回とコピーをして、2倍、3倍のバッファーを用意するのも、もったいないと思いまして質問を上げた次第であります。

    また、マーシャリング等の手間も省けると思いました、上記コードでより簡便にする方法はどのような方法でしょうか。

    ご教授いただけると幸いです。


    2018年3月5日 13:10
  • 佐祐理さんの書かれてるように、メモリ内のデータ転送速度が圧倒的に速いので、まずは分かり易いソースにして、問題となってから、調べた方が良いと思います。
    そして大抵は、メモリ転送でなく、別のところに問題がある事が多いですね。不必要にロックしているとか、、。 特に通信がらみは、色々と待ちとか入るので。

    • 回答としてマーク fushy 2018年3月5日 21:52
    • 回答としてマークされていない fushy 2018年3月5日 21:54
    2018年3月5日 13:12
  • 返信ありがとうございます。

    やはり、2倍、3倍のバッファーを用意してマーシャリングするのが一般的なのですね。

    組み込みやってる人間からするとちょっとメモリのバカ食いが不気味で、怖いのですが、

    郷に入れば郷に従えなのでしょうか。


    • 編集済み fushy 2018年3月5日 13:40
    2018年3月5日 13:35
  • .NETではガベージコレクタによりメモリ管理が行われており、全てのオブジェクトには8バイト/12バイトのヘッダーが含まれています。そしてそのヘッダーによりint[]やbyte[]が識別されています。ですので、SerialPort.ReadやSerialPort.Writeがbyte[]を要求する以上はこれを用意する以外に方法はありません。せめてBuffer.BlockCopyを使うとかその程度です。

    一応、次期バージョンでSpan<T>構造体が導入され、int[] / byte[]のようなメモリの読み替えも可能になりそうですが、肝心のSerialPortクラスはこれに対応しない気がしています。

    • 回答としてマーク fushy 2018年3月5日 21:52
    2018年3月5日 14:09
  • そのバイトヘッダーのせいで、上記ソースのような

    DataPacketsを定義して活用することはできないという認識でよいのでしょうか。

    2018年3月5日 14:17
  • はいその通りです。.NETは不必要なコピーが発生することも、ヘッダー管理コストが生じることも把握した上で、それを上回るメモリー速度を前提として設計されています。本当にパフォーマンスが必要となる機能、例えばEncoding.GetStringなどではbyte*を扱うことでコピーを抑止できるようにも設計されています。ですが、SerialPortクラスはもちろん例えばTCP/IPを提供するSocketクラスですらbyte*は受け付けていません。

    ですので「データのコピーが重そう」という考えは改めた方がいいかと。

    • 回答としてマーク fushy 2018年3月5日 21:51
    2018年3月5日 21:50
  • 丁寧な解説ありがとうございます。

    .NETの流儀に従った、送受信マネージャクラスの検討をしてみます。

    2018年3月5日 21:54
  • 私もしばらく、組込みをやっていましたが、

    組込みの世界と、Windows等の世界は別と考えた方が良いと思います。 PCの場合、見た目、わずかなコードでも、裏で沢山のモジュールが動いていることが多いです。 .NETだと、(CPUの)アセンブラコードが出力されないですね。(その分、遅い)
    一方、組込みに比べ、圧倒的に速いCPUである事が多く、少々、効率が悪くても速い事が多いです。
    また、アーキテクチャの違いで何が速いのか、異なる事が多いですね。 以前、良くあったのが、byteとfloatの多用。 確かに (昔の?) 組込みで使われるCPUでは、メモリ効率と速度で使う事が多かったですが、PCの場合、逆に遅くなる事もあり、要注意でした。
    メモリバカ食いについても、無駄に使う必要はないですが、C#などでは、自動で後始末してくれるので、任せた方が、結果的に良い事が多いと思います。 (最近のWindows、何もしなくても 1G以上のメモリを使ってます。)

    個人的には、変に凝って、メンテナンス効率を下げるより、分かり易く書くべきと思っています。
    (過去には、インラインアセンブラを Cコードに戻して、速くした事もあり)

    ご存知かも知れませんが、参考までに。

    2018年3月6日 12:11