公司簡介 產品資訊 教育訓練 活動資訊 鈦思書網 技術支援 工程師俱樂部 會員登入



 

利用記憶體映圖檔案(Memory Mapped Files)加快大量資料之傳輸速度(下)

鈦思科技股份有限公司
企劃部專員 黃忻 譯


簡單的MEX檔案
MEX檔案是一個擁有一個標準進入點(entry point)的動態聯結函式庫(DLL),而在MATLAB環境下DLL檔是可以被呼叫的。(請參考外部API使用手冊,external API guide中的完整說明)

下面這段程式碼顯示我們把這個標準進入點命名為mexFunction。當一個MATLAB使用者引用這個MEX函式時,這個進入點便是MATLAB開始執行的函式。

您可以看到程式中有四個變數:

1. 輸出變數數目,Number of left hand side arguments, nlhs

2. 輸出變數的指標陣列,Pointer to the list of variables on the left hand side, plhs[]

3. 輸入變數數目,Number of right hand side arguments, nrhs

4. 輸入變數的指標陣列,Pointer to the list of variables on the right hand side, prhs[]

在這裡「輸出」及「輸入」指的是在一個數學式的「等號之前」及「等號之後」的部分。例如:若您鍵入a=sum([1:10]); 在MATLAB中將其解釋為一個輸出變數a及一個輸入變數[0:10]
 
// ***********************************************************
// MEX Interface.
// ***********************************************************
// MATLAB MAIN entry point.
extern "C"
void mexFunction( int nlhs, mxArray* plhs[],
int nrhs, const mxArray* prhs[])
{
//*********************************************************
// Register the MEX exit function first time thru mex file.
// Setup a persistent mxArray that we will us for passing data
//*********************************************************
if (initFlag)
{
mexAtExit(mexExitFunction);
persistent_array_ptr = mxCreateDoubleMatrix(1,1,mxREAL);
mexMakeArrayPersistent(persistent_array_ptr);
memory_holder = mxGetPr(persistent_array_ptr);
initFlag = false ;
}
// Error Checking removed for clarity of code
段落1:MEX檔案:主要進入點及清理準備工作
 
為了方便說明,本文中將整段MEX程式碼分成數個段落。段落1的作用在於執行一些清理準備的工作,並在MEX檔案第一次被呼叫時執行。接下來的段落會針對傳送的參數做一些錯誤檢查的動作。為了清楚簡潔地說明程式碼,我們並沒有將這個段落的程式碼列出,但在本文最後的project code部分,您依舊可以看到這個段落。
// **********************************************************
// Actual work of the MEX file.
// **********************************************************
if (nrhs==1)
{ // User wants to put data into memory map file
hObj->putData(mxGetPr(prhs[0]),
mxGetNumberOfElements(prhs[0]));
}
else // Or user wants to get data from memory mapped file.
{
plhs[0]=persistent_array_ptr;

// Notice that we pass back a pointer to the mem-map
// and we do not copy the data out of the point
mxSetPr(plhs[0],hObj->getPointer());
// Tell the mxArray the size of the data in the mem-map
mxSetN(plhs[0],hObj->getLength());
}
}
段落2:MEX檔案:MEX檔案實際執行的工作
 
段落二是實際執行將資料放到記憶體映圖檔案或從記憶體映圖檔案存取資料等工作的部分。我們假設當一個參數被傳送進來呼叫MEX檔案時,這個傳送進來的參數會被儲存起來,同時我們將送出一個指向真正資料陣列的指標,以及該資料陣列的長度給helper物件。如果沒有參數傳送進來,我們則假設我們應該將所有儲存於記憶體映圖檔案中的資料回傳。所以我們將儲存起來的記憶體映圖陣列指標及陣列長度以mxArray型態的變數中的適當欄位代換後,回傳至MATLAB。這邊需要注意的一個步驟是,我們並沒有對記憶體做複製,而僅是移動指標。這樣的做法之所以可行是由於我們已經在MATLAB中宣告變數為persistent型態變數,不容許以任何方法強制MATLAB移除或更改此變數。
 
Helper物件程式碼
為了在整段程式中有一個中央位置處理資料的設定及存取等工作,我們建立了一個物件。這個物件的唯一作用在於跟記憶體映圖溝通。這段程式會在上面的MEX檔案中使用,同時也會在使用者應用程式碼中被使用,以便與記憶體映圖檔案溝通。
MMFile::MMFile(void)
{
// Create memory mapped file
hMMFile = CreateFileMapping (INVALID_HANDLE_VALUE,
NULL, // default security
PAGE_READWRITE, // read/write permission
0, // max object size
0x01400000, // size of hFile
szMapFileName); // name of mapping Object

// Map a view of this file for us to user in this process.
PointerLength = MapViewOfFile (hMMFile, //Handle to MAPPED Obj
FILE_MAP_ALL_ACCESS,
0,
0,
0);
// Save pointer to data beyond beginning of data array
PointerData = (LPVOID) ((long long)PointerLength+sizeof(long));
}
段落3:Helper物件:建構子
 
欲建立這個物件首先我們必須以CreateFileMapping指令建立記憶體映圖檔案。想要讓兩個應用程式共用資料的關鍵,在於使兩個應用程式開啟同一個檔案。上面這段程式中出現的參數szMapFileName即為這個檔案的名稱。由於我們在兩個應用程式同時使用的檔案中定義了這個名稱,因此我們所有的開發專案程式都將使用同一個記憶體映圖檔案。
 
當我們有了指向記憶體映圖檔案的指標之後,我們需要使用MapViewOfFile函式將這個檔案映射到目前處理程序之記憶體空間中。每當一個程序被附加到DLL檔時,我們便建立一個新的helper物件,因此每次DLL檔載入時就會重新呼叫一次。現在我們將會把記憶體映圖檔案映射到目前應用程式處理程序所使用的記憶體空間。或者,若我們在專案中直接使用這段程式,則每次當我們建立一個物件時,我們將取得一個指向記憶體映圖檔的指標,並映射至目前處理程序之記憶體映圖中。
 
整個建立物件流程的最後一步是取得一個指向資料陣列的指標。由於一個記憶體映圖看起來就像是一個資料陣列,我們將使用一個long的大小來維持有效資料陣列的長度,並在其後開始儲存資料。現在,當我們將資料存入陣列時,我們便可得知有多少資料點為有效資料。
 
// Destructor
MMFile::~MMFile()
{
UnmapViewOfFile (PointerLength);
CloseHandle (hMMFile);
}
段落4:Helper物件:解建構子
 
在解建構子的過程中,我們只需unmap我們對資料的連結,並告訴記憶體映圖檔案我們不再需要它了。若此時沒有其他程式在使用記憶體映圖,便可釋放此記憶體映圖所佔用的記憶體空間給系統使用。
 
// Put data into the file
void MMFile::putData(double * newData, long len)
{
long dataLength = 0x01400000-sizeof(long);
// Length of memory map
if (len<=dataLength)
{
memcpy(PointerData,newData,len*sizeof(double));
setLength(len);
}
}

// Get data from the file
void MMFile::getData(long len, double *retData)
{
long dataLength = getLength();
if (len<=dataLength)
{
memcpy(retData,PointerData,len*sizeof(double));
}
else
{ // Should through an error here.
memcpy(retData,PointerData,dataLength*sizeof(double));
}
}

// Get pointer to data in file
double* MMFile::getPointer(void)
{
return (double*)PointerData;
}
段落5:Helper物件:由記憶體映圖檔案傳送及接收資料
 
Helper物件最重要的作用在於傳送及接收資料。我們將簡略地介紹這部分所用的函式。在putData函式中我們對傳送至記憶體映圖檔案的資料作資料複製的動作。在getData函式中我們同樣做一次資料複製,但如果您有印象,在MEX檔案中我們並沒有使用getData函式,而是使用getPointer函式以將資料導入MATLAB。這使我們不需使用資料複製指令,就能將記憶體映圖檔案所映射之資料移至MATLAB,進而大幅提昇我們接收資料的速度。
 
如何連結使用者應用程式碼
如果您要建立一個將資料存入記憶體映圖檔案並且被MATLAB充分利用的程式,您只需將這個helper物件包在您的專案中,並且呼叫適當的函式即可。下面這段範例程式將示範如何把10個數字放入記憶體映圖檔案中。如果您已經呼叫mmfile函式,記憶體映圖便已存在,同時記憶體映圖中的資料會被範例程式所寫入的10筆資料替換掉。
 
#include "../MMFileDemo/MMFile.h"

int _tmain(int argc, _TCHAR* argv[])
{
double data[] = {0,1,2,3,4,5,6,7,8,9};
MMFile *Obj = new MMFile();

// Put the number 0-9 into the array.
Obj->putData(data,10);

// Once we leave we will not have a connection to
// the memory mapped file
return 0;
段落6:使用者應用程式碼:將資料放入記憶體映圖檔案中
 
接下來這段程式將由記憶體映圖檔案中取出10筆數字並將它們顯示出來。
 
#include "../MMFileDemo/MMFile.h"

int _tmain(int argc, _TCHAR* argv[])
{
MMFile *Obj = new MMFile();
double data[10];

// Get the first 10 elements in the MemoryMaped file
Obj->getData(10,&data[0]);

// Display the numbers
printf("{"};
for (int idx=0;idx<9;idx++)
printf("%g, ",data[idx]);

printf("%g)\n",data[9]);

// We will delete the Obj when we leave therefore remove
// any connection to the Memory Mapped file.

return 0;
}
段落7:使用者應用程式碼:由記憶體映圖檔案抓取資料
 
如何連結使用者應用程式碼
您可以至美國The MathWorks原廠網站下載本文中提及的這個MEX檔案。在使用方法上,若您想要將資料傳入記憶體映圖檔案,則使用適當的變數作為輸入資料(input)並呼叫mmfile函式;若想由記憶體映圖檔案中抓取資料,則使用適當變數作為輸出資料(output)並呼叫mmfile函式。
 
indata=mmfile; 將資料由記憶體映圖檔案中取出並放入變數indata
mmfile(outdata); 將資料由MATLAB變數outdata放入記憶體映圖檔案中
 
這些MATLAB語法使您可以很容易地將資料存入記憶體映圖檔案或由記憶體映圖檔案中取出資料。

mmfile函式被呼叫時,MEX檔案會持續存在於記憶體中並可被其他應用程式呼叫。當您呼叫clear mex時,MEX檔案將由記憶體中被釋放,同時刪除helper物件的函式會被呼叫。若此時沒有其他應用程式連結到此記憶體映圖檔案,這個檔案便會被刪除。