Windows共享內存編程-性能優化

[2019/11/24]

用share memory和semaphore相關的api寫了個ipc庫,用於進程間單向大數據流傳輸,目標傳輸速度:約120MB/s(算了一下自己內存DDR4 2400的理論極限帶寬= 2400 * 64 / 8 = 19200MB/s,我的目標應該是合理的)。
第一個版本已經完成,但是傳輸速度太慢了,大約1MB/s,檢查了一下寫數據的代碼:

int writeSyncBuf(SyncBuf syncBuf, const char *data, int len) {
    for (int i = 0; i < len;) {
        if (is_reader_close(syncBuf)) {
            logError("Opposite end has been closed.");
            return OPPOSITE_END_CLOSED;
        }
        // 等待一個寫資源
        if (WaitForSingleObject(syncBuf->hWriteSem, INFINITE) == WAIT_FAILED) {
            logError("Failed to wait write sem.");
            return OP_FAILED;
        }
        // 寫一個字節
        syncBuf->buf[sb_wc(syncBuf)] = data[i++];
        // 移動寫指針
        sb_wc(syncBuf) = (sb_wc(syncBuf) + 1) % sb_bufSz(syncBuf);
        // 增加一個讀資源
        ReleaseSemaphore(syncBuf->hReadSem, 1, NULL);
    }
    return OP_SUCCEED;
}

我覺得這裏等待信號量和釋放信號量太耗時了,決定不使用信號量(SyncBuf->buf即是從共享內存映射得到的指針)。

[2019/11/25]

不使用信號量要面對一下問題:

  • 如何實現IO阻塞與恢復?在緩存區寫滿時,要阻塞寫操作;在共享內存(以下均稱緩衝區)讀空時,要阻塞讀操作。讀進程掛起時,寫進程寫入新數據後要通知讀進程,讀進程掛起時也一樣。這個問題可以通過windows的事件機制解決。
  • 數據一致性?設計的使用場景爲一個寫進程一個讀進程,如何保證緩衝區的控制信息(如寫指針、讀指針)在讀寫兩端是一致的?舉個例子,讀進程要用讀指針和寫指針來計算可讀的字節數,假設要計算兩次,如果第二次計算前寫指針被寫進程修改了怎麼辦(髒讀)?我已經放棄了信號量,所以也不會使用其他同步方案(像互斥鎖啥的),我決定只計算一次:P例如在一次讀操作中,在一開始就在函數棧裏存下這些控制信息的副本,這樣就犧牲了數據的時效性,但避免了髒讀。

除此之外還有些細節:

  • 寫端關閉後,讀端可以把剩下的數據讀走,如果讀夠了就不報錯,否則報錯。
  • 讀端關閉後,禁止寫操作。
  • 一塊共享內存只能分別以讀方式和寫方式打開一次。

總結一下會被讀寫進程共享的數據,這些數據也應該放在共享內存裏作爲控制信息:

  • 讀、寫指針:指向下次要讀(寫)的位置
  • 緩衝區大小:等於共享內存空間 - 控制信息佔用空間。程序裏首先嚐試調用OpenFileMapping來獲得共享內存的句柄,打開失敗說明還沒有創建,然後調用CreateFileMapping進行創建。也就是說寫進程和讀進程中先嚐試打開共享內存的會進行創建操作,並在CreateFileMapping的參數中指定內存塊的大小,而後來的則不能指定內存塊的大小,所以後來的只能通過讀共享內存來獲取這個值。
  • 讀端、寫端狀態(打開、關閉):這應當是一個標誌位,會被讀寫進程頻繁訪問,肯定是要放共享內存裏的。
  • 緩衝區狀態(寫滿、讀空):沒有讀操作一直寫和沒有寫操作一直讀,會出現同一種情況——讀寫指針指向同一位置,所以需要該標誌來區分到底是寫滿還是讀空了。

最後,我設計出瞭如下結構體:

const u_char MARK_READER_OPEN     = 0b1000;
const u_char MARK_WRITER_OPEN     = 0b0100;
const u_char MARK_READER_CLOSE    = 0b0010;
const u_char MARK_WRITER_CLOSE    = 0b0001;

const u_char STATE_FULL     = 0x10;
const u_char STATE_EMPTY    = 0x01;

typedef struct syncBuf {
    HANDLE hWEvt, hREvt;
    char *buf;
    struct _shared {
        int bufSz, rc, wc;
        unsigned char state, mark;
    } *shared;
} *SyncBuf;

typedef struct channel {
    HANDLE hShareMem;
    int mode;
    SyncBuf syncBuf;
} *Channel;

這裏需要注意一下buf這個字段:它用來存儲調用MapViewOfFile得到的、共享內存的映射地址。我一開始把這個字段也歸爲共享字段的,後來發現,由於每個進程擁有自己的虛擬地址空間,所以這個字段在讀寫進程中的值並不會一樣。

未完待續。。。

Github倉庫地址

發佈了36 篇原創文章 · 獲贊 13 · 訪問量 6503
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章