وبلاگ

توان عبوری به ازای هر چرخهٔ CPU: عددی که در برخورد با یک روتر هم زنده می‌ماند

یک عدد آزمایشگاهی و یک عدد سخت‌افزاری تقریباً هرگز با هم موافق نیستند. ما هر دو را اندازه گرفتیم، بازه‌ها را نگه داشتیم، تله‌ای را که یک‌بار نتیجه‌ای غلط تولید کرده بود فاش کردیم، و صریح گفتیم کجا باختیم.

تاریخ انتشار: ۱۵ شهریور ۱۴۰۵

روی دو روتر فیزیکی، با جابه‌جایی ترافیک واقعی روی یک LAN گیگابیتی واقعی، هستهٔ تونل ما 122 Mbit/s را رد کرد، جایی که پیاده‌سازی تونل کرنلِ خودِ روتر فقط 66.8 Mbit/s را رد کرد — روی همان فرستنده، با CPU کمتر به‌ازای هر مگابیت. هیچ‌کدام از این دو عدد به‌تنهایی تمام داستان نیست: دو بازو مجبور بودند از تولیدکننده‌های ترافیک متفاوتی استفاده کنند، چون هیچ تولیدکنندهٔ واحدی روی هر دو طرف آن مقایسه اجرا نمی‌شود. پس نسبتی که باید با آن بحث کرد این است که هر بازو را در برابر سقفِ بدون‌تونلِ خودش بسنجیم، که در جدول زیر آمده و بیشتر، نه کمتر، به نفع ماست. یک اجرای آزمایشگاهیِ چهار ماشین مجازی روی یک لپ‌تاپ چیزی سریع‌تر در مقیاس مطلق می‌گوید و، روی یک محور، چیزی کمتر چاپلوسانه. ما به عدد روتر بیشتر اعتماد داریم، چون روتر تنها موردی از این دو است که ترافیک واقعی یک مشتری واقعاً از آن عبور می‌کند.

این همان یافته‌ای است که ارزش پیشتازی دارد، و همچنین دلیل اینکه این نوشته دو بخش نتیجه دارد، نه یکی: آزمایشگاه و سخت‌افزار سر اینکه کدام محور اهمیت دارد اختلاف‌نظر دارند، و رویهم‌گذاشتن این اختلاف بیشتر از هرکدام از دو عدد به اعتبار ما لطمه می‌زد.

پیش از دیدن اعداد این را بخوانید. دیتاپلین ما به‌طور پیش‌فرض روی یک ترد اجرا می‌شود. مقایسه‌کننده‌ای که در آزمایشگاه کنارش اجرا کردیم به‌طور پیش‌فرض روی چهار ترد اجرا می‌شود. اگر برابر نشوند، همین یک پرچم به‌تنهایی توان عبوری آن را از حدود 789 به 1427 Mbit/s تغییر می‌دهد — شکافی در پیش‌فرض‌ها، نه در دیتاپلین‌ها — و همین پیش‌تر یک عدد نادرست را در همین‌جا منتشر کرده بود. هر عدد آزمایشگاهی در ادامه، هر دو هستهٔ Rust را روی یک ترد پین کرده است. پیاده‌سازی‌های Go و کرنل در آن جدول با پیش‌فرض‌های خودشان اجرا شدند، که بیش از یک ترد استفاده می‌کنند — اگر تأثیری داشته باشد، همین آن‌ها را در برابر تک‌تردِ ما بهتر نشان می‌دهد. شکافِ پیش‌فرض‌های همان‌طور-که-عرضه-شده هم یک‌بار، جداگانه نشان داده شده، تا این هم با حذف‌کردن پنهان نشود.

چه چیزی، و روی چه چیزی اندازه‌گیری شد

دو محیط مستقل، چون یک بنچمارک فقط درون همان محیطی که در آن اجرا شده صادق است — عددی از iperf3 و عددی از bandwidth-test یک عدد با دو اسم نیستند، و یک VM که هسته‌های یک لپ‌تاپ را با بقیه سهیم است، یک روتر نیست.

سخت‌افزار — دو روتر MikroTik، یک LAN گیگابیتی، 2026-09-01. RB3011UiAS (ARMv7، 2×1400 مگاهرتز) به‌عنوان فرستنده، cAP ax (ARM64، 4×864 مگاهرتز) به‌عنوان گیرنده، RouterOS 7.24.1 روی هر دو. هستهٔ ما به‌شکل fv، در یک کانتینر RouterOS، در فضای کاربر، در برابر پیاده‌سازی درون‌کرنلیِ خودِ روتر از پروتکل WireGuard اجرا شد — تنها مقایسه‌کنندهٔ سیب‌به‌سیب موجود روی آن سخت‌افزار، چون تنها گزینهٔ جایگزینی است که خودِ RouterOS عرضه می‌کند. دو تولیدکننده، چون هیچ‌کدام پیاده‌سازی‌ها را تمیز از هم عبور نمی‌دهد: dd | nc برای اجرای کانتینر ما، bandwidth-test خودِ RouterOS برای اجرای کرنل — هرکدام سقفِ بدون‌تونلِ خودش را به‌عنوان یک کنترل حمل می‌کند، تا خواننده پیش از اعتماد به نسبت، بتواند آن سقف را عقلانی بررسی کند.

آزمایشگاه — چهار مهمان Debian با معماری aarch64 زیر QEMU/HVF روی یک میزبان Apple-silicon، 2026-08-31. همان کلیدهای جفت، آدرس‌ها، پورت‌ها، MTU و راه‌اندازی رابط در تمام باینری‌های آزموده‌شده یکسان است: هستهٔ ما، boringtun 0.7 (هستهٔ فضای‌کاربریِ که هستهٔ ما از آن فورک شده)، wireguard-go (پیاده‌سازی مرجع پروتکل WireGuard، در فضای کاربر)، و ماژول خودِ پروتکل WireGuard در کرنل Linux. اجراهای 15 ثانیه‌ای iperf3، سه نمونه برای هرکدام، هر دو هستهٔ Rust برای دلیلی که در کادر بالا آمده روی یک ترد دیتاپلین پین شده‌اند.

task test:impls    # lab: ours vs boringtun vs wireguard-go vs the kernel
task test:all      # lab: single-hop, multi-hop, and the comparison

اجرای روتر هنوز یک هدف task نیست — fv است که روی هر جعبه دستی در یک کانتینر RouterOS اجرا شده، در برابر سرور توکار پروتکل WireGuard در RouterOS. توپولوژی کامل و هر ردیف خام در docs/benchmarks/results.csv خودمان قرار دارد.

عددی که اهمیت دارد: روی روتر

معیارهستهٔ ما (فضای کاربر)تونل کرنلِ خودِ روتر
توان عبوری122 Mbit/s66.8 Mbit/s
CPU، فرستنده65% (63–67)74% (70–79)
CPU، گیرنده18% (10–22)20% (18–21)
CPU به‌ازای هر Mbit0.53 %/Mbit1.10 %/Mbit
RTT1.22 ms0.92 ms
حافظهٔ مقیم (فرستنده / گیرنده)3.0 / 3.1 MBدرون‌کرنل، غیرقابل‌تفکیک
تولیدکنندهdd | ncbandwidth-test
سقفِ بدون‌تونلِ همان تولیدکننده187 Mbit/s220.5 Mbit/s
حفظ‌شده در برابر سقف خودش65%30%
هستهٔ ما122 Mbit/sتونل کرنل روتر66.8 Mbit/s

طول میله متناسب با Mbit/s است، محور 0 تا 140. فرستندهٔ RB3011 در هر دو اجرا یکسان است؛ در کل، سقفِ CPU همین است، بین 65 تا 79% درحالی‌که cAP ax گیرنده نزدیک 20% بی‌کار است.

دو چارچوب مستقل با هم موافق‌اند، و همین است که نتیجه را باورپذیر می‌کند، نه گلچین‌شده: توان عبوری مطلق 1.8 برابر به نفع هستهٔ ماست، و توان عبوری حفظ‌شده در برابر سقفِ بی‌چون‌وچرای خودِ هر تولیدکننده 2.2 برابر به نفع آن است. چیزی که جدول نمی‌گوید: دو تولیدکننده ابزار یکسانی نیستند، به همین دلیل هر دو سقفِ بدون‌تونل چاپ شده‌اند نه یک نسبتِ تیتری واحد، و اگر فرستنده به‌جای تولید محلی، ترافیک واقعی شبکه دریافت می‌کرد، هر دو عدد تغییر می‌کرد. برخلاف جدول آزمایشگاهی زیر، این‌ها اجراهای تکی هستند: درصدهای داخل پرانتز بازه‌ای است که CPU طی همان اجرا در آن گشته، نه پراکندگی روی آزمایش‌های تکرارشده.

آزمایشگاه: سه هستهٔ فضای کاربر، یک کرنل، تردهای برابرشده

هستهنوعTCP Mbit/s (بازه، n=3)اوج حافظهتأخیر p99
هستهٔ ما، 1 تردفضای کاربر، Rust1535 (1506–1554)3.5 MB0.94 ms
boringtun، 1 تردفضای کاربر، Rust1427 (1346–1479)5.3 MB1.15 ms
wireguard-go، پیش‌فرضفضای کاربر، Go1228 (1221–1239)34.8 MB1.39 ms
WireGuard کرنل، پیش‌فرضکرنل، C1376 (1366–1386)— (کرنل، غیرقابل‌تفکیک)0.96 ms

همان‌طور که عرضه می‌شود، بدون برابرسازی: هستهٔ ما به‌طور پیش‌فرض روی 1 ترد است و 1535 Mbit/s اجرا شد؛ boringtun به‌طور پیش‌فرض روی 4 ترد است و در آن تنظیم ~789 Mbit/s اجرا شد. گزارش‌کردن فقط عدد پیش‌فرض-در-برابر-پیش‌فرض همان اشتباه را در جهت چاپلوسانه تکرار می‌کرد. هر دو عدد واقعی‌اند؛ فقط یکی از آن‌ها دیتاپلین را از شمار ترد جدا می‌کند.

هزینهٔ CPU همان‌جایی است که آزمایشگاه جایگاه خودش را در این نوشته ثابت می‌کند — این تنها محوری است که جدول روتر نمی‌تواند نشان دهد، چون مقایسه‌کنندهٔ درون‌کرنلی یک روتر هیچ تفکیک CPUای برای مقایسه در اختیار نمی‌گذارد.

کاربرسیستمهستهٔ ما0.90 s/Gbit totalکاربر 0.347 · سیستم 0.549 s/Gbitboringtun1.08 s/Gbit totalکاربر 0.290 · سیستم 0.787 s/Gbitwireguard-go2.37 s/Gbit totalکاربر 0.876 · سیستم 1.490 s/Gbit

هرچه کمتر بهتر است. محور 0 تا 2.5 s/Gbit، خطی. هر دو بخش از چپ به راست روی هم چیده شده‌اند؛ محل جداشدن آن‌ها همان‌جایی است که بخش بعدی از باخت می‌گوید.

هستهٔ ما3.5 MBboringtun5.3 MBwireguard-go34.8 MB

مقیاس لگاریتمی، 1 تا 50 MB، به این دلیل بیان شده که پیش‌فرضِ صادقانه نیست: روی یک محور خطی، عدد 34.8 مگابایتیِ wireguard-go دو میلهٔ دیگر را به خط‌های نازک تخت می‌کرد.

جایی که برنده نشدیم

دو جا، که هر دو توضیح داده شده‌اند، نه اینکه در CSV دفن شده باشند:

CPU حالت کاربر، در آزمایشگاه. 0.347 s/Gbit در برابر 0.290 مقایسه‌کنندهٔ ارزان‌تر Rust — تنها ستون آن جدول که در آن از ما جلوتر است. برداشت ما این است که این هزینه‌ای خریداری‌شده است، نه هدررفته: هستهٔ ما offload چک‌سام TUN را در هر دو جهت انجام می‌دهد، که کار را از کرنل بیرون می‌برد و به CPU فضای کاربر منتقل می‌کند که به‌عنوان زمان کاربر حساب می‌شود — و ستون CPU سیستم، جایی که آن معامله بازپرداخت می‌شود، به‌راحتی جلوتر است (0.549 در برابر 0.787). CPU کل هنوز برنده است. تفکیک کاربر/سیستم بالا دقیقاً برای همین نشان داده شده تا این معامله دیده شود، نه فقط ادعا شود.

زمان رفت‌وبرگشت، روی روتر. 1.22 ms در برابر 0.92 ms تونل کرنل — تنها محور سخت‌افزاری‌ای که در آن پیاده‌سازی درون‌کرنل کاملاً برنده است، به دلیلی معمولی: مسیر ما هزینهٔ یک هاپ veth کانتینر و یک نوبت زمان‌بندی فضای کاربر را می‌پردازد که یک مسیر درون‌کرنل نمی‌پردازد. جالب اینکه آزمایشگاه در تأخیر p99 ترتیب برعکس نشان می‌دهد (0.94 ms در برابر 0.96 ms کرنل) — ما از این چیزی فراتر از «محیط‌های متفاوت، گلوگاه‌های متفاوت» برداشت نمی‌کنیم، و همین را می‌گوییم، به‌جای انتخاب هر عددی که بیشتر چاپلوسانه باشد.

چه چیزی تازه است و چه چیزی به‌ارث رسیده

هر عدد بالا تاریخی دارد، و این تاریخ‌ها اهمیت دارند: جدول روتر مربوط به 2026-09-01 است، جدول آزمایشگاه 2026-08-31. از آن زمان، یک ارتقای نسخهٔ اصلیِ هماهنگ، crateهای رمزنگاری زیرین که دیتاپلین ما روی هر بسته صدا می‌زند را جابه‌جا کرده — chacha20poly1305، blake2، curve25519-dalek — و ما هنوز مقایسهٔ کامل آزمایشگاه یا روتر را روی آن درخت دوباره اجرا نکرده‌ایم. اعدادِ بالا واقعی‌اند و با دقت اندازه‌گیری شده‌اند، اما در زمان انتشار شش روز عمر دارند، روی درختی که پیش از آن ارتقا بوده است. ما آن‌ها را با گفتنِ همین موضوع منتشر می‌کنیم، نه بی‌صدا، و وقتی اجرای دوباره‌ای داشته باشیم تاریخش را می‌زنیم. عدد روتر هم در سوابق خودمان به یک commit مشخص پین نشده — «2026-09-01» را «اوایل سپتامبر» بخوانید، نه وابسته به یک تغییر بهینه‌سازی خاص.

چیزی که امروز، روی همین دستگاه، بدون روتر یا آزمایشگاه VM می‌توانیم دوباره اجرا کنیم، همان بخشی از مسیر است که ارتقای رمزنگاری مستقیم‌ترین اثر را روی آن گذاشته: هزینهٔ encapsulate و decapsulate، جدا از هر دغدغهٔ سوکت، TUN و تردسازی که در بالا آمد.

عملیات، اندازهٔ بستههستهٔ ماboringtun 0.7.1
encapsulate، 64 B219.2 ns218.7 ns
encapsulate، 512 B436.4 ns442.5 ns
encapsulate، 1420 B938.1 ns968.3 ns
decapsulate، 64 B238.8 ns226.7 ns
decapsulate، 512 B562.1 ns539.2 ns
decapsulate، 1420 B1099.5 ns1069.4 ns

اجرای دوباره در 2026-09-05، روی Apple M3 Max، با cargo bench -p forestvpn-quantum --bench datapath_benches -- --measurement-time 3 --warm-up-time 1، criterion 0.8.2، 100 نمونه به‌ازای هر نقطه. بدون سوکت، بدون TUN، بدون ترد — یک دست‌دهی کامل‌شده، سپس encapsulate/decapsulate در یک حلقه. نتایج بسته به جهت و اندازهٔ بسته مخلوط‌اند، همه در چند درصدِ هر سو، که دقیقاً همان چیزی است که انتظارش را داشتیم: هستهٔ ما این مسیر را از بالادستِ خودش فورک کرده، و هر دو اکنون روی همان نسل از primitiveهای RustCrypto نشسته‌اند. این ادعای توان عبوری نیست — این می‌گوید خودِ مسیر رمزنگاریِ فورک‌شده، در محدودهٔ نویز، تقریباً همان هزینه‌ای را دارد که بالادست دارد، و همین است که برد‌های آزمایشگاه و روتر بالا را قابل‌اعتماد می‌کند: آن‌ها از جای دیگری در پشته می‌آیند (دسته‌بندی syscall، offload، تردسازی)، نه از یک رمزنگاری سریع‌تر.

بازتولید نتایج

task test:impls          # lab: ours vs boringtun vs wireguard-go vs the kernel
task test:all            # lab: single-hop, multi-hop, and the multihop cost table
cargo bench -p forestvpn-quantum --bench datapath_benches -- --measurement-time 3 --warm-up-time 1

اجرای روتر امروز دستی است: fv در یک کانتینر RouterOS روی هر جعبه، در برابر سرور خودِ پروتکل WireGuard در RouterOS.

صادقانه بگوییم این چه چیزی به شما می‌دهد: هارنس و results.csv خام پشتِ هر جدول اینجا در درخت خودمان است، که امروز عمومی نیست. دستورهای بالا را بیانی دقیق از آنچه ما اجرا کردیم بخوانید، نه چیزی که بتوانید کلون کنید و تکرار کنید. کم‌وابسته‌ترین ادعا به ما، همان ارزش بررسی‌کردنِ بیشتری هم دارد — boringtun و wireguard-go عمومی‌اند، iperf3 عمومی است، و تلهٔ شمار ترد در بالای این نوشته (یک پرچم که یک مقایسه‌کننده را روی یک میزبان بین حدود 789 و 1427 Mbit/s جابه‌جا می‌کند) با همان سه ابزار و بدون هیچ کدی از ما در مسیر، توسط هرکسی قابل بازتولید است. اگر برای شما هم درست از آب درآمد، همین ردیف‌های برابرشدهٔ بالا موضوع بحث‌اند. اگر درنیامد، دوست داریم بشنویم.

همهٔ نوشته‌ها

فارسی
هر سؤالی دارید بپرسید