นอกจากคำตอบของการปรับแต่งฮาร์ดแวร์ / การตั้งค่าที่ยอดเยี่ยมจาก @jimwise "linux latency ต่ำ" กำลังแสดงถึง:
- C ++ สำหรับเหตุผลของการกำหนด (ไม่ล่าช้าอย่างน่าประหลาดใจในขณะที่ GC เตะเข้า), เข้าถึงสิ่งอำนวยความสะดวกระดับต่ำ (I / O, สัญญาณ), พลังภาษา (ใช้ TMP และ STL อย่างเต็มที่, ความปลอดภัยประเภท)
- ชอบความเร็วหน่วยความจำเกิน:> 512 Gb of RAM เป็นเรื่องปกติ ฐานข้อมูลอยู่ในหน่วยความจำแคชล่วงหน้าหรือผลิตภัณฑ์ NoSQL แปลกใหม่
- ตัวเลือกอัลกอริทึม: เร็วที่สุดเท่าที่จะทำได้เมื่อเทียบกับสติ / เข้าใจได้ / ขยายได้เช่นล็อคฟรีอาร์เรย์หลายบิตแทนที่จะเป็นอาร์เรย์ของวัตถุกับคุณสมบัติบูล
- ใช้สิ่งอำนวยความสะดวก OS เต็มรูปแบบเช่นหน่วยความจำที่ใช้ร่วมกันระหว่างกระบวนการบนแกนที่ต่างกัน
- ปลอดภัย ซอฟต์แวร์ HFT มักจะอยู่ในตลาดหลักทรัพย์เพื่อให้มัลแวร์ไม่สามารถยอมรับได้
เทคนิคเหล่านี้ส่วนมากมีการทับซ้อนกับการพัฒนาเกมซึ่งเป็นเหตุผลหนึ่งที่อุตสาหกรรมซอฟต์แวร์ทางการเงินดูดซับโปรแกรมเมอร์เกมที่เพิ่งซ้ำซ้อน (อย่างน้อยก็จนกว่าพวกเขาจะจ่ายค่าเช่าค้าง)
ความต้องการพื้นฐานคือการสามารถรับฟังข้อมูลตลาดแบนด์วิธสูงเช่นความปลอดภัย (หุ้นสินค้าโภคภัณฑ์ fx) ราคาจากนั้นทำการตัดสินใจซื้อ / ขาย / ไม่ทำอะไรเลยอย่างรวดเร็วตามความปลอดภัยราคา และการถือครองปัจจุบัน
แน่นอนว่าสิ่งนี้อาจผิดพลาดได้เช่นกัน
ดังนั้นฉันจะอธิบายอย่างละเอียดเกี่ยวกับจุดบิตอาร์เรย์ สมมติว่าเรามีระบบการซื้อขายความถี่สูงที่ดำเนินการกับรายการสั่งซื้อที่ยาวนาน (ซื้อ 5k IBM, ขาย 10k DELL และอื่น ๆ ) สมมติว่าเราจำเป็นต้องตรวจสอบอย่างรวดเร็วว่าคำสั่งซื้อทั้งหมดได้รับการเติมเพื่อให้เราสามารถย้ายไปยังงานต่อไป ในการเขียนโปรแกรม OO แบบดั้งเดิมสิ่งนี้จะมีลักษณะดังนี้:
class Order {
bool _isFilled;
...
public:
inline bool isFilled() const { return _isFilled; }
};
std::vector<Order> orders;
bool needToFillMore = std::any_of(orders.begin(), orders.end(),
[](const Order & o) { return !o.isFilled(); } );
ความซับซ้อนของอัลกอริทึมของรหัสนี้จะเป็น O (N) เนื่องจากเป็นการสแกนเชิงเส้น ลองดูที่โปรไฟล์ประสิทธิภาพในแง่ของการเข้าถึงหน่วยความจำ: การวนซ้ำของลูปภายใน std :: any_of () จะเรียก o.isFilled () ซึ่ง inlined ดังนั้นจึงกลายเป็นการเข้าถึงหน่วยความจำของ _isFilled, 1 ไบต์ (หรือ 4 ขึ้นอยู่กับสถาปัตยกรรมการตั้งค่าคอมไพเลอร์และคอมไพเลอร์ของคุณ) ในวัตถุที่รวม 128 ไบต์ ดังนั้นเราจึงเข้าถึง 1 ไบต์ในทุก ๆ 128 ไบต์ เมื่อเราอ่าน 1 ไบต์สันนิษฐานว่าเป็นกรณีที่เลวร้ายที่สุดเราจะได้รับแคชข้อมูลของ CPU นี่จะทำให้คำร้องขอการอ่านเป็น RAM ซึ่งอ่านทั้งบรรทัดจาก RAM ( ดูที่นี่สำหรับข้อมูลเพิ่มเติม ) เพื่ออ่าน 8 บิต ดังนั้นโปรไฟล์การเข้าถึงหน่วยความจำจึงเป็นสัดส่วนกับ N
เปรียบเทียบกับ:
const size_t ELEMS = MAX_ORDERS / sizeof (int);
unsigned int ordersFilled[ELEMS];
bool needToFillMore = std::any_of(ordersFilled, &ordersFilled[ELEMS+1],
[](int packedFilledOrders) { return !(packedOrders == 0xFFFFFFFF); }
โปรไฟล์การเข้าถึงหน่วยความจำของกรณีนี้สมมติว่าเลวร้ายที่สุดอีกครั้งคือ ELEMS หารด้วยความกว้างของสาย RAM (แตกต่างกัน - อาจเป็นแบบดูอัลแชนแนลหรือทริปเปิลแชนเนล ฯลฯ )
ดังนั้นด้วยเหตุนี้เราจึงเพิ่มประสิทธิภาพอัลกอริธึมสำหรับรูปแบบการเข้าถึงหน่วยความจำ RAM จำนวนใดจะช่วยได้ - เป็นขนาดแคชข้อมูลของ CPU ที่ทำให้เกิดความต้องการนี้
สิ่งนี้ช่วยได้ไหม?
มี CPPC ที่ยอดเยี่ยมพูดคุยเกี่ยวกับการเขียนโปรแกรมความหน่วงต่ำ (สำหรับ HFT) บน YouTube: https://www.youtube.com/watch?v=NH1Tta7purM