soft real-time ב-Windows רגיל: מדריך מעשי

· עודכן בתאריך: · · Windows, soft real-time, latency, מדידה

היסטוריית עדכונים (גרסה ראשונה, פורסמה בתאריך 9 Mar 2026)
פרסום ראשון
לצטט את המאמר הזה(DOI: 10.5281/zenodo.22173307)

מאמר זה מאוחסן בארכיון Zenodo. להלן גם ה-DOI שתמיד מפנה לגרסה האחרונה וגם ה-DOI המקובע לגרסה שאתם קוראים.

Go Komura (2026). soft real-time ב-Windows רגיל: מדריך מעשי. KomuraSoft LLC. https://doi.org/10.5281/zenodo.22173307 https://comcomponent.com/he/blog/windows-soft-realtime-practical-guide-natural/

DOI (הגרסה האחרונה)
10.5281/zenodo.22173307
DOI (הגרסה הזו)
10.5281/zenodo.22173308

כשבונים ב-Windows עיבוד ש"אי אפשר שיאחר" —‏ periodic processing, אודיו, וידאו, מדידה, בקרת ציוד — הרבה פעמים שומעים ש"ב-Windows זה לא יעבוד". התחושה הזו נכונה חלקית. Windows אינו hard real-time OS, אבל אם מתכננים, מממשים, מודדים ומפעילים כמו שצריך, אפשר להגיע למצב די מעשי כ-soft real-time.

המאמר עוסק ב-Windows 10 / 11 רגיל: בלי הרחבות RTOS, בלי kernel driver עצמאי, בלי בקר ייעודי. השאלה המעשית היא עד כמה אפשר לצמצם latency ו-jitter באפליקציית user-mode על desktop / laptop רגיל. באודיו, וידאו, בקרה מחזורית ואיסוף נתונים הפרטים שונים, אבל המקומות שנוטים להישבר משותפים למדי. לכן ריכזתי כאן את החלק המשותף כ-checklist.

למי זה מיועד, ובאיזו שפה דוגמאות הקוד

המאמר כתוב ל-מפתחים שבונים ב-Windows עיבוד ש"אי אפשר שיאחר" (בקרה מחזורית, אודיו/וידאו, מדידה, בקרת ציוד). מדובר בפיתוח אפליקציות user-mode. מימוש kernel-mode drivers מחוץ להיקף.

שפת דוגמאות הקוד מחולקת כך:

תוכן שפה איפה
periodic loop, MMCSS, power QoS וכדומה — חלקים שקוראים ישירות ל-Win32 API C++ (Win32) 4.1, 4.3, 4.5
אותה קריאה ל-Win32 API מ-C# C# (P/Invoke) 4.5
מדידת זמן, GC, allocation .NET (C#) הבדיקות בצד .NET בסעיף 4.4, וסעיף 5.2

הדיון בגורמים עד פרק 3, וגוף ה-checklist בפרק 4, לא תלויים בשפה. מי שעובד רק ב-C# יכול לקרוא את קוד ה-C++ פשוט כהסבר איזה API קוראים ובאיזה סדר.

תוכן עניינים

  1. המסקנה בקצרה
    • 1.1. טבלת קריאה מהירה לפי טווח מחזור
  2. מה זה soft real-time ב-Windows רגיל
    • 2.1. מה נקרא כאן "Windows רגיל"
    • 2.2. מה ריאלי, ומאיפה זה נהיה קשה
    • 2.3. מונחים, במשפט
  3. הגורמים העיקריים ל-latency ול-jitter
    • 3.1. scheduler ו-priority
    • 3.2. DPC / ISR ו-drivers
    • 3.3. page fault וזיכרון
    • 3.4. timer resolution ו-power management
    • 3.5. מעבר בין cores וחום
  4. checklist מעשי להקטנת latency ב-Windows רגיל
    • 4.1. periodic loop ואיך ממתינים
    • 4.2. fast path / slow path ו-queue באורך קבוע
    • 4.3. priority / MMCSS / background mode
    • 4.4. זיכרון / GC / עלות ראשונה
    • 4.5. הגדרות power / EcoQoS / timer resolution
    • 4.6. שיבוץ CPU / מעבר בין cores / חום
    • 4.7. isolation של driver / DPC / ISR / הפרעות חיצוניות
  5. מדידה והערכה
    • 5.1. מה מתעדים
    • 5.2. איך קוראים p99 / p99.9 / max
    • 5.3. באילו כלים מסתכלים
    • 5.4. איך בודקים
  6. איך בוחרים בפועל
  7. סיכום
  8. מקורות

ב-diagram, solid line מציינת relation שתמיד מתקיים ו-dashed line מציינת relation מותנה (התנאים מופיעים בהסבר של כל relation ב-detail page). הרשימה המלאה של ה-relations (סה”כ 20, כולל evidence ו-certainty) וההגדרות של ה-concepts המרכזיים נמצאות ב-detail page של ה-knowledge map (ביפנית). Data: JSON-LD / Turtle

1. המסקנה בקצרה

  • ב-Windows רגיל היעד אינו hard real-time. היעד הוא soft real-time: פחות איחורים, וגם כשמאחרים — המערכת לא נשברת.
  • מה שהכי משפיע: לקצר את ה-hot path, לשמור אותו fixed-size, ולשמור אותו non-blocking.
  • מפרידים fast path (acquisition / בקרה) מ-slow path (שמירה / תקשורת / UI), ומחברים ביניהם ב-queue באורך קבוע.
  • את ה-periodic loop מריצים לפי absolute deadline, לא לפי Sleep(1).
  • בזרם רציף כמו אודיו או וידאו, בודקים קודם MMCSS.
  • למדידת זמן משתמשים ב-QueryPerformanceCounter (QPC); ב-.NET ב-Stopwatch.
  • להמתנה מעדיפים device event או waitable timer ברזולוציה גבוהה.
  • את timeBeginPeriod קוראים רק כל עוד צריך. לא מתכננים בהנחה שהוא דולק תמיד.
  • בתפעול בפועל עוזרים AC power / power mode / הטיפול ב-EcoQoS / צמצום עומס ברקע.
  • מעריכים לא רק לפי ממוצע, אלא לפי p99 (הסף שבו מתחיל להיראות 1 מתוך 100 המדידות האיטיות) / p99.9 / max / מספר misses / DPC / ISR / page fault / עומק queue.

במילים אחרות, ב-Windows רגיל עדיף לצמצם בתכנון את הסיבות לאיחור, מאשר להעלות priority. priority והגדרות power חשובות, אבל לבדן הן לא מייצרות יציבות.

יציבות מגיעה מהתכנון, לא מ-priorityקיצור ה-hot path והפיכתו ל-fixed-size ול-non-blocking מקטינים בתכנון את הסיבות לאיחור, ומגיעים למבנה שמאחר פחות ולא נשבר. priority והגדרות power חשובות, אבל לבדן לא מספיקות.hot path קצר ו-fixed-sizeפחות סיבות לאיחור כבר בתכנוןמבנה שמאחר פחות ולא נשברכיוונון priority והגדרות powerחשוב, אבל לא מספיק לבד

איור 1: מה שהכי משפיע אינו כיוונון priority, אלא לצמצם בתכנון את הסיבות לאיחור עצמן.

1.1. טבלת קריאה מהירה לפי טווח מחזור

המאמר ארוך, לכן הטבלה הזו מאפשרת לקרוא רק את השורה שמתאימה למקרה שלכם. התוכן שישב פעם בסוף (פרק 6) הועבר לכאן.

מחזור / דרישה המבנה הראשוני הסעיפים שכדאי לקרוא
סדר גודל 10-20ms, אפשר לספוג תנודות מדי פעם הפרדת fast path / slow path, queue באורך קבוע, priority רגילה עד מעט גבוהה, event-driven. לרוב זה מספיק 4.1, 4.2
סדר גודל 1-5ms, רוצים לעמוד בזה באופן רציף בנוסף: בלי allocations ב-hot path, thread ייעודי, MMCSS או כיוונון priority זהיר, waitable timer ברזולוציה גבוהה, AC power ובדיקת הגדרות power 4.1 עד 4.5
מתקרבים ל-מתחת ל-1ms, ולא רוצים לפספס גם בריצה ארוכה ובעומס גבוה קשה מאוד עם user-mode לבד ב-Windows רגיל. כדאי לשקול קודם תכנון שמעביר את החלק הקריטי החוצה (firmware בצד ה-device, בקר ייעודי, FPGA, RTOS) 2.2, 6
רוצים הכול באותו מקום — GUI / logging / תקשורת / DB לא לגורר הכול ב-“process אחד, loop אחד”. מחלקים אחריות. העבודה בשלב המאוחר נוטה לשבור את ה-deadline של השלב המוקדם 4.2, 4.3, 6

isolation של גורמים ושיטת המדידה משותפים לכל טווח (פרקים 3 ו-5).

2. מה זה soft real-time ב-Windows רגיל

2.1. מה נקרא כאן “Windows רגיל”

Windows רגיל, כאן, מניח בערך את הדברים הבאים.

  • desktop / laptop רגיל עם Windows 10 / 11
  • בלי הרחבת RTOS עצמאית
  • בלי פיתוח kernel-mode driver עצמאי
  • אפליקציית user-mode רגילה
  • כיוונון עם Windows API והגדרות רגילות

כלומר, זה לא סיפור של “לבנות מכונה ייעודית אחת כולה לבקרת real-time”, אלא “עד כמה אפשר לצמצם באופן מעשי על PC Windows רגיל”.

היקף המאמרהמאמר עוסק באפליקציית user-mode על PC Windows רגיל שמכוונת ל-soft real-time — latency נמוך, פחות jitter, ומעקב אחרי deadline misses — ודרישה לאפס misses שייכת ל-RTOS, בקר ייעודי, FPGA או עיבוד בצד ה-device.PC Windows 10 / 11 רגילאפליקציית user-modeמכוונים ל-soft real-timeמורידים latencyמקטינים jitterמנטרים deadline misses ולא נשבריםרוצים להבטיח אפס deadline missesRTOS / בקר ייעודי / FPGA / עיבוד בצד ה-device

איור 2: באפליקציית user-mode ב-Windows רגיל מכוונים ל-soft real-time. אפס deadline misses שייך ל-RTOS וכדומה.

2.2. מה ריאלי, ומאיפה זה נהיה קשה

גם ב-Windows רגיל אפשר להגיע למצב מעשי של “מאחר פחות” בעיבודים כמו אלה.

  • periodic processing של כמה ms עד כמה עשרות ms
  • audio / video שמבוססים על buffer
  • קריאה מחיישנים ו-control loops
  • עיבוד מחזורי קבוע בסגנון soft PLC
  • pipeline בהשהיה נמוכה שרץ ב-thread נפרד מה-UI

עם זאת, “אפשר” כאן אינו אומר שאפשר לאפס לגמרי latency spikes מדי פעם. המטרה היא המצב הבא.

  • להוריד את ה-latency הרגיל
  • להקטין jitter
  • לא להישבר גם כשמפספסים deadline מדי פעם
  • לראות בבירור שפספסנו

לעומת זאת, כשהדרישה היא כזו, קשה מאוד לספק אותה עם user-mode ב-Windows רגיל בלבד.

  • רוצים להבטיח אפס deadline misses
  • רוצים לשמור בעקביות על פחות ממאות מיקרו-שניות לאורך זמן
  • רוצים לחיות יחד עם GUI כבד, רשת ו-storage
  • רוצים לעשות זאת על סוללה או במצב חיסכון בחשמל
  • אסור שיופיעו גם spikes שמקורם ב-driver או ב-device

במקרים כאלה, בטוח יותר לשקול להעביר רק את החלק שבאמת דורש דיוק זמנים חמור ל-firmware בצד ה-device, לבקר ייעודי, ל-FPGA או ל-RTOS.

המצב שמכוונים אליו ב-soft real-time, ומתי מעבירים החוצהב-Windows רגיל מכוונים להוריד latency רגיל, להקטין jitter, ולא להישבר תוך יכולת לנטר כשמפספסים deadline מדי פעם. דרישה כמו אפס deadline misses שווה להעביר החוצה רק את החלק שדורש דיוק זמנים.המצב שמכוונים אליו ב-soft real-timeהורדת ה-latency הרגילהקטנת jitterלא נשברים גם כשמפספסים, וניתן לנטרדרישה של אפס deadline missesמעבירים ל-device או RTOS

איור 3: “אפשר” אינו אפס spikes. זה מצב שמאחר פחות, נשבר פחות, וניתן לנטר.

2.3. מונחים, במשפט

לפני שנכנסים לגוף, הנה המשמעות של המונחים במאמר.

מונח במשפט הזווית המעשית
soft real-time מקבלים שאיחור מדי פעם אפשרי, מקטינים אותו, ולא נשברים גם כשהוא קורה זה מה שמכוונים אליו קודם ב-Windows רגיל
hard real-time עולם שבו רוצים להבטיח אפס deadline misses לא יעד מעשי ל-user-mode לבד ב-Windows רגיל
jitter תנודה במחזור או בזמן התגובה גם אם הממוצע טוב, jitter גדול לא יציב בתפעול
deadline miss העיבוד לא מסתיים עד הזמן המתוכנן לא מסתירים, סופרים, וכותבים ללוג
p99 / p99.9 מדדים שמראים את הזנב האיטי p99 הוא “הסף שבו מתחיל להיראות 1 מתוך 100 המדידות האיטיות”
DPC / ISR עיבוד בצד ה-kernel סביב drivers ו-interrupts אם הוא ארוך, thread ה-user-mode נאלץ לחכות
MMCSS מנגנון ב-Windows שמקצה CPU לעיבוד רגיש-זמן כמו אודיו / וידאו שימושי בעיבוד שלא רוצים שה-buffer שלו יתרוקן
QPC כינוי ל-QueryPerformanceCounter הבסיס למדידת זמן שעבר. לא שעון קיר, אלא מונה ברזולוציה גבוהה
waitable timer אובייקט kernel שהופך ל-signaled בזמן שנקבע. אם מוסיפים CREATE_WAITABLE_TIMER_HIGH_RESOLUTION ל-CreateWaitableTimerExW, מקבלים גרסה ברזולוציה גבוהה מתאים כבסיס להמתנה מחזורית יותר מ-Sleep (4.1)
EcoQoS מצב שסווג כ”מותר לתת עדיפות לחיסכון בחשמל”. יכול להוריד תדר CPU או להעביר ל-cores חסכוניים נמנעים ממנו בעיבוד רגיש-זמן. אם לא מציינים במפורש, ה-OS מנחש אוטומטית (4.5)
IGNORE_TIMER_RESOLUTION ציון “מותר להתעלם מבקשת timer resolution של ה-process” (PROCESS_POWER_THROTTLING_IGNORE_TIMER_RESOLUTION) אם זה דולק, האפקט של timeBeginPeriod נעלם. ב-Windows 11 זה יכול לחול אוטומטית כשהחלון מוסתר (4.5)
CPU Sets מנגנון לציון רך מסוג “רוצים שהריצה תהיה בקבוצת ה-cores הזו” עבור thread או process בודקים לפני pin ל-core ספציפי (4.6)
ETW / WPR / WPA תשתית ה-trace הסטנדרטית של Windows (ETW), כלי ההקלטה שלה (WPR) וה-GUI לניתוח (WPA) משמש לחפירה ב-context switch, DPC / ISR ו-page fault (5.3)
LatencyMon כלי צד-שלישי לבדיקת latency שמקורו ב-driver סוקר את זמני ה-DPC / ISR לפי driver כדי לזהות חשוד (5.3)

3. הגורמים העיקריים ל-latency ול-jitter

הסיבה שבגללה periodic processing מאחר ב-Windows רגיל מגיעה בדרך כלל לאחד מהענפים בתרשים הזה.

גורמים לאיחור ב-periodic processingחמישה כיווני חקירה כש-periodic processing מאחר: scheduler ו-priority, DPC ו-ISR ו-drivers, page fault וזיכרון, timer resolution ו-power management, ומעבר בין cores וחום.periodic processing מאחרscheduler / priorityDPC / ISR / driverpage fault / זיכרוןtimer resolution / power managementמעבר בין cores / חום

איור 4: הסיבות לאיחור ב-periodic processing מגיעות לחמש מערכות — scheduler, DPC/ISR, זיכרון, timer ו-power, ומעבר בין cores וחום.

3.1. scheduler ו-priority

סדר הריצה של threads ב-Windows נקבע לפי priority. באותו priority רצים ב-round-robin, וכש-thread בעדיפות גבוהה יותר הופך ל-runnable, thread בעדיפות נמוכה יותר נדחק הצידה.

כלומר, גם אם כותבים periodic thread ברצינות, יכולים באופן שגרתי לרוץ לפניו:

  • thread אחר
  • process אחר
  • עיבוד פנימי של ה-OS
  • מוצר אבטחה
  • עיבוד עזר של device
  • סנכרון ברקע
דחיקה על ידי scheduling לפי prioritythreads באותו priority רצים ב-round-robin, וכש-thread בעדיפות גבוהה יותר הופך ל-runnable, thread בעדיפות נמוכה נדחק הצידה. לכן זה שגרתי ש-process אחר או עיבוד פנימי של ה-OS ירוצו לפני periodic thread.threads באותו priorityרצים ב-round-robinpriority גבוה יותר הופך ל-runnablepriority נמוך נדחק הצידהלמשל process אחר או עיבוד פנימי של ה-OS

איור 5: גם כשכותבים periodic thread ברצינות, זה שגרתי שעבודה בעדיפות גבוהה יותר תרוץ לפניו.

3.2. DPC / ISR ו-drivers

זו נקודה חשובה מאוד. גם אם מסדרים את ה-priority בצד האפליקציה, אם DPC (Deferred Procedure Call) או ISR (Interrupt Service Routine) ארוכים, thread ה-user-mode לא יכול לרוץ באותו זמן.

ה-devices וה-drivers שנוטים לגרום לזה הם בערך אלה.

  • USB
  • Wi-Fi / Bluetooth
  • storage
  • audio
  • GPU
  • ACPI וסביבת power

גם כשהקוד של האפליקציה בסדר, אפשר להיעצר בגלל ה-driver או החומרה. כאן, אם חושבים “אם מעלים עוד את ה-priority של האפליקציה, ננצח” — בדרך כלל נכשלים בכאב.

איך DPC ו-ISR עוצרים את ה-user-modeכש-devices כמו USB, Wi-Fi ו-GPU מפעילים ISR או DPC ארוכים בצד ה-kernel, thread ה-user-mode לא יכול לרוץ באותו זמן, וגם העלאת priority של האפליקציה לא מנצחת את זה.devices כמו USB, Wi-Fi, GPUISR או DPC רצים בצד ה-kernelבאותו זמן ה-user-mode לא יכול לרוץגם העלאת priority לא מנצחת

איור 6: גם כשהקוד של האפליקציה בסדר, ה-user-mode נעצר בגלל ה-driver או החומרה.

3.3. page fault וזיכרון

כשמתרחש page fault ב-hot path (העמוד הדרוש לא נמצא בזיכרון, ויוצאים להביא אותו), ה-latency קופץ בבת אחת.

תבניות שכדאי במיוחד להימנע מהן:

  • commit של עמוד בגישה הראשונה
  • lazy load
  • page-in של memory-mapped file
  • הקצאה דינמית מעבר לנדרש
  • אובייקטים גדולים או heap מפוצל

בגוף ה-periodic processing, נכון להקצות מראש את הזיכרון הדרוש ולגעת בו פעם אחת בזמן ה-startup.

latency שנגרם מ-page fault ב-hot pathאם העמוד שנוגעים בו ב-hot path לא נמצא בזיכרון, יוצאים להביא אותו ב-page fault וה-latency קופץ בבת אחת. לכן עדיף להקצות מראש ולגעת פעם אחת ב-startup.כןלאנוגעים בזיכרון ב-hot pathהעמוד נמצא בזיכרון?ממשיכים כרגיליוצאים להביא אותו ב-page faultה-latency קופץ בבת אחתהקצאה מראש ונגיעה פעם אחת ב-startup

איור 7: ה-latency קופץ לפי אם מתרחש page fault. הפתרון הוא הקצאה מראש ו-warmup ב-startup.

3.4. timer resolution ו-power management

“רוצה להריץ כל 1ms, אז Sleep(1)” — כמעט תמיד לא עובד. דיוק ההמתנה ב-Windows מושפע מ-timer resolution, מה-scheduler, וממצב ה-power.

מעבר לזה, הגדרה שמעלה את ה-timer resolution משפרת מעט את דיוק ההמתנה, אבל יש לה תופעות לוואי על צריכת חשמל ועל התנהגות המערכת כולה — נקודה שאי אפשר להתעלם ממנה.

3.5. מעבר בין cores וחום

כש-thread עובר בין cores, נוצר חימום מחדש של ה-cache. זה עצמו מטופל לרוב היטב על ידי ה-OS, אבל בסביבה עמוסה זה הופך לגורם תנודה.

מעבר לזה, בריצה ארוכה גם החום אי אפשר להתעלם ממנו. כשנכנס thermal throttling, המחזור שהיה יציב עד אז יכול להישבר.

המסלול שבו מעבר בין cores וחום שוברים את המחזורמעבר thread בין cores גורם לחימום מחדש של ה-cache שהופך לגורם תנודה בסביבה עמוסה, ובריצה ארוכה הצטברות חום מפעילה thermal throttling ששובר את המחזור שהיה יציב.thread עובר בין coresחימום מחדש של ה-cacheבסביבה עמוסה — גורם תנודהבריצה ארוכה מצטבר חוםthermal throttlingהמחזור היציב נשבר

איור 8: מעבר בין cores פוגע ביציבות דרך jitter, וחום פוגע בה דרך throttling — שניהם בריצה ארוכה.

4. checklist מעשי להקטנת latency ב-Windows רגיל

מכאן החלק המעשי. לגורמים שראינו למעלה, ה-checklist מרכז מה בודקים, ממה נמנעים, ומה מחליטים מראש ב-Windows רגיל.

4.1. periodic loop ואיך ממתינים

קודם כול, anti-pattern טיפוסי הוא זה.

while (running)
{
    Sleep(1);
    Step();
}

זו לא לולאה של “מחזור 1ms”. זו לולאה שבה ממתינים בערך 1ms ומעלה, ומעל זה מוסיפים את זמן הריצה של Step(). בנוסף, overshoot של ההמתנה מצטבר כמו שהוא.

המתנה יחסית מול absolute deadlineהשוואה בין לולאה מבוססת Sleep יחסי, שבה שגיאת ההמתנה וזמן הריצה מצטברים בהדרגה, לבין לולאה מבוססת absolute deadline שמקדמת את הזמן הבא בקבוע ולכן קשה לצבור בה drift.מבוסס absolute deadlineמבוסס זמן יחסיWaitUntil(next - margin)next += periodspin קצר במידת הצורךFastStep()Step()Sleep(1)שגיאת ההמתנה וזמן הריצה מצטברים בהדרגהקשה לצבור drift

איור 9: לולאה מבוססת זמן יחסי שמסתמכת על Sleep(1) צוברת שגיאה. לולאה מבוססת absolute deadline קשה לה לצבור drift.

checklist

  • לא מבססים את ה-periodic loop על Sleep(1)
  • מריצים את המחזור לפי absolute deadline של next += period
  • להמתנה מעדיפים device event או waitable timer
  • רק את ה-fine-tune האחרון מגבילים ל-busy-spin קצר מאוד
  • את timeBeginPeriod קוראים רק כל עוד צריך, ומחזירים כשמסיימים
  • בודקים את ההתנהגות גם במצב ממוזער / מוסתר / לא נראה

ה-periodic loop יציב יותר כשהוא רץ לפי absolute deadline, לא לפי זמן יחסי.

int64_t next = QpcNow() + periodTicks;

while (running)
{
    WaitUntil(next - wakeMarginTicks);

    while (QpcNow() < next)
    {
        CpuRelax(); // רק בסוף, spin קצר
    }

    int64_t started = QpcNow();
    FastStep();
    int64_t finished = QpcNow();

    RecordTiming(next, started, finished);

    next += periodTicks;

    while (finished > next)
    {
        ++missedDeadlines;
        next += periodTicks;
    }
}

4.2. fast path / slow path ו-queue באורך קבוע

יסוד המבנה: ב-fast path שמים רק עבודה רגישה ל-deadline, ואת השאר מוציאים ל-slow path.

הפרדה בין fast path ל-slow pathevent מה-device מטופל ב-fast path עם עבודה מינימלית, עובר דרך queue באורך קבוע אל slow path שמבצע שמירה, שליחה, UI וסיכום, וה-fast path מתעד גם lateness, miss ועומק queue.device / event של acquisitionfast path: acquisition, בקרה, העתקה מינימליתqueue באורך קבועslow path: שמירה, שליחה, UI, סיכוםתיעוד lateness / miss / עומק queue

איור 10: ב-fast path שמים רק עבודה רגישה ל-deadline, ומוציאים ל-slow path דרך queue באורך קבוע.

מה שעושים ב-fast path מצמצמים בערך לזה.

  • acquisition של נתונים
  • חישוב ערך בקרה
  • העתקה מינימלית נדרשת
  • timestamp
  • הזנה ל-queue
  • תיעוד miss / overrun

כל השאר יורד ל-slow path.

checklist

  • אין כתיבה לקובץ, שליחה ברשת, כתיבה ל-DB ב-hot path
  • אין logging כבד, Flush, או RPC סינכרוני ב-hot path
  • מפרידים בבירור בין fast path ל-slow path לפי thread או אחריות
  • ה-queue באורך קבוע
  • מדיניות למקרה שה-queue מלא הוחלטה מראש
  • מנטרים מספר miss, מספר drop, ועומק queue
  • עדכון UI וריכוז לוגים מופרדים לתדירות נמוכה

כשה-queue מתמלא, בטוח יותר לא להשאיר את המדיניות מעורפלת.

מדיניות כשה-queue מתמלאשלוש מדיניות אפשריות כשה-queue מלא, לפי מה שחשוב לשמור: מחיקת ישן ושמירת העדכני, התראה ועצירה או בקרת המקור, או הפלת ישן עם תיעוד מספר ה-drop בלבד.הערך העדכני חשובכל הרשומות חשובותלצורכי לוגה-queue מלאמה שומרים?מוחקים ישן ומשאירים עדכניהתראה / עצירה / בקרת המקורמפילים ישן, ומתעדים רק את מספר ה-drop

איור 11: מה שומרים כשה-queue מתמלא מחליטים מראש לפי מטרת השימוש.

4.3. priority / MMCSS / background mode

יסוד ה-priority: לא להעלות הכול. ב-Windows רגיל, “מעלים רק את ה-threads החשובים, ומורידים כראוי את עבודות הרקע” עובד טוב יותר. background mode הוא מנגנון שמתייחס בעדיפות נמוכה לא רק ל-CPU אלא גם למשאבים כמו I/O.

חלוקת העבודה בין threads ו-prioritiesחלוקה של העבודה ל-thread רגיש ל-deadline, ל-worker thread ול-UI, עם priority גבוה יותר או MMCSS לראשון, background mode לשני ו-priority רגילה לשלישי, ובלי לעבור מלכתחילה ל-REALTIME_PRIORITY_CLASS.מחלקים עבודהthread רגיש ל-deadlineשמירה / שליחה / דחיסה / סיכוםUIpriority גבוה יותר או MMCSS, במידת הצורךbackground mode / priority נמוכה יותרpriority רגילהלא הופכים ל-REALTIME_PRIORITY_CLASS מלכתחילה

איור 12: איך מחלקים priority — מעלים רק את ה-thread הרגיש ל-deadline, ומורידים כראוי את עבודות הרקע.

checklist

  • לא הופכים את כל ה-threads ל-priority גבוהה
  • מעלים רק את ה-thread שבאמת רגיש לזמן
  • עבודות רקע כמו שמירה, שליחה, דחיסה וסנכרון יורדות ל-background mode
  • בעיבוד buffer רציף כמו אודיו, וידאו, capture או playback, שוקלים MMCSS
  • חושבים קודם ברמת ה-thread, לא ברמת ה-process כולו
  • לא משתמשים ב-REALTIME_PRIORITY_CLASS עד שהצורך ברור

MMCSS (Multimedia Class Scheduler Service) יעיל במיוחד עבור עיבוד כמו אודיו / וידאו שרוצים למלא buffer בתוך זמן קצוב. זה תואם יותר לתכנון של Windows מאשר פשוט להריץ thread ב-priority גבוהה כל הזמן.

הרגשת הקוד נראית בערך כך.

DWORD taskIndex = 0;
HANDLE avrt = AvSetMmThreadCharacteristicsW(L"Pro Audio", &taskIndex);
if (!avrt)
{
    throw std::runtime_error("AvSetMmThreadCharacteristicsW failed");
}

// כאן רצה הלולאה הרגישה לזמן

if (!AvRevertMmThreadCharacteristics(avrt))
{
    throw std::runtime_error("AvRevertMmThreadCharacteristics failed");
}
רישום וביטול thread ל-MMCSSלפני הלולאה הרגישה לזמן נרשמים ל-MMCSS עם AvSetMmThreadCharacteristicsW, ואחרי שהלולאה מסתיימת חוזרים עם AvRevertMmThreadCharacteristics. MMCSS מקצה CPU בעדיפות לעיבוד רגיש-זמן.AvSetMmThreadCharacteristicsWמריצים לולאה רגישה לזמןAvRevertMmThreadCharacteristicsMMCSS מקצה CPU בעדיפות

איור 13: משתמשים ב-MMCSS כזוג של רישום וביטול. בעיבוד buffer רציף, זה תואם יותר את תכנון Windows מהרצה קבועה ב-priority גבוהה.

4.4. זיכרון / GC / עלות ראשונה

אם משתמשים ב-hot path בכל פעם ב-new / malloc / List<T>.Add / חיבור מחרוזות / LINQ, מוקדם או מאוחר צרכי ה-collection או ה-compaction יעלו לפני השטח. GC עצמו אינו רע, אך אם כותבים קוד עתיר allocations, ההשפעה מופיעה כ-jitter.

warmup לפני המדידהאחרי ה-startup מקצים את ה-buffers הדרושים, נוגעים בהם פעם אחת כדי לחמם עמודים, מסיימים JIT, טעינת DLL ו-I/O ראשוני, ורק אחר כך מודדים או מריצים בפועל.startupמקצים את ה-buffers הדרושיםנוגעים פעם אחת כדי לחמם עמודיםמסיימים JIT, טעינת DLL, ו-I/O ראשונירק אחר כך מודדים / מריצים בפועל

איור 14: מקצים buffers, מחממים עמודים, ומסיימים עלות ראשונה ב-startup, ורק אז נכנסים למדידה / ריצה אמיתית.

checklist

  • אין allocation / שחרור זיכרון בכל פעם ב-hot path
  • ה-buffers הדרושים מוקצים מראש ב-startup
  • נוגעים פעם אחת ב-startup כדי לחמם עמודים
  • ה-JIT הראשוני, טעינת ה-DLL הראשונה, וה-I/O הראשוני לא מעורבים במדידה האמיתית
  • לא מגדלים מבנה ענק או לוג באורך משתנה בתוך הלולאה
  • גם אם משתמשים ב-VirtualLock, זה מוגבל לאזור קריטי קטן מאוד

בדיקות בצד .NET

  • למדידת זמן משתמשים ב-Stopwatch / Stopwatch.GetTimestamp()
  • אין LINQ, חיבור מחרוזות, ToString(), או יצירת לוג ענק ב-hot path
  • לא מכניסים async/await ל-hot path
  • מעריכים בנפרד לפני ואחרי warmup

4.5. הגדרות power / EcoQoS / timer resolution

זה חלק לא בולט אבל יעיל. גם אם מסדרים את הקוד, אם בקרת ה-power ברמה הגבוהה יותר פעילה בעוצמה, התוצאה לא תהיה יציבה.

הגדרות powerמה בודקים בסביבת ה-power של Windows רגיל: הרצה על AC, power mode שמכוון לביצועים, תוכנית power ייעודית לסביבת ייצור, הימנעות מ-EcoQoS ב-process רגיש-זמן, ובדיקת הטיפול בבקשת timer resolution.סביבת ה-power ב-Windows רגילמריצים על ACpower mode: כיוון לביצועים מיטבייםבמידת הצורך, תוכנית power ייעודית לסביבת ייצורנמנעים מ-EcoQoS ב-process רגיש-זמןבודקים את הטיפול בבקשת timer resolution

איור 15: הנקודות שבודקים בסביבת ה-power — חיבור לחשמל, power mode, EcoQoS, וטיפול בבקשת timer resolution.

checklist

  • הערכת הייצור נעשית קודם על AC
  • [הגדרות] > [מערכת] > [חשמל וסוללה] > [מצב צריכת חשמל] מכוונים לכיוון ביצועים מיטביים
  • לא משתמשים במצב חיסכון בסוללה / חיסכון בחשמל בזמן הריצה
  • בודקים מצבי שקט / eco / battery של כלים עצמאיים של היצרן
  • לא משאירים process רגיש-זמן ב-EcoQoS (QoS מוטה חיסכון) בלי כוונה
  • IGNORE_TIMER_RESOLUTION לא דולק בצד process רגיש-זמן
  • בודקים שהאפקט של בקשת timer resolution לא משתנה כשממזערים / מסתירים
  • מפרידים הגדרות power לשימוש יומיומי מהגדרות לייצור / מדידה / הדגמה

timeBeginPeriod שימושי כשמשתמשים בו בזהירות, אבל הוא לא תרופת פלא.

  • קוראים לו ממש לפני הצורך
  • כשמסיימים, מחזירים עם timeEndPeriod
  • מ-Windows 10 version 2004 ואילך, ההתנהגות אינה גלובלית מלאה כפי שהייתה בעבר
  • ב-Windows 11, כש-process עם חלון מוסתר לגמרי / ממוזער / לא נראה / לא נשמע, ייתכן שהרזולוציה הגבוהה לא מובטחת
  • העלאת הרזולוציה לא משפרת את דיוק ה-QPC

אם ההשפעה של power או QoS מוטלת בספק, בודקים את מצב ה-power throttling עם SetProcessInformation.

PROCESS_POWER_THROTTLING_STATE state{};
state.Version = PROCESS_POWER_THROTTLING_CURRENT_VERSION;
state.ControlMask =
    PROCESS_POWER_THROTTLING_EXECUTION_SPEED |
    PROCESS_POWER_THROTTLING_IGNORE_TIMER_RESOLUTION;
state.StateMask = 0; // HighQoS (מוטה ביצועים) + כיבוד בקשת timer resolution

if (!SetProcessInformation(
        GetCurrentProcess(),
        ProcessPowerThrottling,
        &state,
        sizeof(state)))
{
    throw std::runtime_error("SetProcessInformation failed");
}

ControlMask הוא “אילו מנגנונים שולטים בעצמכם”, ו-StateMask הוא “האם מפעילים או מכבים את המנגנון הזה”. הדוגמה למעלה בוחרת שני מנגנונים לשליטה, ומכבה את שניהם. כלומר, זו הצהרה שלא נופלים ל-EcoQoS (מוטה HighQoS), ושבקשת timer resolution לא מתעלמים ממנה. לעומת זאת, אם הופכים את ControlMask ל-0, שניהם חוזרים ל-OS (התנהגות ברירת מחדל).

תפקיד שתי המסכות בהגדרת power throttlingבוחרים את המנגנון שנשלט על ידי ControlMask, וקובעים אם הוא פועל או כבוי עם StateMask, וכך אפשר להצהיר שלא נופלים ל-EcoQoS ושבקשת timer resolution לא מתעלמים ממנה.ControlMask בוחר יעד שליטהStateMask קובע פועל וכבויהצהרה שלא נופלים ל-EcoQoSבקשת timer resolution לא מתעלמים ממנהאם ControlMask הוא 0, חוזרים ל-OS

איור 16: תפקיד שתי המסכות של SetProcessInformation. קודם בוחרים יעד שליטה, ואז מצהירים on / off.

כשקוראים מ-C#

אם רוצים לעשות את אותו הדבר מ-C#, הצהרת ה-P/Invoke נראית כך.

// C# / .NET 8
using System.Runtime.InteropServices;

internal static class PowerQos
{
    [StructLayout(LayoutKind.Sequential)]
    private struct PROCESS_POWER_THROTTLING_STATE
    {
        public uint Version;
        public uint ControlMask;
        public uint StateMask;
    }

    private const uint PROCESS_POWER_THROTTLING_CURRENT_VERSION = 1;
    private const uint PROCESS_POWER_THROTTLING_EXECUTION_SPEED = 0x1;
    private const uint PROCESS_POWER_THROTTLING_IGNORE_TIMER_RESOLUTION = 0x4;

    // המקום החמישי (4 מ-0) ב-PROCESS_INFORMATION_CLASS הוא ProcessPowerThrottling
    private const int ProcessPowerThrottling = 4;

    [DllImport("kernel32.dll", SetLastError = true)]
    [return: MarshalAs(UnmanagedType.Bool)]
    private static extern bool SetProcessInformation(
        IntPtr hProcess,
        int processInformationClass,
        ref PROCESS_POWER_THROTTLING_STATE processInformation,
        uint processInformationSize);

    [DllImport("kernel32.dll")]
    private static extern IntPtr GetCurrentProcess();

    /// <summary>מבטל גם את הנטייה לחיסכון בחשמל, וגם את ההתעלמות מבקשת timer resolution.</summary>
    public static void OptOutOfPowerThrottling()
    {
        var state = new PROCESS_POWER_THROTTLING_STATE
        {
            Version = PROCESS_POWER_THROTTLING_CURRENT_VERSION,
            ControlMask =
                PROCESS_POWER_THROTTLING_EXECUTION_SPEED |
                PROCESS_POWER_THROTTLING_IGNORE_TIMER_RESOLUTION,
            StateMask = 0,
        };

        if (!SetProcessInformation(
                GetCurrentProcess(),
                ProcessPowerThrottling,
                ref state,
                (uint)Marshal.SizeOf<PROCESS_POWER_THROTTLING_STATE>()))
        {
            throw new System.ComponentModel.Win32Exception(Marshal.GetLastWin32Error());
        }
    }
}

הקריאה מתבצעת פעם אחת עם PowerQos.OptOutOfPowerThrottling(); לפני התחלת הלולאה הרגישה לזמן. ל-handle של ה-process נדרשת הרשאת PROCESS_SET_INFORMATION, אבל ה-handle המדומה של ה-process העצמי שמחזיר GetCurrentProcess() לא בעייתי.

4.6. שיבוץ CPU / מעבר בין cores / חום

בשיבוץ CPU, לרוב עדיף להתחיל מ-ציון רך הקרוב ל-soft affinity, “רוצים שהריצה תהיה בקבוצת ה-cores הזו”, ולא ישר מ-pin ל-core ספציפי (hard affinity / CPU pinning).

סדר הצעדים בשיבוץ coresקודם מודדים, אחר כך מנסים SetThreadIdealProcessor או CPU Sets, ורק אם השיפור אינו מספיק שוקלים בסוף SetThreadAffinityMask, תוך בדיקה מקבילה של טמפרטורה, clock וריצה ארוכה.כןלאקודם מודדיםSetThreadIdealProcessor / CPU Setsהשתפר מספיק?עוצרים כאןבסוף שוקלים SetThreadAffinityMaskבאותו זמן בודקים גם טמפרטורה / clock / ריצה ארוכה

איור 17: שיבוץ CPU מתחיל במדידה. אם ציון רך מספיק, עוצרים שם. pin ל-core ספציפי הוא המוצא האחרון.

checklist

  • קודם מודדים, ורק אז נוגעים בשיבוץ CPU
  • לא עושים pin ישר ל-core ספציפי
  • בודקים קודם את SetThreadIdealProcessor או CPU Sets
  • SetThreadAffinityMask נחשב מוצא אחרון
  • בריצה ארוכה בודקים טמפרטורה, clock ו-thermal throttling
  • בודקים מצב שקט או רעש נמוך ב-laptop

הסדר הבטוח הוא הזרימה הזו.

  1. קודם מודדים
  2. במידת הצורך, ideal processor / CPU Sets
  3. אם עדיין נדרש שיפור, pin ל-core ספציפי

pin ל-core ספציפי נראה יעיל, אבל הוא מצמצם את דרכי המילוט של ה-OS, ולכן שימוש לא זהיר בו עלול להפוך את המצב לפחות גמיש.

4.7. isolation של driver / DPC / ISR / הפרעות חיצוניות

כשקורה “לפעמים רק ה-max מתפוצץ” או “הממוצע טוב אבל p99.9 גרוע”, כדאי לחשוד גם בהפרעות חיצוניות מחוץ לקוד האפליקציה.

עץ ההחלטה כשמופיע spikeסדר isolation כשמופיע spike של late, miss או max: תחילה זמן העיבוד של האפליקציה, אחר כך DPC ו-ISR, אחר כך page fault, GC ועלות ראשונה, אחר כך סוללה, חיסכון בחשמל וחום, ולבסוף חפירה עם ETW, WPA או LatencyMon.כןלאכןלאכןלאכןלאהופיע spike של late / miss / maxגם זמן העיבוד שלכם ארוך?קיצור hot path / הפחתת allocations / הסרת I/Oיש spike של DPC / ISR?בודקים USB / Wi-Fi / Bluetooth / GPU / audio / storage / ACPI / עדכון driverיש page fault / GC / עלות ראשונה?הקצאה מראש / warmup / הפחתת עומס ב-heapיש השפעה של סוללה / חיסכון בחשמל / חום?AC / הגדרות power / קירור / בדיקה ארוכהחפירה עם ETW / WPA / LatencyMon

איור 18: סדר ה-isolation כשמופיע spike. חושדים לפי הסדר בעיבוד שלכם עצמו, ב-DPC/ISR, בזיכרון, וב-power וחום.

checklist

  • בודקים drivers סביב Wi-Fi / Bluetooth / USB / storage / GPU / audio
  • מכבים סנכרון ענן, בניית אינדקס, ועדכון אוטומטי מיותרים ומשווים
  • בודקים גם אם זה נשבר בהקטנה, או אם זה נשבר כשהמסך כבוי
  • בודקים מגמות DPC / ISR עם LatencyMon או ETW
  • מבדילים בין “העיבוד שלנו כבד” לבין “מישהו מבחוץ עוצר אותנו”

5. מדידה והערכה

5.1. מה מתעדים

אלה הדברים שכדאי לקחת לפחות.

  • זמן מתוכנן של המחזור
  • זמן התחלה בפועל
  • זמן סיום בפועל
  • lateness (כמה איחור ביחס להתחלה המתוכננת)
  • זמן ריצה
  • מספר deadline misses
  • מספר deadline misses ברצף
  • עומק queue
  • מספר drop
  • ניצול CPU
  • הטיה בין cores
  • DPC / ISR spikes
  • page fault
  • תנודות טמפרטורה / clock

גם אם מסתכלים רק על הממוצע, קשה לתפוס את המהות. מה שגורם לבעיות בייצור הוא ה-spikes הגדולים שמופיעים מדי פעם.

5.2. איך קוראים p99 / p99.9 / max

מדדים כמו p99 נועדו לראות את הזנב האיטי. בממוצע בלבד, ה-latency הגדול שמופיע מדי פעם מוסתר.

מדד משמעות תמונה כשמודדים 10,000 פעמים
ממוצע ערך ממוצע כללי קל שה-spike ייטמע בו
p50 הערך האמצעי קרוב לתחושה היומיומית
p95 הסף שבו מתחילים להיראות 5% האיטיים הסף אחרי הוצאת 500 המדידות האיטיות
p99 הסף שבו מתחיל להיראות 1% האיטי הסף אחרי הוצאת 100 המדידות האיטיות
p99.9 הסף שבו מתחיל להיראות 0.1% האיטי הסף אחרי הוצאת 10 המדידות האיטיות
max הערך הגרוע ביותר המדידה האיטית ביותר מכולן

לדוגמה, נניח שהתקבל הרצף הבא (דוגמה להסבר איך קוראים את המדדים, לא ערכים שנמדדו בפועל מציוד מסוים).

  • ממוצע: 0.8ms
  • p99: 1.2ms
  • p99.9: 3.5ms
  • max: 28ms

זה מצב שבו בדרך כלל מהיר, אבל מדי פעם יש spike גדול. ב-Windows רגיל, הבעיה האמיתית מופיעה בדרך כלל בזנב הזה, מ-p99 עד max.

ההבדל בין הממוצע לבין המדדים בצד הזנבכשמסתכלים רק על הממוצע, ה-latency הגדול שמופיע מדי פעם מוסתר. כשמסתכלים על p99, p99.9 ו-max, נראה הזנב האיטי, וב-Windows רגיל הבעיה האמיתית מופיעה שם.מסתכלים רק על הממוצעה-latency הגדול שמופיע מדי פעם מוסתרמסתכלים על p99, p99.9 ו-maxנראה הזנב האיטיהבעיה האמיתית מופיעה בין p99 ל-max

איור 19: גם אם הממוצע טוב, אם ה-max קופץ, זה מצב “בדרך כלל מהיר אבל קופץ מדי פעם”. תופסים את זה עם מדדי הזנב.

המאמר הזה אינו מציג ערכים מספריים להשפעת המבנה המומלץ. latency ו-jitter משתנים בקלות לפי CPU, driver, תוכנה נלווית, הגדרות power ואופן העמסת העומס, ולכן ערכים ממחשב של מישהו אחר לא משמשים כשלעצמם הוכחה עבור הסביבה שלכם. במקום זאת, הנה נוהל ליצירת אותה טבלה עבור הסביבה שלכם.

הנוהל המינימלי להוצאת p99 בסביבה שלכם

  1. ב-hot path מתעדים בלבד. לוקחים lateness וזמן ריצה עם Stopwatch.GetTimestamp() (ב-C++: QueryPerformanceCounter), וכותבים למערך שהוקצה מראש. אסור לחשב כאן ממוצע או מיון
  2. עוצרים את המדידה, ורק אז מרכזים. ממיינים, ומוציאים את הערך במיקום האחוזון
  3. חוזרים על אותו נוהל בשינוי תנאים: לפני/אחרי warmup, AC/סוללה, UI קדמי/ממוזער, עם/בלי עומס מ-processes אחרים (5.4)
  4. בכל שינוי בודד, לוקחים מדידה חדשה באותם תנאים ומשווים
// C# / .NET 8. הריכוז מתבצע אחרי שעוצרים את המדידה
using System.Diagnostics;

// ב-hot path רק כותבים למערך (אפס allocations)
long[] latenessTicks = new long[100_000];
int count = 0;

// דוגמה: בתוך ה-periodic loop
// latenessTicks[count++] = Stopwatch.GetTimestamp() - scheduledTimestamp;

static double PercentileMs(long[] ticks, int count, double percentile)
{
    long[] sorted = ticks.AsSpan(0, count).ToArray();
    Array.Sort(sorted);

    int index = (int)Math.Ceiling(percentile / 100.0 * count) - 1;
    index = Math.Clamp(index, 0, count - 1);

    return sorted[index] * 1000.0 / Stopwatch.Frequency;
}

// שימוש
// Console.WriteLine($"p50={PercentileMs(latenessTicks, count, 50):F3}ms");
// Console.WriteLine($"p99={PercentileMs(latenessTicks, count, 99):F3}ms");
// Console.WriteLine($"p99.9={PercentileMs(latenessTicks, count, 99.9):F3}ms");
// Console.WriteLine($"max={PercentileMs(latenessTicks, count, 100):F3}ms");

Stopwatch.Frequency הוא מספר המונים לשנייה, ולכן אם מחלקים ומכפילים ב-1000 מקבלים מילישניות. אם מספר המדגמים קטן מדי, ל-p99.9 אין משמעות. אם רוצים לדבר על p99.9, יש לאסוף לפחות 10,000 מדגמים (רצוי 100,000).

5.3. באילו כלים מסתכלים

מערך הכלים כבר קבוע בגדול.

  • מדידה פנימית באפליקציה קודם לוקחים בעצמכם period / lateness / execution time / queue depth / drop
  • ETW / WPR / WPA חופרים ב-CPU, context switch, DPC / ISR ו-page fault
  • LatencyMon מזהים חשוד בתנודה שמקורה ב-driver
  • ניטור טמפרטורה / clock בודקים את השפעת החום
מה מודדים ואיך מחליטיםמדידה פנימית באפליקציה נותנת התפלגות אחוזונים ומדדי miss, drop ועומק queue. ETW, WPR ו-WPA מגלים context switch, DPC, ISR ו-page fault. ניטור טמפרטורה ו-clock מצטרף אליהם כדי לקבוע סדר עדיפות לשיפור.מדידה פנימית באפליקציהp50 / p95 / p99 / p99.9 / maxmiss / drop / עומק queueETW / WPR / WPAcontext switch / DPC / ISR / page faultניטור טמפרטורה / clockקובעים סדר עדיפות לשיפור

איור 20: משווים בין תוצאות המדידה הפנימית, ETW וניטור הטמפרטורה, וקובעים לפיהן סדר עדיפות לשיפור.

עד להגעה ל-WPA זה קצת מייגע, אבל זה יעיל מאוד להבחין בין האם DPC / ISR הם הגורם, או שהעיבוד שלכם עצמו פשוט כבד.

איך משיגים ואופן שימוש מינימלי

כלי איך משיגים הנוהל המינימלי
WPR / WPA (Windows Performance Toolkit) מותקן כשבוחרים “Windows Performance Toolkit” בהתקנת Windows ADK (Windows Assessment and Deployment Kit). המיקום המובנה: C:\Program Files (x86)\Windows Kits\10\Windows Performance Toolkit פותחים שורת פקודה כ-Administrator, (1) מתחילים הקלטה עם wpr -start CPU, (2) מריצים את העיבוד הבעייתי כמה עשרות שניות, (3) שומרים עם wpr -stop trace.etl "חקירת latency מחזורי". אחר כך פותחים את trace.etl ב-WPA. שמות הפרופילים הזמינים ניתן לבדוק עם wpr -profiles
LatencyMon מורידים מהאתר של Resplendence Software. גרסת Home Edition לשימוש אישי היא בחינם מפעילים, מתחילים מדידה, ומריצים את העיבוד הרלוונטי כמה דקות. מתקבל ריכוז של השהיית ה-timer המקסימלית של ה-kernel, וגם זמני ISR / DPC לפי driver, ו-hard pagefault, כך שרושמים את ה-drivers שזמן הריצה שלהם בולט

מה שמסתכלים עליו קודם ב-WPA, מבין הגרפים הקשורים ל-CPU, הם זמן הריצה של DPC / ISR ו-context switch. מוצג “איזה driver החזיק את ה-CPU בזמן שה-thread שלכם לא יכול היה לרוץ”, ומשווים את זה לזמני ה-latency שתועדו ב-5.1.

המאמר הזה לא כולל צילומי מסך. המראה משתנה הרבה בין גרסאות, לכן מבצעים את השלבים למעלה ובודקים במסך האמיתי.

5.4. איך בודקים

בדיקה בסביבת benchmark שקטה בלבד לא מספיקה. כדאי לפחות לבדוק בנפרד את התנאים האלה.

  • לפני warmup, מיד אחרי startup
  • אחרי warmup
  • ריצה ארוכה ורציפה
  • UI קדמי
  • מצב קרוב ל-UI ממוזער / מוסתר
  • AC
  • סוללה
  • מצב עם עומס על רשת או דיסק

הערכה רק בסביבת benchmark נוטה לפספס בעיות שמופיעות בתפעול בפועל. מכיוון ש-ההתנהגות של Windows רגיל נמשכת אחרי אופן השימוש, חשוב לבדוק תוך התקרבות לתנאים בפועל.

למה בודקים בתנאים נפרדיםהערכה רק בסביבת benchmark שקטה מפספסת בעיות מהתפעול בפועל, ולכן בודקים בנפרד תנאים כמו לפני/אחרי warmup וריצה ארוכה, UI קדמי מול ממוזער, ו-AC מול סוללה ועומס.הערכה רק בסביבת benchmark שקטהמפספסים בעיות מהתפעול בפועלבודקים בתנאים נפרדיםwarmup וריצה ארוכהUI קדמי וממוזערתנאי חיבור לחשמל ועומס

איור 21: ההתנהגות של Windows רגיל נמשכת אחרי אופן השימוש. מעריכים תוך התקרבות לתנאי השימוש בפועל.

6. איך בוחרים בפועל

טבלת הקריאה המהירה לפי טווח מחזור הועברה ל-סעיף 1.1 כדי שתוכלו לחזור אליה תוך כדי קריאה. כאן משלימים שתי החלטות שהטבלה הזו לבדה לא קובעת.

מתי מחליטים ש”אי אפשר עם Windows רגיל”

כשמופיעה דרישה לשמור על פחות מ-1ms לאורך זמן ובעומס גבוה, לרוב מהיר יותר להחליט להעביר החוצה רק את החלק הרגיש לזמן, מאשר להמשיך לכוונן. מועמדי היעד הם firmware בצד ה-device, בקר ייעודי, FPGA, RTOS. חומר ההחלטה צריך להיות לא סובייקטיבי אלא המספרים מפרק 5.

  • למרות שאי אפשר לקצר עוד את ה-hot path, p99.9 וה-max ממשיכים לחרוג מהדרישה
  • הגורם הוא הפרעה חיצונית (DPC / ISR, driver, process אחר), ואמצעים בצד האפליקציה לא מספיקים (מאושר לפי ה-isolation בסעיף 4.7)
  • הדרישה היא לא “לא נשברים גם כשמפספסים מדי פעם” אלא “אסור לפספס אפילו פעם אחת”

כשרוצים לחיות עם הכול ב-process אחד

כשמחזיקים GUI, logging, תקשורת ו-DB באותה לולאה של אותו process, העבודה בשלב המאוחר שוברת את ה-deadline של השלב המוקדם. המתנה ל-flush של קובץ, חיבור מחדש ל-DB, וציור מחדש של UI, כולם יכולים להתארך בסדר גודל של עשרות ms. הרחבת ההפרדה בין fast path ל-slow path (4.2) עד לרמת ה-process, ולא רק ה-thread, היא גם אפשרות. כשמפרידים ל-processes, גם אם השלב המאוחר נתקע, המחזור של השלב המוקדם ממשיך לרוץ.

השוואה בין מבנה משותף להפרדת processesכשמחזיקים GUI, logging, תקשורת ו-DB באותה לולאה של אותו process, העבודה בשלב המאוחר שוברת את ה-deadline של השלב המוקדם. הרחבת הפרדת fast path/slow path לרמת ה-process שומרת על המחזור המוקדם גם כשהשלב המאוחר נתקע.מחזיקים הכול ב-process אחד ולולאה אחתהשלב המאוחר שובר את ה-deadline המוקדםהמתנה ל-flush, חיבור מחדש, ציור מחדשמפרידים בין השלב המוקדם למאוחר לפי processגם אם השלב המאוחר נתקע, המוקדם ממשיך לרוץ

איור 22: אם באמת נדרש לחיות יחד, יש אפשרות להרחיב את הפרדת fast path / slow path עד לרמת ה-process.

7. סיכום

יש שתי הנחות שכדאי לזכור.

  • ב-Windows רגיל היעד אינו hard real-time. היעד הוא soft real-time: מקטינים latency ו-jitter, ולא נשברים גם כשמפספסים deadline
  • מה שהכי משפיע הוא סידור ה-hot path, יותר מכיוונון priority

מבחינת מימוש, מה שעוזר הוא בערך זה.

  • מפרידים בין fast path ל-slow path
  • queue באורך קבוע, ומדיניות שהוחלטה מראש למקרה שהוא עולה על גדותיו
  • מודדים עם QPC, וממתינים עם event / waitable timer
  • נמנעים ב-hot path מ-allocation, I/O חוסם, ו-locks כבדים

מבחינת תפעול, מה שעוזר הוא בערך זה.

  • מריצים על AC
  • מפרידים הגדרות power לייצור
  • מפחיתים עומס רקע מיותר
  • מעריכים לפי p99 / p99.9 / max ומספר misses

soft real-time ב-Windows רגיל לא נקבע רק על ידי הגדרת priority. אם מטפלים בנפרד בתכנון, במימוש, בהגדרות power, במדידה ובתפעול, אפשר להגיע למערכת די יציבה.

8. מקורות

מאמרים עדכניים עם אותן תגיות, להעמקה בנושאים קרובים.

העמודים האלה ממקמים את הנושא בהקשר רחב יותר של שירותים והחלטות.

המאמר קשור ישירות לשירותים הבאים.

שאלות נפוצות

שאלות נפוצות בפניות בנושא המאמר.

אפשר לעשות real-time processing ב-Windows?
אי אפשר להבטיח hard real-time (אפס deadline misses). אבל אם מתכננים, מממשים, מודדים ומפעילים כמו שצריך, אפשר להגיע למצב די מעשי כ-soft real-time. periodic processing של כמה ms עד כמה עשרות ms, audio/video שמבוססים על buffer, קריאה מחיישנים ו-control loops — כל אלה ריאליים גם ב-Windows 10/11 רגיל. אם לעומת זאת צריך אפס deadline misses, או יציבות לאורך זמן מתחת למאות מיקרו-שניות, כדאי להעביר את זה ל-RTOS, בקר ייעודי, FPGA, או עיבוד בצד ה-device.
למה אסור לבסס periodic loop על Sleep(1)?
כי Sleep(1) אינו "מחזור של 1ms". הוא ממתין בערך 1ms ומעלה, ואז מוסיפים מעל זה את זמן העיבוד. overshoot של ההמתנה מצטבר כמו שהוא. periodic loop יציב יותר כשהוא רץ לפי absolute deadline עם next += period; להמתנה מעדיפים device event או waitable timer ברזולוציה גבוהה, ורק את ה-fine-tune האחרון מגבילים ל-busy-spin קצר מאוד. את timeBeginPeriod קוראים רק כל עוד צריך, ואחר כך מחזירים.
מה הכי עוזר להקטין latency ו-jitter?
לא להעלות priority. לקצר את ה-hot path, להפוך אותו ל-fixed-size, ולשמור אותו non-blocking. מפרידים fast path (acquisition ובקרה) מ-slow path (שמירה, תקשורת, UI) ומחברים ביניהם ב-queue באורך קבוע. ב-hot path נמנעים מכתיבה לקובץ, שליחה ברשת, logging כבד, ו-allocation בכל איטרציה. בצד התפעול: AC power, בדיקת power mode, EcoQoS, וצמצום עומס ברקע.
באילו מדדים מעריכים יציבות של periodic processing?
לא רק בממוצע. מסתכלים על p99, p99.9, max, ומספר ה-deadline misses. למשל ממוצע 0.8ms עם max של 28ms אומר: בדרך כלל מהיר, אבל מדי פעם יש spike גדול. ב-Windows רגיל הבעיה האמיתית יושבת בזנב, מ-p99 עד max. במקביל מתעדים גם DPC/ISR spikes, page fault, עומק queue, ותנודות טמפרטורה/clock, ומעריכים בנפרד warmup לפני/אחרי, ריצה ארוכה, מצב ממוזער, וסוללה.

פרופיל הכותב

עמוד היכרות עם כותב המאמר.

Go Komura

מנהל KomuraSoft LLC

מתמחה בפיתוח תוכנה עבור Windows, ייעוץ טכני וחקירת תקלות, בעיקר בפרויקטים עם מערכות קיימות ובאגים שקשה לשחזר.

קישורים ציבוריים

חזרה לבלוג