2015-10-15 72 views
2

我有兩個線程和一個大的數據集。線程R連續讀取數據集並向用戶顯示數據視圖。線程W不斷接收遠程數據,對其執行一些工作並將其發佈到數據集。別名在Windows上的物理內存

線程R需要控制其接收數據集一致視圖的粒度。一種解決方案是雙緩衝;當R從另一個拷貝讀取時,W寫入一個拷貝,並且當R準備進行更新時,W的拷貝被原子地拷貝到R(禁止,因爲數據集很大並且大部分沒有變化),或者它們以原子方式交換拷貝並且W帶來R的舊通過重新應用自上次交換以來的增量更改(惱人地追蹤這些更改,以及煩惱所有的增量必須被處理兩次)來複制到目前爲止。

我希望做的是以下幾點:

  • 兩個線程獨立保留虛擬只讀存儲器範圍,並
  • 線程W¯¯安裝的兩個範圍被映射到相同的物理組頁面異常處理程序捕獲寫入只讀頁面,抓取新的物理塊,將其映射爲readwrite,然後讓寫入重新嘗試
  • 當R想要更新時,以原子方式(W已替換的任何物理頁面被釋放(或返回到池中),然後這些虛擬內存地址得到W的新物理pa那麼W將其整個範圍再次標記)。

這避免了額外的內存拷貝,需要跟蹤並重新三角洲等

然而,AFAICT儘管Windows確實允許共享內存區域(甚至自動寫入時複製存儲區)的創建,它似乎走出了一條路,使它不可能以任何方式明確地映射物理頁面,W可以用它來發佈一個新的視圖R.

有什麼我失蹤? - 是否有可能實現這樣的事情,一個純粹通過改變頁面映射實現的發佈步驟,沒有內存拷貝?

+0

閱讀所有可以找到的文檔,所有對COW的引用都是在寫作的上下文中,通過編寫COW映射爲自己創建私有副本。我內心工作的思維模式是通過類似我的問題中的異常機制之類的東西,並且我一直假設,如果您要求爲一系列地址提供rw保護,這就是您真正獲得的地址,而不管存在其他映射,並且在那個世界中,由於寫入者的寫入沒有被捕獲,所以沒有辦法通過COW行爲來實現讀取器快照。我的假設錯了嗎? – moonshadow

+0

AWE與VirtualProtect()結合使用可能會做到這一點。如果沒有,那可能是從設備驅動程序。但是,寫入時複製是否會涉及至少與原子交換和正確方法相同的內存複製?無論哪種方式,寫入的每個塊都被複制一次。 –

+0

不,AWE將不起作用:「物理頁不能同時映射到多個虛擬地址。」 –

回答

1

我認爲應該有可能做一些事情,以接近你所要求的一些小伎倆。

我將描述我認爲最簡單,最有效,但最不靈活的方法,並將其稱爲方法A。使用這種方法的數據必須被佈置在塊組,並且每個塊必須完全在單個頁面內包含:

  • 創建W上的讀/寫圖和寫入時複製視圖用於R,使用相同的文件映射對象。

  • 每當W想要修改數據塊時,它首先在寫時複製視圖中對相應的塊執行虛擬寫入。

    注意:寫入頁面會導致寫入時複製,即使寫入並未真正改變內容,但爲了安全起見,我建議避免這種假設,您可以通過包含每個數據塊中的虛擬字節,即R將忽略的一個虛擬字節。 W然後可以增加虛擬字節以確保相應的頁面被複制。

  • 要同步,請丟棄現有的寫時複製視圖並創建一個新視圖。

我期望不必要的虛擬寫入的開銷可以忽略不計,但對齊塊以避免重疊頁面邊界可能不方便。

如果是這樣,方法B與方法A相同,除了有多個虛擬字節,間隔放置確保每個頁面重疊該塊包含至少一個虛擬字節。這增加了虛擬寫入的開銷,但我不希望它過度。但是,W每次需要進行更改時都會明確地進行這些虛擬寫入,例如,如果數據實際上不是按塊排列,或者如果每個塊內有多個虛擬字節,則可能會很尷尬不方便。因此,我們應該考慮方法Ç

  • 同時創建一個只讀和W上的讀寫視圖,以及在寫入時複製視圖R.包含W使用read - 僅查看讀取數據,但讀寫視圖寫入。

  • 使用VirtualProtect和PAGE_GUARD來保護讀寫視圖中的所有頁面。

  • 當防護頁錯誤被觸發時,讓異常處理程序在寫時複製視圖中的相應頁面上進行虛擬寫入。向量化的異常處理程序在我看來像最乾淨的選項。

    注意:我的研究表明,儘管事實上這涉及到故意在頁面錯誤處理程序中調用頁面錯誤,但這並不是非常確切的說明。它應該得到支持,因爲任何異常處理程序都沒有合理的方法來確保它不引用被分頁的數據,但是由於我沒有找到明確的說明,因此建議進行一些實驗。

方法C是可能比阿效率較低或B,因爲它需要處理的額外頁錯誤異常,與相應的另外的往返行程到內核模式和背部。我也不確定涉及跟蹤防護頁面的頁面表開銷。但是,它可能更加方便,因爲消除來自處理代碼的虛擬寫入減少了代碼需要了解緩衝的程度。

最終變體避免了處理代碼通過使用單個視圖來知道緩衝的所有的需要,而不管W是讀還是寫。 方法d如下:

  • 創建W上的讀/寫視圖和寫入時複製視圖R.

  • 用VirtualProtect改變上的所有網頁的權限讀/寫視圖爲只讀。

  • 當觸發頁面錯誤時,讓異常處理程序更改錯誤頁面上的權限以讀取/寫入,並在寫時複製視圖中對相應頁面進行虛擬寫入。

我相信這種方法效率最低,因爲我希望顯式更改塊的權限會比使用防護頁顯着慢。它也可能導致頁表更多碎片化。但是,如果事實證明它的性能足夠好,那幾乎肯定是最方便的解決方案。


一些其他注意事項:

我相信,所有這些方法應該工作,受到一個警告關於到底發生了什麼的時候,同時處理的第一個第二個頁面錯誤被觸發。對於不同變體的比較效率,我沒有那麼自信。做一些比較測試可能是明智的。

文件映射對象可能應該由頁面文件支持,並且您可能需要嘗試使用大頁面。這會增加需要複製的數據量,但會減少頁表上的負載。再次,比較測試可能是適當的。

我假設您已經考慮過這一點,但未來的讀者應該注意,根據數據的性質,根本不可能使用映射。例如:

  • 的數據塊可以給予兩個修訂號,一個指示何時該塊變爲有效,而另一個時,它應該被認爲刪除。這種方法的時間開銷非常小,R只需要在處理塊時檢查版本號,以便它可以跳過太新的塊並刪除過時的塊。這涉及到較少的數據複製:W只需複製它正在處理的數據塊,而不是整個頁面,並且添加/刪除塊根本不需要複製任何數據。

  • 如果塊需要按特定順序鏈接,修訂版號可能不夠,但您可以爲R和W分開鏈。同步將要求您重新鏈接塊,但這仍然可能比修改頁面表更快。

0

即使有人會創建你想要的API,做這個CoW的東西。 即使您將切換到單獨的進程而不是線程(每個進程只有1個頁表,但不能有2個線程在同一地址上看到不同的數據)。

如果您要修改/重新映射隨機4kb頁面,您將要waste gigabytes of RAM for your page table。不僅會浪費RAM,還會降低性能。

我覺得你所看到的問題太籠統了,你試圖在抽象層次太低的問題上解決它。

併發/性能要求有多重?你能否鎖定你的數據庫,這樣閱讀和寫作就不會同時發生?

你的觀點到底是什麼?你可以在啓動時創建視圖,新消息到達時,在線程W上更新數據庫,並更新線程R上的視圖?

整個事情是否持久?如果是的話,只需使用帶有事務隔離功能的嵌入式NoSQL引擎。例如ESENT,或者如果您的軟件是跨平臺的,LMDB可能適合。

+0

沒有必要在相同的地址上看到不同的數據;它實際上是相反的,我想將相同的數據映射到不同的地址。 「爲頁表浪費千兆字節的RAM」只有當數據集的大部分對於不同視圖不同時纔是真實的,並且備選方案甚至更糟(雙緩存意味着數據集的兩個完整副本,爲每個讀取鎖定整個事物並且寫本質意味着序列化讀者和寫者,爲什麼甚至打擾把他們放在單獨的線程,如果這就是你在做什麼?這個想法是解耦他們!) – moonshadow

+0

「浪費GB的RAM頁面表只有如果大比例的數據集對於不同視圖而言是不同的「 - 在對數據集進行多次更改後,您將有連續的VMEM區域映射到隨機放置的一組RAM頁面。這需要每頁兩頁的表格條目(因爲你的數據集被映射到兩個VMEM位置)。 「雙緩衝意味着數據集的兩個完整副本」 - 如果您有16GB數據集,則不能擁有16 GB視圖。即使你正在研究一個視頻遊戲,其視圖可能非常複雜,VRAM是2-4GB,所以你的視圖不能比這個大得多 – Soonts

+0

「爲每個讀寫操作鎖定整個事物本質上意味着序列化讀寫器「 - 你是否試圖實現鎖定? C++關鍵部分/ C#監視器非常高效。 「爲什麼還要把它們放在不同的線程上」 - 因爲「接收遠程數據,在它上面執行一些工作」將在後臺線程中完成,鎖定只需要「發佈」更改。此外,您可以優化:使後臺線程不會發布更改,然後將它們全部發布在一個鎖中。 – Soonts

相關問題