.Net Framework 4.0開始有包好的MemoryMappedFile的類別了

  • 6444
  • 0
  • 2011-11-01

摘要:.Net Framework 4.0開始有包好的MemoryMappedFile的類別了

以前在.Net要用MemoryMap這種跟process的通訊,需用到P/Invoke。

從.Net Framework 4.0開始有包好的MemoryMap的類別了,可以開開心心的用了。

但有幾點要注意的,

(1)經過測試利用.Net包好的類別去讀取資料,效能會差一點

例如:以我測試的範例,

這需要約120ms

using (MemoryMappedViewAccessor mmva = _Map.CreateViewAccessor(0xA00000, 100 * 0x100000))
        {
          data.HDataPos = nSize;
          mmva.WriteArray<double>(nSize, HeightData, 0, nLen);
          data.HBytePos = data.HDataPos + nLen * nDoubleSize;
          mmva.WriteArray<byte>(data.HBytePos, btDataZ, 0, nLen);
          data.CDataPos = data.HBytePos + nLen * nByteSize;
          mmva.WriteArray<byte>(data.CDataPos, ConfData, 0, nLen);
          data.IDataPos = data.CDataPos + nLen * nByteSize;
          mmva.WriteArray<byte>(nSize += nLen * nByteSize, IntensityData, 0, nLen);
          data.DataBeginPos = data.IDataPos + nLen * nByteSize;
        } 
//用指標的方式,讀出MemoryMap只要約36ms
        using (MemoryMappedViewAccessor mmva = _Map.CreateViewAccessor(0xA00000, 100 * 0x100000))
        {
          unsafe
          {
            byte* pByteOriginal = null;
            try
            {
              mmva.SafeMemoryMappedViewHandle.AcquirePointer(ref pByteOriginal);
              double* pDouble = (double*)pByteOriginal;
              for (int i = 0; i < nLen; i++)
                HeightData2[i] = *pDouble++;
 
              byte* pByte = (byte*)pDouble;
              for (int i = 0; i < nLen; i++)
                btDataZ2[i] = *pByte++;
              for (int i = 0; i < nLen; i++)
                ConfData2[i] = *pByte++;
              for (int i = 0; i < nLen; i++)
                IntensityData2[i] = *pByte++ ;
            }
            finally
            {
              if (pByteOriginal != null)
                mmva.SafeMemoryMappedViewHandle.ReleasePointer();
            }
          }
  所以可以看出明顯的差別了。若在意效率的還是用指標吧。

(2) 1個MemoryMappedFile最大能開到約1.5G,最大就會出現OutOfMemory的例外。
(3) MemoryMappedFile的數目應該沒有限制,視系統能管理的上限而定。
(4) 雖然利用MemoryMappedFile可能資料不佔用程式的記憶體容量(一樣約1.5G的大小),
但建立CreateViewAccessor或CreateViewStream時,所開的記憶體存取大小,就會加到該程式的記憶體使用量中。
所以最好只建需存取的記憶體範圍即可,且用完就Release掉。

(5)將Structure寫入MemoryMappedFile,結構中不能含有任何的參考型別。如陣列,類別等。
以上是我測試的一點心得。詳情請自己參考MSDN中的說明。

2011/11/01
註1:關於第4點我另外弄了個測試,在工作管理員中該Process並沒有看到記憶體增加的情況。
這讓我很困惑,以前是怎麼測的。下面列出這次測試的程式碼。
MemoryMappedFile[] _MMF = new MemoryMappedFile[10];
    string _MMFName = "TestABC";
    int _Capacity = 200 * 0x100000;
    private void button17_Click(object senderEventArgs e)
    {
      try
      {
        byte[] byteARray = new byte[0x100000];
        for (int i = 0; i < 0x100000; i++)
          byteARray[i] = 0xA;
        for (int i = 0; i < 10; i++)
        {
          Trace.WriteLine(i.ToString(), "MemoryMappedFile.CreateOrOpen");
          _MMF[i] = MemoryMappedFile.CreateOrOpen(_MMFName + i.ToString(), _Capacity);
          using(MemoryMappedViewStream mmvs = _MMF[i].CreateViewStream(0, 0))
          {
            for (int j = 0; j < 200; j++)
              mmvs.Write(byteARray, 0, 0x100000);
            System.Threading.Thread.Sleep(3000); //用來查看工作管理員中,該Process的記憶體使用狀況。
          }
        }
      }
      catch (System.Exception ex)
      {
        MessageBox.Show(ex.Message);
      }
    }

2011/11/01
註2:剛又修改一下測試碼,發現,以前的測試方式應該是將MemoryMappedViewStream 建為全域的變數所造成的。
如下:
MemoryMappedFile[] _MMF = new MemoryMappedFile[10];
    MemoryMappedViewStream[] _MMVS = new MemoryMappedViewStream[10];
    string _MMFName = "TestABC";
    int _Capacity = 200 * 0x100000;
    private void button17_Click(object senderEventArgs e)
    {
      try
      {
        byte[] byteARray = new byte[0x100000];
        for (int i = 0; i < 0x100000; i++)
          byteARray[i] = 0xA;
        for (int i = 0; i < 10; i++)
        {
          Trace.WriteLine(i.ToString(), "MemoryMappedFile.CreateOrOpen");
          _MMF[i] = MemoryMappedFile.CreateOrOpen(_MMFName + i.ToString(), _Capacity);
          _MMVS[i] = _MMF[i].CreateViewStream(0, 0);
          {
            for (int j = 0; j < 200; j++)
              _MMVS[i].Write(byteARray, 0, 0x100000);
          }
          Trace.WriteLine(i.ToString(), "_MMF[i].CreateViewStream");
        }
      }
      catch (System.Exception ex)
      {
        MessageBox.Show(ex.Message);
      }
    }

因為MemoryMappedViewStream 為全域的,所以都沒有釋放,所以第5個MemoryMappedViewStream 要產生的,就會發生"儲放空間不足,無法處理此指令"的例外。

所以MemoryMappedViewStream 的確會算到同一Process的記憶體使用空間,所以決對不要使用全域的方式。

另外還有一點要注意的,在工作管理員中,該Process的記憶體使用狀況並沒有加上MemoryMappedViewStream 的部份,所以看起來很少。那是不準的。

 

============ 以下是簽名檔 ============

一個小小螺絲釘。

第一次建立Blog,希望以後能慢慢充實它。

Howard