摘要:.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 sender, EventArgs 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 sender, EventArgs 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