پولار سیگنالز فلیمگراف رو از SVG به Canvas برد
خلاصهٔ کاملتر
تیم پولار سیگنالز دربارهٔ بازنویسی داشبورد پروفایلینگش نوشته و اینبار با عدد. به گفتهٔ خودشون این اولینبار نیست که سراغ سریعتر کردن فلیمگراف رفتن: سال قبل رندر SVG رو موازی کرده بودن و اولین رندر یک کوئری بزرگ رو از حدود هشت ثانیه به تقریباً ۹۱ میلیثانیه رسونده بودن. ولی SVG یک سقف داره و اون سقف از نودهای DOM ساخته شده.
در فلیمگراف SVG هر فریم از یک rect و یک text ساخته میشه، پس یک پروفایل بزرگ یعنی دهها هزار نود DOM که مرورگر باید برای هرکدوم استایل و لِیاوت و پینت انجام بده و تو درختی نگهشون داره که بعداً باید جمعشون کنه. رندرر قدیمی برای اینکه تب فریز نشه نودها رو دستههای حدوداً ۵۰۰تایی در هر فریم میکشید — که به گفتهٔ نویسنده فقط درمان نشونه بود، نه خودِ مشکل که همون سپردن حجم عظیمی از ساختار به مرورگره.
تفاوت بنیادی رو اینطور توضیح میدن: SVG و DOM از نوع retained-mode هستن، یعنی مرورگر درختی از المانها رو تحویل میگیره و مالکش میشه؛ هر تغییر، محاسبهٔ استایل و لِیاوت رو حتی برای بقیهٔ درخت مجبور میکنه. Canvas اما immediate-mode هست: درختی وجود نداره، فقط یک context نقاشیه و دستوری مثل fillRect میفرستی، مرورگر پیکسل میذاره و فراموشش میکنه. چیزی برای garbage collector نمیمونه و هزینه با تعداد فریمی که واقعاً میکشی مقیاس میگیره، نه تعداد فریمی که مرورگر باید یادش بمونه.
نیمهٔ دیگهٔ ماجرا سمت دادهست. از وقتی کوئرییر و دیتابیس رو با Rust بازنویسی کردن، فلیمگراف خروجی یک فانکشن قابلترکیب در SQL شده: کوئرییر سمپلها رو میگرده، درخت رو میسازه، موقعیت و عرض هر فریم رو حساب میکنه و نتیجه رو بهصورت Arrow ستونی و آمادهٔ رندر استریم میکنه. ستونی بودن یعنی همهٔ xها در یک آرایه، همهٔ عرضها در یکی دیگه و همهٔ عمقها در سومی — پس خوندنشون شبیه رفتن پایینِ یک لیسته، نه باز کردن یک آبجکت برای هر فریم.
این رکوردها با Arrow Flight و در فرمت IPC میرسن و چون چیدمان Arrow روی سیم با چیدمانش در حافظه یکیه، یک فانکشن tableFromIPC بدون پارس و بدون کپی یک تیبل قابلاستفاده تحویل میده. همین zero-copy باعث میشه حلقهٔ نقاشی تقریباً بیهزینه بشه، چون درخت و لِیاوت قبل از رسیدن داده ساخته شدن. کالینگ ویوپورت هم حفظ شده: اگه پروفایلی ۵۰٬۰۰۰ فریم داشته باشه و فقط ۸۰۰تاش تو ناحیهٔ دیدهشده باشه، حدود ۸۰۰ دستور نقاشی صادر میشه نه ۵۰٬۰۰۰.
اسکرول و زوم هم به دو دلیل ارزون شدن. اول اینکه کل گراف هیچوقت یکجا کشیده نمیشه؛ کانواس فقط کمی بزرگتر از پنجرهٔ دیدهشدهست و موقع اسکرول بیشتر وقتها پیکسلهای کشیدهشده جابهجا میشن و فقط وقتی از حاشیه بگذری یک برش تازه کشیده میشه. دوم اینکه زوم هیچچیزی رو بازنمیسازه: موقعیت و عرض هر فریم یکبار در کوئری حساب شده، پس زوم فقط برشی باریکتر از همون اعداد ثابت رو روی کانواس تصویر میکنه.
اندازهگیری رو سهجوره انجام دادن — اتوماسیون مرورگر، ریپلی فیکسچر و تریس DevTools — و عددی که بیشتر بهش اعتماد دارن از ریپلی فیکسچر میآد: روی پروفایلی با حدود ۱۷ هزار فریم، میانهٔ SVG ۱۶۴ میلیثانیه و کانواس ۴۶ میلیثانیه، یعنی حدود ۳.۶ برابر سریعتر. روی صفحهٔ واقعی زوم از ۱۲۱ به ۲۹، اسکرول از ۳۰ به ۶ و ریست زوم از ۵۲۸ به ۴۷ میلیثانیه رسیده. خودشون تأکید میکنن عدد بزرگتر صفحهٔ زنده بهخاطر رقابت رندرر قدیمی روی تکترد اصلیه و ۳.۶ برابر رقم منصفانه و تکرارشدنیه.
قیمت این سرعت اینه که مرورگر دیگه کاری برات نمیکنه. هیتتستینگ رو خودشون انجام میدن: ردیف از تقسیم y ماوس بر ارتفاع ردیف درمیآد و یک اسکن سریع همون ردیف، فریم زیر نشانگر رو پیدا میکنه. :hover وجود نداره، پس فریم هاور رو خودشون ترک میکنن و تولتیپ رو روی کانواس میکشن. text-overflow: ellipsis هم نیست، پس هر لیبل رو خودشون کوتاه میکنن و برای صفحهٔ High-DPI بکینگاستور رو با devicePixelRatio مقیاس میدن. یک باخت رو هم صریح پذیرفتن: کانواس برای اسکرینریدر ماته و متنش انتخابشدنی نیست.
نکات کلیدی:
- SVG برای فلیمگراف سقف داره، چون هر فریم یک نود DOM با هزینهٔ استایل و لِیاوت و پینته
- Canvas از نوع immediate-mode هست: دستور نقاشی میگیره، پیکسل میده و چیزی نگه نمیداره
- موقعیت و عرض فریمها در کوئری حساب و بهشکل Arrow ستونی و zero-copy تحویل داده میشه
- در تست ایزوله ۱۶۴ به ۴۶ میلیثانیه رسید؛ ریست زوم روی صفحهٔ واقعی از ۵۲۸ به ۴۷
- هزینهاش: هیتتست و تولتیپ و کوتاه کردن متن دستی میشه و دسترسپذیری آسیب میبینه




