2011-12-22 53 views
2

我寫了一個股票市場模擬器,它使用一個ConcurrentHashMap作爲緩存。寫一個高性能的緩存

緩存容納大約75個元素,但它們更新和檢索得非常快(每秒約500次)。

這裏是我做過什麼:

主題1:

連接到爲我提供串流報價爲給定的股票代碼的外部系統。

線程2(回調線):

等到數據被外部系統交付給它。一旦獲取數據,它就會解析數據,創建一個不可變的DataEntry對象,將其緩存起來並向thread3發送一個信號。

線程3(消費者線程): 收到信號後,從緩存中檢索DataEntry並使用它。 (這是任務中不讓thread2直接將數據推送到thread3的任務的一部分)。

public final class DataEntry{ 

     private final String field1; 
     private final String field2; 
     //... 
     private final String field25; 

     // Corresponding setters and getters 

} 

public final class Cache{ 

     private final Map<String, DataEntry> cache; 

     public Cache(){ 
      this.cache = new ConcurrentHashMap<String, DataEntry> (65, 0.75, 32); 
     } 

     // Methods to update and retrieve DataEntry from the cache. 
} 

通過分析器運行它之後,我發現我創建一個很多DataEntry對象。因此伊甸園很快就要填滿了。

所以,我想通過調整設計了一下:

一)使得DataEntry級可變。

b)用空的DataEntry對象預填充緩存。

c)當更新到達時,從地圖檢索DataEntry對象並填充字段。

這樣,DataEntry對象的數量將不變並等於元素的數量。

我的問題是:

一)這是否設計有一個我可以通過使DataEntry可變紛紛推出任何併發問題。

b)我還有什麼可以優化緩存嗎?

謝謝。

+0

您可以每秒訪問超過一百萬次的ConcurrentHasMap,如果您只訪問它,它不會有太大影響500次/秒。 – 2011-12-22 15:27:14

+0

如果您希望一次接近16個或更多內核,則只會增加分區大小。如果您使用的核心數量少於說4個內核同時訪問地圖(而沒有做其他任何事情),那麼它不太可能會產生很大的差異。 – 2011-12-22 15:30:24

+0

爲什麼Eden很快就會填滿問題?您是否因Eden GC而遇到問題? – kdgregory 2011-12-22 21:42:05

回答

1

我不會擔心的ConcurrentHashMap

的速度
Map<Integer, Integer> map = new ConcurrentHashMap<>(); 
long start = System.nanoTime(); 
int runs = 200*1000*1000; 
for (int r = 0; r < runs; r++) { 
    map.put(r & 127, r & 127); 
    map.get((~r) & 127); 
} 
long time = System.nanoTime() - start; 
System.out.printf("Throughput of %.1f million accesses per second%n", 
     2 * runs/1e6/(time/1e9)); 

打印

Throughput of 72.6 million accesses per second 

這是遠遠超出你似乎要使用的接入速率。

如果你想減少垃圾,你可以使用可變對象和原語。由於這個原因,我會避免使用字符串(因爲你似乎有比字符串多得多的字符串)

+0

謝謝彼得。還有一個問題,如果我可以,如果我使DataEntry可變,我必須鎖定地圖,而我將它添加到地圖,不是嗎?或者我可以用AtomicReference包裝可變的DataEntry?乾杯(順便說一句,愛你的博客)。 – CaptainHastings 2011-12-22 21:41:08

+1

對於高度併發的系統,默認的併發散列表並不是一個好主意,但我同意從描述來看,這似乎不太可能(我認爲任何有幾十個線程高度併發的任何東西都在這裏YMMV) 。無論如何,從描述來看,hashmap似乎是一個奇怪的選擇。 – Voo 2011-12-22 21:46:21

+0

如果您使用的是可變數據條目,我只會添加一次(最好在啓動時)您是否需要每臺儀器多於一個?歡呼關於博客。 ;) – 2011-12-23 08:20:36

1

這聽起來像您使用的是ConcurrentHashMap,當你真的需要是像併發隊列 - 說,一個LinkedBlockingQueue

+0

嗨,其實我確實需要一張地圖,因爲我想讓感興趣的各方輪詢新數據而不是推送給他們。 – CaptainHastings 2011-12-22 21:45:18

+1

嗯...我不確定地圖是否仍然是最好的數據結構。你有沒有想過爲每個「感興趣的派對」單獨使用隊列? – 2011-12-22 22:10:10

1
  • a。是的,它確實。可變的DataEntry對象可以在沒有讀者注意的情況下更新,這將導致不一致的狀態。
  • b。是的,你可以:做一個可變的DataEntryCache,根據請求返回不可變的DataEntry。這樣,您將在讀取時創建新的DataEntry對象,而不是寫入。 DataEntryCache可以內部緩存它構造並返回的不可變的DataEntry,並在變異調用時使該「緩存」無效。

編輯:我假設你正在緩存(而不是創建線程2和3之間的隊列)的原因是,消費者線程可以讀取除了其他條目的其中之一螺紋2發送通知。如果這種假設不正確,則根本不需要緩存。

+0

+1,雖然你對b的回答看起來像是一種競爭條件 – kdgregory 2011-12-22 21:40:53

+0

嗨,我需要一張地圖,這樣我可以要求感興趣的各方爲新數據輪詢而不是推送給他們。 – CaptainHastings 2011-12-22 21:47:18

+0

@kdgregory這個操作需要在'DataEntryCache'內部同步。 – dasblinkenlight 2011-12-22 23:24:41

0

a)在我的代碼對象創建往往表現爲瓶頸,所以我認爲你自己的想法重用DataEntry對象也值得實施。但是,正如kdgregory評論的那樣,簡單地覆蓋當前元素將導致讀取不一致的條目。因此,在更新條目時,請寫入新的或可用的重複使用的空閒(例如閒置幾分鐘)條目並將其放入地圖中。將新條目放入地圖後,將舊條目放在某種空閒列表中。爲了完全安全,不允許讀取線程訪問高速緩存提供的DataEntry,例如, 1分鐘。如果線程可能被阻塞,他們應該複製DataEntry對象,也許爲此重用自己的對象。

b)您目前的設計是模塊化的,但涉及許多上下文切換,因爲線程反映了模塊。我會嘗試一個設計,其中一個請求從一個線程開始到完成。請求可以是對新的DataEntry對象的完整處理。實現此功能的併發設計模式爲Leader/FollowerHalf-Sync/Half-Asynch

+0

「我沒有看到明顯的併發問題」 - 如果一個線程正在從對象讀取值而另一個線程正在寫入值,會發生什麼? – kdgregory 2011-12-22 21:39:08

+0

感謝您的鏈接,今天將通過他們閱讀。 – CaptainHastings 2011-12-22 21:48:06

+0

@ kdgegory,謝謝。那*很明顯;)。我更新了我的答案。 – 2011-12-23 13:49:55