مرجع: Developing High-Frequency Trading Systems

مقدمه

برای آنکه یک استراتژی معاملاتی کم‌تاخیر (Low-Latency) به درستی عمل کند، درک نحوه تعامل نرم‌افزار با سیستم‌عامل و سخت‌افزار کامپیوتر ضروری است. عملکرد مناسب تنها زمانی حاصل می‌شود که تمام این لایه‌ها در هماهنگی کامل باشند. در فصل‌های آتی، با اعمال تکنیک‌های بهینه‌سازی پیشرفته، تاخیر زمانی سیستم را به کمتر از ۱۰ میکروثانیه خواهیم رساند.

اگر خواهان مطالعه فوق‌تخصصی درباره معماری کامپیوتر هستید، کتاب کلاسیک Computer Architecture: A Quantitative Approach نوشته جان هنسی و دیوید پترسون مرجع بی‌نظیری است. در این فصل، ما منحصراً بر بخش‌هایی از سخت‌افزار و سیستم‌عامل تمرکز می‌کنیم که یک سیستم HFT برای حیات و برتری به آن‌ها نیازمند است:

  • شناخت سخت‌افزار رایانه‌های HFT
  • معماری پردازنده‌ها، هسته‌ها و حافظه‌های اشتراکی NUMA
  • سلسله‌مراتب کش‌های L1، L2 و L3
  • تعامل با سیستم‌عامل، فضای کاربر در برابر کرنل و سوئیچ کانتکست
  • مدیریت حافظه، بافر TLB و صفحات غول‌پیکر (Huge Pages)
  • نقش کامپایلرها، بهینه‌سازی حلقه‌ها و پیوند ایستا (Static Linking)

۱. شناخت رایانه‌های HFT (Understanding HFT Computers)

شاید تصور کنید برای راه‌اندازی معاملات فرکانس بالا حتماً به کامپیوترهای سفارشی عجیب‌وغریب نیاز دارید؛ اما واقعیت این است که در اکثر موارد از سخت‌افزارهای تجاری استاندارد بازار (Off-the-shelf Hardware) استفاده می‌شود. آنچه تمایز ایجاد می‌کند، نحوه پیکربندی و درک عمیق از معماری این سخت‌افزارهاست.

تصویر زیر نگاه یک مهندس HFT به ساختار یک واحد پردازش مرکزی (CPU) را نشان می‌دهد:

شکل ۴.۱: ساختار پایه‌ای پردازنده (CPU)

مسیر جریان داده در رایانه HFT:

هدف اصلی سرور در یک سیستم HFT، انجام وظایف پایه‌ای معاملاتی است: دریافت داده‌های بازار، اجرای مدل‌های الگوریتمی، و ارسال سفارش به صرافی.

  1. بسته‌های داده (Packets) از طریق کابل فیزیکی وارد کارت شبکه (NIC) می‌شوند.
  2. کارت شبکه بسته‌ها را درون حافظه رم (Host Memory) قرار می‌دهد.
  3. پردازنده مرکزی (CPU) تکه‌های داده را از رم به درون حافظه‌های نهان (Caches) می‌کشد تا هسته‌های پردازشی بتوانند محتوای بسته را رمزگشایی کرده و فوراً تصمیم‌گیری کنند.
    برای دستیابی به تاخیر کم، باید مسیر حرکت این بایت‌ها را در لایه‌های سخت‌افزار به دقت مهندسی کرد.

۲. تکامل پردازنده‌ها: از چندپردازنده‌ای تا چندهسته‌ای

یک پردازنده (CPU) مجموعه‌ای از یک یا چند هسته پردازشی است که دستورالعمل‌های برنامه را واکشی (Fetch) و اجرا (Execute) می‌کنند. در گذشته، برای پردازش چندگانه باید چندین چیپ فیزیکی مجزا روی مادربورد نصب می‌شد. با رسیدن قانون مور (Moore’s Law) به محدودیت‌های فیزیکی مصرف توان و حرارت (Dennard Scaling)، سازندگان پردازنده‌ها به جای افزایش سرسام‌آور فرکانس، چندین هسته را روی یک دای سیلیکونی واحد (Multi-core on a single die) و امروزه درون چیپلت‌ها (Chiplets) قرار دادند.

هسته پردازنده در انجام عملیات منطقی و محاسباتی ریز فوق‌العاده سریع است؛ از جمع و تفریق ساده گرفته تا دستورات شتاب‌یافته سخت‌افزاری اختصاصی نظیر CRC32، مراحل رمزنگاری AES و ضرب‌های بدون انتقال مانند PCLMUQDQ.

چالش تک‌هسته‌ای در برابر چندهسته‌ای در HFT:

هنگامی که دیتای بازار می‌رسد، داده باید پارس شود، به دفترچه سفارشات ارسال شود و به استراتژی برسد. اگر سیستم تنها با یک هسته کار کند، تمام این اعمال باید به صورت متوالی (Sequential) انجام شوند؛ یعنی یک بسته باید کاملاً تمام شود تا بسته بعدی پردازش گردد. این امر باعث ایجاد صف طویل بسته‌ها و افزایش تاخیر می‌شود. برای حل این مشکل، نیازمند واحدهای پردازشی متعددی هستیم که بتوانند به صورت موازی داده‌ها را دست‌به‌دست کنند.


معماری حافظه غیریکنواخت: NUMA (Non-Uniform Memory Access)

در سرورهای مدرن، چند سوکت پردازنده روی مادربورد قرار دارد. به معماری سرورهای چندسوکت امروزی NUMA می‌گویند:

شکل ۴.۲: معماری چهارگانه NUMA به عنوان یک گراف کامل پیوسته

مفاهیم کلیدی NUMA:

  • حافظه محلی (Local Resource): هر سوکت پردازنده، مستقیماً به بانک حافظه RAM و اسلات‌های PCIe اختصاصی خود متصل است. دسترسی پردازنده به رم محلی خود در بالاترین سرعت انجام می‌شود.
  • حافظه از راه دور (Remote Resource): اگر یک پردازنده بخواهد به داده‌های موجود در رم متصل به یک پردازنده دیگر دسترسی یابد، باید از گذرگاه‌های پرسرعت بین‌پردازنده‌ای (نظیر Intel UPI یا AMD Infinity Fabric) عبور کند. این دسترسی به شدت کندتر از دسترسی محلی است (دسترسی غیریکنواخت).
  • سیستم‌های ccNUMA: اصطلاح Cache-Coherent NUMA تضمین می‌کند که داده‌های حافظه همواره بین هسته‌های مختلف یکپارچه و معتبر بمانند.

دام بزرگ هایپرتردینگ (Hyper-Threading / SMT)

فناوری Hyper-Threading باعث می‌شود هر هسته فیزیکی مانند دو هسته مجازی به نظر برسد. هنگامی که یک نخ منتظر رویدادی کند (مثل واکشی از RAM) بماند، هسته به اجرای نخ دوم سوئیچ می‌کند. با اینکه این کار ظرفیت کلی سرورهای عادی را بالا می‌برد، اما در HFT یک تاخیر غیرقابل‌پیش‌بینی شبیه کانتکست سوئیچ سخت‌افزاری ایجاد می‌کند؛ به همین دلیل در سیستم‌های جدی معاملات فرکانس بالا، Hyper-Threading در تنظیمات BIOS غیرفعال (Disable) می‌شود تا هر هسته ۱۰۰٪ ظرفیت خود را منحصراً به نخ حیاتی معاملاتی اختصاص دهد.


۳. سلسله‌مراتب حافظه و کش‌های پردازنده (Memory & Caches)

سرعت پردازنده در مقیاس گیگاهرتز عمل می‌کند (هر چرخه کلاک کمتر از ۰.۳ نانوثانیه)، در حالی که دسترسی به حافظه اصلی RAM صدها چرخه کلاک زمان می‌برد؛ شکافی که به دیوار حافظه (Memory Wall) معروف است. پردازنده‌ها برای حل این شکاف از سلسله‌مراتب حافظه نهان (کش) استفاده می‌کنند.

خط کش (Cache Line):

پردازنده هرگز تک‌بایت را از حافظه بارگذاری نمی‌کند، بلکه داده‌ها را در بلوک‌های متوالی ۶۴ بایتی به نام Cache Line به درون کش منتقل می‌کند:

شکل ۴.۳: ساختار خط کش و بارگذاری بلوک‌های حافظه

شکل ۴.۴: پایپ‌لاین اجرایی پردازنده و تعامل با حافظه

سطوح سه‌گانه حافظه نهان:

  1. کش سطح یک (L1 Cache):
    • سریع‌ترین حافظه سیستم که چسبیده به واحدهای اجرایی هسته است.
    • به دو بخش مجزا تقسیم می‌شود: L1 Instruction (کش دستورالعمل‌ها) و L1 Data (کش داده‌ها).
    • سرعت آن حدود ۱۰۰ برابر سریع‌تر از حافظه رم اصلی است.
  2. کش سطح دو (L2 Cache):
    • بزرگ‌تر از L1 (بین ۲۵۶ کیلوبایت تا چند مگابایت)، اما اندکی کندتر از آن.
    • اختصاصی برای همان هسته فیزیکی بوده و حدود ۲۵ برابر سریع‌تر از RAM است.
  3. کش سطح سه (L3 Cache):
    • بزرگ‌ترین و کندترین لایه کش پردازنده که میان تمام هسته‌های پردازنده به اشتراک گذاشته می‌شود.
    • به عنوان یک Victim Cache عمل می‌کند؛ خطوط داده‌ای که از کش‌های L1 و L2 بیرون رانده می‌شوند، پیش از ارسال به RAM اصلی ابتدا در L3 ذخیره می‌گردند.

مدل‌های حافظه اشتراکی: UMA در برابر NUMA

  • در مدل‌های قدیمی UMA (Uniform Memory Access)، تمام پردازنده‌ها از یک کنترلر حافظه و گذرگاه مشترک استفاده می‌کردند که با افزایش هسته‌ها، گذرگاه به شدت مسدود شده و گلوگاه عملکردی می‌شد.
  • در مدل NUMA، هر پردازنده کنترلر حافظه خود را دارد و مقیاس‌پذیری بسیار بهتر است؛ اما چالش پروتکل‌های انسجام کش (Cache Coherency) به وجود می‌آید. برنامه باید طوری نوشته شود که تبادل داده بین سوکت‌های مختلف به حداقل برسد.

دستگاه‌های ورودی/خروجی و موقعیت کارت شبکه (PCIe)

دستگاه‌های I/O مانند کارت شبکه از طریق گذرگاه PCI Express (PCIe) مستقیماً به یک پردازنده خاص در معماری NUMA وصل می‌شوند:

اصل طلایی پین کردن کارت شبکه در HFT

هنگام توسعه درگاه دریافت داده بازار (Market Data Gateway)، فرآیند نرم‌افزاری شما باید دقیقاً روی همان هسته‌های پردازنده‌ای اجرا شود که کارت شبکه (NIC) فیزیکاً به اسلات PCIe آن سوکت متصل است؛ در غیر این صورت پکت‌های داده برای رسیدن به نرم‌افزار مجبورند از گذرگاه بین‌سوکت‌ها عبور کنند و تاخیرهای مرگبار میکروثانیه‌ای به سیستم تحمیل خواهد شد!

همچنین دیسک‌های سخت به دلیل کندی مطلق، در مسیر پردازش زنده بازار هرگز خوانده یا نوشته نمی‌شوند و تنها برای بک‌تست‌های تاریخی کاربرد دارند.


۴. بهره‌گیری از سیستم‌عامل برای سیستم‌های HFT

سیستم‌عامل (OS) به عنوان لایه انتزاعی میان نرم‌افزار و سخت‌افزار عمل می‌کند. در HFT رویکرد مهندسی بر این است که این لایه‌های انتزاعی را تا جای ممکن بشکنیم و مستقیماً با سخت‌افزار گفتگو کنیم.

وظایف اصلی سیستم‌عامل شامل: انتزاع منابع، زمان‌بندی فرآیندها، مدیریت حافظه، مدیریت فایل‌ها و دستگاه‌ها، و مدیریت وقفه (Interrupts).

فضای کاربر (User Space) در برابر فضای هسته (Kernel Space)

  • کرنل (Kernel): هسته دارای بالاترین سطح دسترسی سیستمی است که درایورها، پشته شبکه و دسترسی مستقیم به حافظه فیزیکی را در دست دارد.
  • فضای کاربر (User Space): برنامه‌های ما (سیستم معاملاتی) در فضای کاربر اجرا می‌شوند. هرگونه درخواست ارتباط با سخت‌افزار معمولاً نیازمند عبور از کرنل و یک تعویض زمینه (Context Switch) پرهزینه است که چرخه‌های گرانبهای CPU را تلف می‌کند.

زمان‌بندی فرآیندها (Process Scheduling)

در سیستم‌عامل‌های استاندارد مانند لینوکس، زمان‌بند (Scheduler) زمان پردازنده را با الگوریتم‌هایی نظیر CFS میان صدها فرآیند به صورت عادلانه در قالب برش‌های زمانی (Timeslices) تقسیم می‌کند.

  • در سیستم‌های بلادرنگ و HFT، از سیاست‌های زمان‌بندی با اولویت بالا نظیر SCHED_FIFO و SCHED_RR استفاده می‌شود تا تسک‌های معاملاتی بدون نوبت اجرا شوند (با مراقبت کامل برای جلوگیری از پدیده قفل یا معکوس شدن اولویت - Priority Inversion).

مدیریت حافظه، صفحات و بافر TLB

سیستم‌عامل حافظه فیزیکی را نمی‌شناسد، بلکه آن را به بلوک‌هایی استاندارد به نام صفحه (Page) با اندازه پیش‌فرض ۴ کیلوبایت تقسیم می‌کند:

شکل ۴.۵: نحوه نگاشت صفحات حافظه فیزیکی به فضای آدرس مجازی فرآیندها

  • واحد مدیریت حافظه (MMU): قطعه‌ای سخت‌افزاری در پردازنده که آدرس‌های مجازی برنامه را به آدرس‌های فیزیکی RAM ترجمه می‌کند.
  • بافر ترجمه آدرس (TLB - Translation Lookaside Buffer): کش فوق‌العاده سریع سخت‌افزاری در پردازنده که آخرین نگاشت‌های آدرس را نگهداری می‌کند.
  • اگر آدرسی در TLB پیدا نشود (TLB Cache Miss)، پردازنده باید به سراغ جدول صفحات در حافظه برود که تاخیر زیادی ایجاد می‌کند.

راهکار صفحات غول‌پیکر (Huge Pages):

در سیستم‌های HFT برای داده‌های بسیار حجیم (مانند دفترچه سفارشات)، از Huge Pages (صفحات ۲ مگابایتی یا ۱ گیگابایتی) به جای صفحات ۴ کیلوبایتی استفاده می‌شود. با بزرگ شدن صفحات، تعداد رکوردهای جدول صفحه کاهش یافته و تقریباً تمام آدرس‌ها درون TLB جا می‌گیرند و TLB Miss حذف می‌شود.


فراخوان‌های سیستمی (System Calls) و تکنولوژی مدرن vDSO

هر زمان برنامه به کرنل نیاز داشته باشد یک System Call صادر می‌کند. رفتن به کرنل و بازگشت به فضای کاربر زمان‌بر است.

  • برای خواندن فایل یا شبکه، سفر کامل به کرنل الزامی است.
  • تکنولوژی انقلابی vDSO (Virtual Dynamic Shared Object) در لینوکس: لینوکس برخی از پرکاربردترین عملکردهای کرنل (به‌ویژه دریافت ساعت دقیق سیستم با clock_gettime(CLOCK_MONOTONIC)) را مستقیماً به فضای کاربر مپ می‌کند! بدین ترتیب برنامه بدون نیاز به سوئیچ کانتکست و بدون رفتن به حالت کرنل، زمان دقیق را در حد چند نانوثانیه می‌خواند.

مدیریت وقفه (Interrupts) و پین کردن هسته‌ها (Core Pinning)

هنگام وقوع یک رویداد سخت‌افزاری (مثلاً رسیدن بسته به کارت شبکه)، یک وقفه (Interrupt) صادر می‌شود که اجرای برنامه را موقتاً متوقف می‌کند:

شکل ۴.۶: اثر وقفه سخت‌افزاری و کانتکست سوئیچ بر اتلاف زمان اجرای تسک

در مدل تک‌هسته‌ای (شکل ۴.۶)، پردازنده مداوم میان اجرای کد برنامه و مدیریت وقفه کرنل جابه‌جا می‌شود و زمان زیادی می‌سوزد.

راهکار طلایی: تفکیک هسته‌ها با دو پردازنده و پین کردن فرآیند:

شکل ۴.۷: مزیت استفاده از دو هسته مجزا و حذف کانتکست سوئیچ با CPU Pinning

همان‌طور که در شکل ۴.۷ مشاهده می‌شود:

  • با استفاده از قابلیت CPU Pinning (CPU Affinity)، فرآیند استراتژی به هسته دوم مقید (Pin) می‌شود.
  • کرنل طوری پیکربندی می‌شود که کلیه وقایع و وقفه‌های سخت‌افزاری توسط هسته اول مدیریت شوند.
  • در نتیجه، هسته دوم بدون کوچک‌ترین وقفه، کانتکست سوئیچ یا تداخلی از سوی کرنل، با سرعت ۱۰۰٪ خالص به اجرای کد استراتژی می‌پردازد.

۵. نقش کامپایلرها در معاملات فرکانس بالا (The Role of Compilers)

کامپایلرها کدهای سطح بالا (مانند ++C) را به دستورالعمل‌های اسمبلی و باینری ماشین تبدیل می‌کنند. در HFT، بیشترین زمان برنامه در حلقه‌ها (Loops) صرف می‌شود. کامپایلر با تکنیک‌های زیر زمان اجرا را به حداقل می‌رساند:

  1. باز کردن حلقه (Loop Unrolling):
    حذف گام‌های بررسی شرط پایان حلقه و پرش‌های شرطی؛ کاهش شدید سربار Branch Predictor پردازنده.
  2. درون‌خطی کردن توابع (Function Inlining):
    جایگزینی متن تابع در محل فراخوانی و حذف کامل سربار call و ret در سطح پشته اسمبلی.
  3. جداول محاسبات از پیش انجام‌شده (Pre-computed Lookup Tables):
    تبدیل محاسبات تکراری به آرایه‌های آماده در حافظه برای جلوگیری از پردازش مجدد.

فرمت فایل‌های اجرایی و تفاوت پیوند ایستا و پویا

در لینوکس فایل‌های اجرایی با فرمت ELF و در ویندوز با فرمت PE کامپایل می‌شوند. دو شیوه برای اتصال کتابخانه‌ها وجود دارد:

ویژگیپیوند پویا (Dynamic Linking)پیوند ایستا (Static Linking)
اندازه فایل باینریکوچک‌تر (فقط ارجاعات را نگه می‌دارد)بزرگ‌تر (تمام کد کتابخانه‌ها در باینری جا می‌گیرد)
اشتراک حافظهکد کتابخانه بین چند برنامه در رم مشترک استهر برنامه نسخه اختصاصی خود را در رم دارد
تاخیر در فراخوانی توابعکندتر؛ از طریق جدول ارجاع غیرمستقیم PLT فراخوانی می‌شودفوق‌العاده سریع؛ توابع مستقیماً فراخوانی می‌شوند
بهینه‌سازی کامپایلرامکان بهینه‌سازی بین کتابخانه‌ای وجود نداردکامپایلر با دید کلان می‌تواند LTO (Link-Time Optimization) انجام دهد
کاربرد در HFTتوصیه نمی‌شوداستاندارد مطلق سیستم‌های کم‌تاخیر HFT

خلاصه و جمع‌بندی فصل چهارم (Summary)

در این فصل، مدل ذهنی کاملی از فیزیک سخت‌افزار، معماری پردازنده و هسته سیستم‌عامل به دست آوردیم:

  1. شناخت سلسله‌مراتب کش‌های L1/L2/L3 و پرهیز از Cache Miss و خطوط ۶۴ بایتی کش.
  2. معماری NUMA و ضرورت قرار گرفتن کارت شبکه در کنار پردازنده متناظر محلی.
  3. خاموش کردن Hyper-Threading و پین کردن فرآیندهای حساس به هسته‌های ایزوله‌شده (CPU Pinning).
  4. استفاده از Huge Pages برای کاهش TLB Miss و بهره‌گیری از vDSO لینوکس برای توابع زمانی.
  5. پیوند ایستا (Static Linking) و بهینه‌سازی‌های کامپایلر.

در فصل بعدی (05 - شبکه در حرکت و پروتکل‌های تبادل)، نحوه حرکت بسته‌های شبکه با سرعت نور در سوئیچ‌ها و جزئیات پروتکل FIX و باینری را کالبدشکافی خواهیم کرد.