以C++為核心語言的高頻交易系統是如何做到低延遲的?

面試的時候問到了這個問題,本人是做基於C++的電信後台業務的,常用的性能優化手段也略知一二,但是這個問題就苦於無從入手了


我們的程序的響應時間是10us(從收到行情到發出報單的響應時間),但是ping期貨公司的交易前置機需要大約30us【這個數值會變化,見注釋4】,所以網路延時佔據了大量時間。


我所有的性能測試都是在一台DELL r630機器上運行的,這台機器有2個NUMA結點,CPU型號是E5 2643 v4(3.4GHz 6核)。所有的測試都是用rdtsc指令來測量時間,Intel官網上有一篇pdf文檔[Gabriele Paoloni, 2010],講述了如何精準地測量時間(要用cpuid來同步)。我自己做的性能測試的結果會寫成「100(sd20)ns」的形式,代表平均值是100ns,標準差是20ns。我在算均值和標準差的時候會去掉最大的0.1%的數據再算,因為那些數據似乎並不是程序延時,而是cpu被調度執行別的任務了【原因見注釋3】。有些性能測試在網上有現成的測試結果,我就沒自己測,直接拿來用了,但是以後我會重新在我的機器上測一遍。


一些我們比較注意的點:

1.限制動態分配內存

相關的知識背景:glibc默認的malloc背後有複雜的演算法,當堆空間不足時會調用sbrk(),當分配內存很大時會調用mmap(),這些都是系統調用,似乎會比較慢,而且新分配的內存被first touch時也要過很久才能準備好。

可取的做法:盡量使用vector或者array(初始化時分配足夠的空間,之後每次使用都從裡面取出來用)。盡量使用內存池。如果需要二叉樹或者哈希表,盡量使用侵入式容器(boost::intrusive)。

性能測試:我測試的分配尺寸有64和8128兩種。首先,我測試了glibc malloc的性能,分配64位元組耗時98(sd247)ns,分配8128位元組需要耗時1485(sd471)ns。其次,我寫了一個多進程安全的內存池,分配64位元組需要29(sd15)ns,分配8128位元組需要22(sd12)ns。【內存池的細節見注釋6】。最後,我單獨測試了sbrk()和first touch的性能,但是數據不記得了。


2.使用輪詢,盡量避免阻塞

相關的知識背景:上下文切換是非常耗時的,其中固定的消耗包括(cpu流水線被衝掉、各種寄存器需要被保存和恢復、內核中的調度演算法要被執行),此外,緩存很有可能出現大量miss,這屬於不固定的時間消耗。

可取的做法:使用帶有內核bypass功能的網卡。每個進程或者線程都獨佔一個cpu核【isolcpus和irqbalance的細節見注釋3】,並且不停地輪詢,用以保證快速響應。盡量避免任何可能導致阻塞的事件(如mutex),某些註定很慢的活動(比如把log寫到磁碟上)應該被獨立出來放到別的cpu上,不能影響主線程。

性能測試:網上有一篇博客[tsunanet, 2010]測試了mode switch、thread switch、process switch的耗時,但是這篇文章太早了,以後我要用我的新cpu重新測一下。這篇博客裡面,系統調用只需要&<100ns,線程/進程切換需要&>1us(不包括緩存miss的時間)。


3.使用共享內存作為唯一的IPC機制

相關的知識背景:共享內存只有在初始化的時候有一些系統調用,之後就可以像訪問正常內存一樣使用了。其他IPC機制(管道、消息隊列、套接字)則是每次傳輸數據時都有系統調用,並且每次傳輸的數據都經歷多次拷貝。因此共享內存是最快的IPC機制。

可取的做法:使用共享內存作為唯一的IPC機制。當然,可能需要手動實現一些東西來保證共享的數據在多進程下是安全,我們是自己實現了無鎖內存池、無鎖隊列和順序鎖【關於seqlock的疑點見注釋1】。

性能測試:我使用了boost中的Interprocess庫和Lockfree庫,在共享內存上建立了一個spsc隊列,然後用這個隊列來傳送數據,代碼參考了stackoverflow上的一個答案[sehe, 2014]。我傳送的數據是一個8位元組整數,延時是153(sd61)ns。至於其他IPC機制,我在[cambridge, 2016]看到了一些性能測試結果,通常是要幾微秒到幾十微秒不等。

4.傳遞消息時使用無鎖隊列

相關的知識背景:我只關注基於數組的無鎖隊列,其中:spsc隊列是wait-free的,不論是入隊出隊都可以在確定的步數之內完成,而且實現時只需要基本的原子操作【為什麼這很重要見注釋7】;mpmc隊列的實現方式則多種多樣,但都會稍微慢一點,因為它們需要用一些比較重的原子操作(CAS或者FAA),而且有時它們需要等待一段不確定的時間直到另一個線程完成相應操作;另外,還有一種multi-observer的『廣播隊列』,多個讀者可以收到同一條消息廣播,這種隊列也有sp和mp類型的,可以檢查或者不檢查overwrite;最後,還有一種隊列允許存儲不定長的消息。

可取的做法:總的來說,應該避免使用mp類型的隊列,舉例:如果要用mpsc隊列,可以使用多個spsc來達成目的,並不需要mp隊列;同理,如果是消息廣播,也可以使用多個sp隊列來取代一個mp隊列;如果廣播時observer只想訂閱一部分消息,那麼可以用多個spsc+有計數功能的內存池【具體做法見注釋2】;如果要求多個觀察者看到多個生產者的消息,並且順序一致,那隻能用mp隊列了。總結一下,mp類型的隊列應該盡量避免,因為當多個生產者同時搶佔隊列的時候,延時會線性增長。

性能測試:我寫了一個mp類型的廣播隊列,傳輸的數據是8位元組int,當只有一個生產者時,傳輸的延時是105(sd26)ns。增加觀察者會使延時略微變大,增加生產者會使延時急劇變大(我用rdtsc指令控制不同生產者同時發送消息)。對於這個隊列來說,它的延時只略高於跨核可視延時【測試結果見注釋8】,所以應該算是不錯了。


5.考慮緩存對速度的影響

相關的背景知識:現在的機器內存是十分充足的,但是緩存還是很小,因此所有節省內存的技巧都還有用武之地。

可取的做法:盡量讓可能被同時使用的數據挨在一起;減少指針鏈接(比如用array取代vector,因為鏈接指向的地方可能不在緩存里);盡量節省內存(比如用unique_ptr&取代vector&,比如成員變數按照從大到小排序,比如能用int8的地方就不用int16);指定cpu affinity時考慮LLC緩存(同核的兩個超線程是共享L1,同cpu的兩個核是共享L3,不同NUMA核是通過QPI匯流排);會被多個核同時讀寫的數據按照緩存行對齊(避免false sharing)。


【注釋1】:有一篇惠普的論文[Hans-J.Boehm, 2012]大致敘述了順序鎖的實現方法,但是那裡面有兩點讓我感到困惑。一是需要用到thread_fence,這在某些cpu上可能會影響性能(x86似乎沒影響);二是被保護的內容也必須是原子變數(可以是多個原子變數,所以被保護的內容可以很長)。但這是我見過的唯一一個符合C++標準的SeqLock的實現。

【注釋2】:如果有M個生產者要發消息給N個觀察者,可以建M*N個spsc隊列和M個內存池,觀察者只能讀內存池裡的數據,只有對應的那一個生產者可以修改內存池。我感覺這樣應該會更快,但我沒測過。

【注釋3】:isolcpus可以隔離出一些cpu,避免其他線程被調度到這些cpu上執行。此外,設置irq affinity可以讓一些cpu盡量避免響應中斷,但在/proc/interrupts裡面仍然有一些項目是避免不了的,而cpu處理中斷時,用戶程序會有一段時間(有時高達幾十微秒)無法響應,我們沒法解決這個問題。

【注釋4】:在不同的時間點,ping的結果會有很大差異。交易時間段內ping出來的結果是30us,其它時間段ping出來的結果可能是幾百微秒。我不知道這是什麼原因,可能是期貨公司為了省電關掉了某些東西?

【注釋6】:我們要在共享內存上使用內存池,所以不得不自己寫一個。我寫的內存池只能分配固定尺寸的內存塊,但是用戶可以建立好幾個內存池,用來分配不同的尺寸。實現的過程中有兩個要點。一是用無鎖鏈表來保存空閑的內存塊;二是每個線程內部有一個緩衝區,所以真正取內存塊的時候是沒有CAS操作的。

【注釋7】:在Intel x86的cpu上,如果C++中的內存順序只用了acquire和release,那麼編譯出來的彙編代碼裡面不會有任何內存柵欄指令;如果同時也沒有RMW(讀-改-寫)指令的話,無鎖的代碼編譯出來就會像是普通的代碼一樣了。事實上,spsc隊列的延時幾乎等於跨核可視延時。

【注釋8】:跨核可視延時:對於一個共享變數來說,如果有一個核上面的進程或者線程修改了這個變數,另一個核需要過一段時間才能看到這個修改,這段時間被稱作跨核可視延時。我不確定在這段時間內,第二個核是會看到舊的數據還是這條指令會執行很久。在我的機器上,對於同一個cpu上的不同核心,這個值是96(sd14)ns。另外,對於同一個核心上的不同超線程,這個值應該會更小;對於同一台機器上的不同cpu,這個值應該會更大。


[cambridge, 2016]:Computer Laboratory

[Gabriele Paoloni, 2010]:Code Execution Times: IA-32/IA-64 Instruction Set Architecture

[Hans-J.Boehm, 2012]:http://www.hpl.hp.com/techreports/2012/HPL-2012-68.pdf

[sehe, 2014]:Shared-memory IPC synchronization (lock-free)

[tsunanet, 2010]:http://blog.tsunanet.net/2010/11/how-long-does-it-take-to-make-context.html


謝邀。只搞過 sell side,沒搞過 buy side,只能算「實時交易」,算不上「高頻交易」。工作以來一直在跟延遲做鬥爭,勉強可以說上幾句。

要控制和降低延遲,首先要能準確測量延遲,因此需要比較準的鍾,每個機房配幾個帶GPS和/或原子鐘primary standard的NTP伺服器是少不了的。而且就算用了NTP,同一機房兩台機器的時間也會有毫秒級的差異,計算延遲的時候,兩台機器的時間戳不能直接相減,因為不在同一時鐘域。解決辦法是設法補償這個時差。另外,不僅要測量平均延遲,更重要的是要測量並控制長尾延遲,即99百分位數或99.9百分位數的延遲,就算是sell side,系統偶爾慢一下被speculator利用了也是要虧錢的。

普通的C++服務程序,內部延遲(從進程收到消息到進程發出消息)做到幾百微秒(即亞毫秒級)是不需要特殊的努力的。沒什麼忌諱,該怎麼寫就怎麼寫,不犯低級錯誤就行。我很納悶國內流傳的寫 C++ 服務程序時的那些「講究」是怎麼來的(而且還不是 latency critical 的服務程序)。如果瓶頸在CPU,那麼最有效的優化方式是「強度消減」,即不在於怎麼做得快,而在於怎麼做得少。哪些可以不用做,哪些可以不提前做,哪些做一次就可以緩存起來用一陣子,這些都是值得考慮的。

網路延遲分傳輸延遲和慣性延遲,通常區域網內以後者為主,廣域網以前者為主。前者是傳送1位元組消息的基本延遲,大致跟距離成正比,千兆區域網單程是近百微秒,倫敦到紐約是幾十毫秒。這個延遲受物理定律限制,優化辦法是買更好的網路設備和租更短的線路(或者想辦法把光速調大,據說 Jeff Dean 干過)。慣性延遲跟消息大小成正比,跟網路帶寬成反比,千兆網TCP有效帶寬按115MB/s估算,那麼發送1150位元組的消息從第1個位元組離開本機網卡到第1150個位元組離開本機網卡至少需要 10us,這是無法降低的,因此必要的話可以減小消息長度。舉例來說,要發10k的消息,先花20us CPU時間,壓縮到3k,接收端再花10us解壓縮,一共「60us+傳輸延遲」,這比直接發送10k消息花「100us+傳輸延遲」要快一點點。(廣域網是否也適用這個辦法取決於帶寬和延遲的大小,不難估算的。)

延遲和吞吐量是矛盾的,通常吞吐量上去了延遲也會跟著飈上去,因此控制負載是控制延遲的重要手段。延遲跟吞吐量的關係通常是個U型曲線,吞吐量接近0的時候延遲反而比較高,因為系統比較「冷」;吞吐量上去一些,平均延遲會降到正常水平,這時系統是「溫」的;吞吐量再上去一些,延遲緩慢上升,系統是「熱」的;吞吐量過了某個臨界點,延遲開始飆升,系統是「燙」的,還可能「冒煙」。因此要做的是把吞吐量控制在「溫」和「熱」的範圍,不要「燙」,也不要太冷。系統啟動之後要「預熱」。

延遲和資源使用率是矛盾的,做高吞吐的服務程序,恨不得把CPU和IO都跑滿,資源都用完。而低延遲的服務程序的資源佔用率通常低得可憐,讓人認為閑著沒幹什麼事,可以再「加碼」,要抵住這種壓力。就算系統到了前面說的「發燙」的程度,其資源使用率也遠沒有到 100%。實際上平時資源使用率低是為了準備應付突發請求,請求或消息一來就可以立刻得到處理,盡量少排隊,「排隊」就意味著等待,等待就意味著長延遲。消除等待是最直接有效的降低延遲的辦法,靠的就是富裕的容量。有時候隊列的長度也可以作為系統的性能指標,而不僅僅是CPU使用率和網路帶寬使用率。另外,隊列也可能是隱式的,比如操作系統和網路設備的網路輸入輸出 buffer 也算是隊列。

延遲和可靠傳輸也是矛盾的,TCP做到可靠傳輸的辦法是超時重傳,一旦發生重傳,幾百毫秒的延遲就搭進去了,因此保持網路隨時暢通,避免擁塞也是控制延遲的必要手段。要注意不要讓batch job搶serving job的帶寬,比方說把伺服器上的日誌文件拷到備份存儲,這件事不要在繁忙交易時段做。QoS也是辦法;或者布兩套網,每台機器兩個網口,兩個IP。

最後,設法保證關鍵服務進程的資源充裕,避免侵佔(主要是CPU和網路帶寬)。比如把伺服器的日誌文件拷到別的機器會佔用網路帶寬,一個辦法是慢速拷貝,寫個程序,故意降低拷貝速度,每50毫秒拷貝50kB,這樣用時間換帶寬。還可以先壓縮再拷貝,比如gzip壓縮100MB的伺服器日誌文件需要1秒,在生產伺服器上會短期佔滿1個core的CPU資源,可能造成延遲波動。可以考慮寫個慢速壓縮的程序,每100毫秒壓縮100kB,花一分半鐘壓縮完100MB數據,分散了CPU資源使用,減少對延遲的影響。千萬不要為了加快壓縮速度,採用多線程並發的辦法,這就喧賓奪主了。


問題中限定語言是C++,可討論的範圍就比較精簡了。現有的答案都在談系統架構層次上的東西,略顯跑題。我對C++了解不多,但我嘗試以一名C++程序員的視角,從基本思路出發做一個分析,拋磚引玉。

首先我們要明確系統的需求。所謂交易系統,從一個應用程序的角度來說,有以下幾個特點:

  1. 一定是一個網路相關的應用,假如機器沒聯網,肯定什麼交易也幹不了。所以系統需要通過TCP/IP連接來收發數據。數據要分兩種,一種從交易所發過來的市場數據,流量很大,另一種是系統向交易所發出的交易指令,相比前者流量很小,這兩種數據需要在不同的TCP/IP連接里傳輸。
  2. 因為是自動化交易系統,人工干預的部分肯定比較小,所以圖形界面不是重點。而為了性能考慮,圖形界面需要和後台分開部署在不同的機器上,通過網路交互,以免任何圖形界面上的問題導致後台系統故障或者被搶佔資源。這樣又要在後台增加新的TCP/IP連接。
  3. 高頻交易系統對延遲異常敏感,目前(2014)市面上的主流系統(可以直接買到的大眾系統)延遲至少在100微秒級別,頂尖的系統(HFT專有)可以做到10微秒以下。其他答案里提到C++隨便寫寫延遲做到幾百微秒,是肯定不行的,這樣的性能對於高頻交易來說會是一場災難。
  4. 系統只需要專註於處理自己收到的數據,不需要和其他機器合作,不需要擔心流量過載。

有了以上幾點基本的認識,我們可以看看用C++做為開發語言有哪些需要注意的。

首先前兩點需求就決定了,這種系統一定是一個多線程程序。雖然對於圖形界面來說,後台系統相當於一個服務端,但這部分的性能不是重點,用常用的模式就能解決(也許這裡你可以介紹一下常用的C++ Client/Server庫,或者內嵌Web Server之類,相信應該有豐富的選擇,這裡不展開討論)。而重要的面向交易所那端,系統其實是一個客戶端程序,只需要維護好固定數量的連接就可以了。為延遲考慮,一定要選擇非同步I/O(阻塞的同步I/O會消耗時間在上下文切換),這裡有兩點需要注意:

  • 是否可以在單線程內完成所有處理?考慮市場數據的流量遠遠高於發出的交易指令,在單線程內處理顯然是不行的,否則可能收了一大堆數據還沒開始處理,錯過了髮指令的最佳時機。
  • 有答案提到要壓低平時的資源使用率,這是完全錯誤的設計思路。問題同樣出在上下文切換上,一旦系統進入IDLE狀態,再重新切換回處理模式是要付出時間代價的。正確的做法是在線程同步代碼中保持對共享變數/內存區的瘋狂輪詢,一旦有消息就立刻處理,之後繼續輪詢,這樣是最快的處理方式。(順帶一提現在的CPU一般會帶有環保功能,使用率低了會導致CPU進入低功耗模式,同樣對性能有嚴重影響。真正的低延遲系統一定是永遠發燙的!)

現在我們知道核心的模塊是一個多線程的,處理多個TCP/IP連接的模塊,接下來就可以針對C++進行討論。因為需要對接受到的每個TCP或UDP包進行處理,首先要考慮的是如何把包從接收線程傳遞給處理線程。我們知道C++是面向對象的語言,一般情況下最直觀的思路是創建一個對象,然後發給處理線程,這樣從邏輯上看是非常清晰的。但在追求低延遲的系統里不能這樣做,因為對象是分配在堆上的,而堆的內存結構對我們來說是完全不透明的,沒辦法控制一個對象會具體分到內存的什麼位置上,這直接導致的問題是本來連續收到的網路包,在內存里的分布是分散的,當處理線程需要讀取數據時就會發生大量的cache miss,產生不可控的延遲。所以對C++開發者來說,第一條需要謹記的應該是,不要隨便使用堆(用關鍵字new)。核心的數據要保證分配在連續內存里。

另一個問題在於,市場數據和交易指令都是結構化的,包含了股票名稱,價格,時間等一系列信息。如果使用C++ class來對數據進行建模和封裝,同樣會產生不可知的內存結構。為了嚴格控制內存結構,應該使用struct來封裝。一方面在對接收到的數據解析時可以直接定義名稱,一方面在分配新對象(比如交易指令)時可以保證所有數據都分配在連續的內存區域。

以上兩點是關於延遲方面最重要的注意事項(如果真正做好這兩點,大概剩下的唯一問題是給系統取個好名字吧:TwoHardThings)。除此之外,需要考慮的是業務邏輯的編寫。高頻交易系統里註定了業務邏輯不會太複雜,但重要的是要保證正確性和避免指針錯誤。正確性應該可以藉助於C++的特性比如強類型,模板等來加強驗證,這方面我不熟悉就不多說了。高頻系統往往運行時要處理大量訂單,所以一定要保證系統運行時不能崩潰,一旦coredump後果很嚴重。這個問題也許可以多做編譯期靜態分析來加強,或者需要在系統外增加安全機制,這裡不展開討論了。

以下是幾點引申思考:

  • 如何存儲系統日誌?
  • 如何對系統進行實時監控?
  • 如果系統coredump,事後如何分析找出問題所在?
  • 如何設計保證系統可用性,使得出現coredump之類的情況時可以及時切換到備用系統?

這些問題相信在C++框架內都有合適的解決方案,我對此了解不多,所以只列在這裡供大家討論。

註:

  • 從開發語言角度上說,C++只是一種選擇,並不是唯一的解決方案。簡單的認為低延遲就等同於用C++開發,是不正確的。其他語言同樣有可能做出高性能的設計,需要根據語言特性具體分析。
  • 關於整體的軟硬體架構,可以看我的另一個回答:高頻交易軟硬體是怎麼架構的?
  • 關於C++在性能方面的一些最新發展,包括內存結構的一些分析,可以參看:Modern C++: What You Need to Know

樓上的扯蛋的比較多
算來也在一家比較大的prop幹了不短時間了
最近都不說自己搞的是hft,因為很多人都聲稱自己搞hft,能得我們很無奈,所以我現在做的是ultra high frequency.
數據的話,t2o, tick to order, 700nanosecond
FPGA + share memory,很多std的東西都不能用,都是自己的lib, 都是單線程,沒有lock,最少IO, log很少, 用C++主要是寫起來簡單,但大部分情況下都是C sytle


先說,並不是c/c++的效率是決定因素。

最大的延時來自賬戶席位和網路延時,一席的賬戶成交優先順序高於二席,二席又高於散戶。怎樣做倒一席呢?只要賬戶上有足夠多的錢就可以。網路延時是最大的,因此在物理位置上離交易所核心機房越近越好,能直接放進去當然最好,如果不能,也要放到ping交易前置機在1ms以內的地方。證券公司會有資源,這要求動用你的一切力量爭取到最滿意的位置。早年間,這是場內交易和場外交易的區別。

接下來就是演算法的效率了,這個可以抽象出來跟語言沒關係,大多跟數學/統計模型有關係,然後是演算法的實現,c/c++/fortran/彙編的效率確實很好,而且優化的空間很大,但是如果很複雜的演算法matlab可能會優化得比自己寫得好,那就用matlab實現。

這還沒完,操作系統也可以調優,交易介面也可以不用交易所或者證券公司給的,自己分析通信協議重新實現;如果模型很複雜,計算量超大,那麼就用並行計算架構,MPI, CUDA什麼的用上。如果還要求絕對的速度,就用硬體實現演算法, 這時候就輪到DSP晶元, FPGA什麼的上陣,最後做一個專用的黑盒子。總之呢,就是所有能提高效率的地方,都是可以想辦法做的。

但是,其實你要考慮的首先是,你的速度要求有多高,或者問你的交易策略是否真的需要那麼高的速度嗎?其次是投入產出比,你的演算法是否真的能夠掙足夠的錢來支持你做各層面的優化。

以上很多雖然只有一句話,但是做起來東西很多,好多我現在也只知道概念,還不會做,提供個思路供參考。


----------+

忽然覺得題主可能理解錯題了,考官想問的或許是:「如果指定你用C/C++做HFT , 你將怎麼降低延時」,就是在語言和編程技巧這個層面上怎麼做優化,我們都被帶跑題了。我瞎猜。


kernel bypass,使用專用網卡搭配用戶態網路協議棧,比如 openonload。

Update:
其他的軟體層面的優化可以做的還包括 使用 realtime scheduler,cpu pinning,memory prefaulting 等等;部署上可以採用 colocation,把機器放在交易所的機房;硬體上,可以採用專用的網路設備,當然機器配置越高越好。程序本身的優化手段和其它場景並無太多差異。


回答問題的有多少是高頻交易員?目前國內最重要的是你能比別人先拿到同一時間戳的Tick數據。搞不定前面毫秒級差異,再是挖空心思優化系統及程序那十幾微秒,都是徒勞。


最後你會發現,摩根斯坦利等公司覺得光靠C++還不夠快,愣是把自己的機房蓋在了交易所隔壁,用最後的網線長度作鬥爭……


從C++角度提幾點:避免用不必要的virutal function,因為這樣compiler無法做足夠的優化,在compile的時候不知道哪個函數會被用。用memory pool,用lock free data structure (Intel TBB),避免kernel call. 善用cache。少用std::map, std::unordered_map,用std::array, std::vector.用pool allocator. 別用std::string.

Colo大家都在用,沒有什麼特別的優勢。FPGA是肯定有用的,那些在幾個us水平的應該都是用了FPGA的。

Java美國有一家頂級的quant fund做HFT在用。我知道有些地方用Java寫低延時的系統是要改寫JVM的,不然一個GC延遲就上去了。


早上看到,隨便講一點和CPP本身有關的特性。編譯期決定,比如meta programming. 非鎖同步. cache friendly optimization等等。速度實際上還要調。而不經過內核TCP棧的也是必須的。同機內通信用shared memory,集群內用組播


從戰略上來說,避免任何可能的阻塞,以可預測的方式IO,包括但不限於:網路IO,磁碟,內存,甚至CPU的各級緩存。具體展開來講:

網路IO:
使用非面向連接的協議,因為面向連接的協議有窗口,有可能引起阻塞。
磁碟:
使用Write Ahead Log(WAL),避免使用資料庫。
內存:
1. 避免動態分配內存。(一次隨機內存訪問花費的時間和順序訪問對於CPU來說花費的時間相差兩個數量級)
2. 零內存拷貝,不一定能做到,總之是拷貝的次數越少越好。
3. 數據無需編碼解碼,可以直接順序從內存讀取,直接可以在網路上收發。
CPU:
避免false sharing,cache miss等。

再具體到C++:
使用Pod,使用placement new,最好連Pod都不使用,直接讀寫ByteArray。

再具體到代碼:
推薦Martin Thompson的兩個庫:Disruptor SBE,這兩個應該都有c++版本。


FPGA是給手殘的人用的,嘿嘿。因為很多人用不好強大複雜的硬體和OS,所以只好去用FPGA了。就如同不會開航母的人只好去開小舢板。
首先要夠快拿到數據,你接的switch是不是ECN那邊最快的switch。
其次你能多快的速度在開盤的瞬間處理100w條quote?FPGA,算了吧。想並發的同學可以試試GPU,比FPGA還犀利。一旦並發,你就已經輸了。
第三你要意識到unpredictable delay並不是你的敵人,而是你的朋友。


怎麼沒人提OS/kernel的問題…如果OS沒有hack真是難以想像的。
因為單靠主板上的晶振,OS其實自身提供在ms級別準度的測量都算不錯了(你調用OS得到的時間戳其實非常假,精度並不高[1];而新的CPU因為多核和變頻的緣故,從彙編里hack得到的時間精度也有問題[2])。事實上這樣一點也不準,實際情況是:非RT的OS也許發出去到回報收到會非常快,但是這時候CPU的中斷還沒到,讓程序傻等著楞半天[3]。
所以使用hard RTOS是必須的。一是靠CPU自身晶振和寄存器的hack可以達到,這是普遍hard realtime linux kernel的工作方式。這樣的linux都得打個realtime kernel的patch。
另一種是直接外接高頻晶振,這個就非常昂貴了。
其實這兩個思路並不稀奇,有點像做航天控制系統。

因為hack之後變成了hard realtime system,所以整個C++程序的最大延遲被要求在規定的CPU中斷周期內結束,超過這個都會計入嚴重後果事件甚至引發系統崩潰。這個值往往就是整個系統運行的最大latency。


[1]註:指在CMOS中的RTC,最大f一般是8192Hz;而PIT是8254時鐘實現,最高才約4.19Mhz
[2]註:一般指從TSC寄存器benchmarking的方法。
[3]註:普通linux kernel系統時鐘為10ms(10ms啊,10ms啊,10ms啊=w=)


曾經寫過後端,略知一點點。不要用QuickFix 了,自己琢磨好好寫 fix engine 和 OMS 吧。然後花錢燒硬體吧,把CBOT 樓下的包子鋪給盤下來。


1. 成熟的消息驅動框架
2. Zero copy/kernel bypass
3. CPU isolation
4. 精簡業務層邏輯

有些交易所還嫌這不夠快,在自己的matching engine邊上擺起了機架租給這些做高頻交易的...


- 共享內存

- 無鎖隊列

- 避免動態分布內存

- 避免false sharing

- 少log

- onload

- 核隔離

- interrupt綁定

- 各種book management的trick

- 全系統無鎖


有些回答的確很扯淡。

線程切換,信號量混洗的時間,都是毫秒級別的,如果真的要求微秒級的反應速度,就必須保證,交易期間不能發生任何線程切換,所以各類鎖之類的都不能用。

有人居然說到多線程,非同步io,那肯定是不可能達到要求的。


Java很容易做到20 micro一下,getco,virtu,nasdaq都是用的一個java source code...

說java有gc就慢的屬於java還沒入門的,說改jvm的屬於聽說過java的。。。


能在編譯期搞定的東西都在編譯期搞定,寧可多耗費編譯時間。

少用鎖。
減少上下文切換開銷,包括函數棧,核間,協議層之間等等。

善用工具,找到瓶頸。


系統的擴展性和高速是一對矛盾,沒法兼顧。純軟體方案,很多HTF是用C和彙編干,不寫日誌,單線程,網路全部muticast(多購買成熟成品,比如29west),樓上有提到tcp的有點扯淡,實際不可能。 也有用c++搞,這個要求有點高,不相信的話可以打開boost源代碼的任意一個文件,如果可以流暢看懂那麼基本達標,系統幾乎沒有任何繼承,大部分工作都在編譯時刻完成,非常重的模板嵌套,大量使用mpl,tbb,各種lockfree演算法


推薦閱讀:

TAG:CC | 交易系統 | 高頻交易 |