چرا tsgo اینقدر حافظه مصرف میکنه؟
خلاصهٔ کاملتر
نویسنده با یه مشاهدهی ساده شروع میکنه: اگه tsgo (نسخهی Go کامپایلر TypeScript) رو روی یه پروژهی نسبتاً بزرگ اجرا کنی، دیدن مصرف چند گیگابایتی حافظه عجیب نیست. سؤال اینه که چرا؟ جواب کوتاهش به نحوهی چندتردی شدن tsgo برمیگرده.
اول یه تحلیل از heap انجام میده. با ابزار pprof گو، یه پروژهی بزرگ Next.js با Zod، tRPC و Drizzle (حدود ۷ هزار فایل .ts) رو بررسی میکنه. توی حالت چندتردی، حدود ۴۵ درصد حافظه برای گرههای AST مصرف میشه که برای یه کامپایلر طبیعیه و کاریش نمیشه کرد، چون این گرهها باید تا آخر اجرا زنده بمونن. ولی چیزی که نظرش رو جلب میکنه، حافظهی تایپچکره: حدود ۴۰۰ مگابایت یا ۳۰ درصد.
نکتهی کلیدی اینجا روشن میشه: وقتی tsgo رو با --singleThreaded اجرا میکنه، مصرف تایپچکر از حدود ۴۰۰ مگابایت به فقط ۵۰ مگابایت میافته. این تفاوت بزرگ نشون میده سربار اصلی از چندتردی میاد.
دلیلش هم اینه که tsgo برای هر ترد یه Checker جدا توی یه استخر (pool) میسازه و هر Checker کل برنامه و فایلها رو میگیره. مشکل اینجاست که هر چکر state مخصوص خودش رو داره و این داده بین تردها به اشتراک گذاشته نمیشه، چون همگامسازیش بین تردها پرهزینهست. در نتیجه کار تکراری انجام میشه:
- فایل a.ts میره به Checker 1 و یه عالمه تایپ میسازه.
- فایل b.ts که از a.ts تایپ import میکنه میره به Checker 2.
- Checker 2 چون state جداگانه داره، مجبوره دادههای a.ts رو دوباره محاسبه و دوباره تخصیص بده.
هر Checker کلی map و cache برای انواع تایپها داره (مثل tupleTypes، unionTypes، stringLiteralTypes و خیلی بیشتر) و این حافظه فقط مال همون یه چکره. بدتر اینکه تایپهای ساختهشده هیچوقت آزاد نمیشن. نویسنده با یه تست ساده اینو ثابت میکنه: یه تایپ جنریک بازگشتی به اسم BuildTuple که یه tuple با ۱۰۰ عضو میسازه و هر مرحلهی بازگشت یه تایپ جدید ساخته و برای همیشه cache میشه.
نتیجهی تست گویاست. با یه چکر، ۱۰۲ تا tupleType و ۱۰۱ تا numberLiteralType ساخته میشه. ولی با ۴ چکر، همون تایپها روی هر چهار ترد تکرار میشن و به ۴۰۸ و ۴۰۴ تا میرسن. این یعنی دو چیز: تایپها روی تردهای مختلف بهصورت افزونه ساخته میشن، و یه تایپ جنریک بازگشتی میتونه کلی تایپ گذرا (transient) بسازه که حافظه میخورن. حالا این میزان تکرار رو روی هزاران فایل واقعی تصور کن.
نکات کلیدی:
- tsgo روی پروژههای بزرگ راحت چند گیگابایت حافظه مصرف میکنه
- بخش بزرگی از حافظه برای گرههای ASTـه که طبیعیه و اجتنابناپذیر
- سربار اصلی تایپچکر از چندتردی میاد؛ تکترد مصرفش از ۴۰۰ به ۵۰ مگابایت میافته
- هر ترد یه Checker جدا با state مستقل داره، پس کار و حافظه تکرار میشه
- تایپهای ساختهشده هیچوقت آزاد نمیشن و تایپهای جنریک بازگشتی اوضاع رو بدتر میکنن




