مصاحبه عالی با کلیف کلیک، پدر کامپایل JIT در جاوا

مصاحبه عالی با کلیف کلیک، پدر کامپایل JIT در جاواکلیف کلیک کنید - CTO Cratus (حسگرهای IoT برای بهبود فرآیند)، بنیانگذار و هم بنیانگذار چندین استارتاپ (از جمله Rocket Realtime School، Neurensic و H2O.ai) با چندین خروج موفق. کلیف اولین کامپایلر خود را در سن 15 سالگی نوشت (پاسکال برای TRS Z-80)! او بیشتر به خاطر کارش بر روی C2 در جاوا (دریای گره های IR) شناخته شده است. این کامپایلر به دنیا نشان داد که JIT می تواند کدهای باکیفیت تولید کند که یکی از عوامل ظهور جاوا به عنوان یکی از اصلی ترین پلتفرم های نرم افزاری مدرن بود. سپس کلیف به Azul Systems کمک کرد تا یک مین‌فریم 864 هسته‌ای با نرم‌افزار جاوا خالص بسازد که از مکث GC در یک پشته 500 گیگابایتی در عرض 10 میلی‌ثانیه پشتیبانی می‌کند. به طور کلی، کلیف موفق شد روی تمام جنبه های JVM کار کند.

 
این هابراپست یک مصاحبه عالی با کلیف است. در مورد موضوعات زیر صحبت خواهیم کرد:

  • انتقال به بهینه سازی های سطح پایین
  • چگونه یک بازسازی بزرگ انجام دهیم
  • مدل هزینه
  • آموزش بهینه سازی سطح پایین
  • نمونه های عملی بهبود عملکرد
  • چرا زبان برنامه نویسی خود را بسازید؟
  • شغل مهندس عملکرد
  • چالش های فنی
  • کمی در مورد تخصیص رجیستر و چند هسته
  • بزرگترین چالش زندگی

مصاحبه توسط:

  • آندری ساتارین از خدمات وب آمازون. در حرفه خود، او موفق شد در پروژه های کاملاً متفاوتی کار کند: او پایگاه داده توزیع شده NewSQL را در Yandex، یک سیستم تشخیص ابر در آزمایشگاه کسپرسکی، یک بازی چند نفره در Mail.ru و یک سرویس برای محاسبه قیمت ارز در دویچه بانک آزمایش کرد. علاقه مند به آزمایش سیستم های باطنی و توزیع شده در مقیاس بزرگ.
  • ولادیمیر سیتنیکوف از Netcracker. ده سال کار روی عملکرد و مقیاس پذیری سیستم عامل NetCracker، نرم افزاری که توسط اپراتورهای مخابراتی برای خودکارسازی فرآیندهای مدیریت تجهیزات شبکه و شبکه استفاده می شود. علاقه مند به مسائل مربوط به عملکرد پایگاه داده جاوا و اوراکل. نویسنده بیش از ده ها بهبود عملکرد در درایور رسمی PostgreSQL JDBC.

انتقال به بهینه سازی های سطح پایین

اندرو: شما نام بزرگی در دنیای کامپایل JIT، جاوا و به طور کلی کار اجرایی هستید، درست است؟ 

صخره: مثل اونه!

اندرو: اجازه دهید با چند سوال کلی در مورد کار اجرایی شروع کنیم. نظر شما در مورد انتخاب بین بهینه سازی های سطح بالا و سطح پایین مانند کار در سطح CPU چیست؟

صخره: بله، اینجا همه چیز ساده است. سریع ترین کد کدی است که هرگز اجرا نمی شود. بنابراین، شما همیشه باید از یک سطح بالا شروع کنید، روی الگوریتم ها کار کنید. یک نماد O بهتر، نماد O بدتر را شکست می دهد، مگر اینکه برخی از ثابت های به اندازه کافی بزرگ دخالت کنند. چیزهای سطح پایین آخرالزمان می شوند. به طور معمول، اگر بقیه پشته خود را به اندازه کافی بهینه کرده اید و هنوز چیزهای جالبی باقی مانده است، سطح پایینی است. اما چگونه از یک سطح بالا شروع کنیم؟ چگونه می دانید که کارهای سطح بالا به اندازه کافی انجام شده است؟ خب... به هیچ وجه. هیچ دستور العمل آماده ای وجود ندارد. شما باید مشکل را درک کنید، تصمیم بگیرید که قرار است چه کاری انجام دهید (تا در آینده اقدامات غیر ضروری انجام ندهید) و سپس می توانید نمایه ساز را کشف کنید، که می تواند چیز مفیدی بگوید. در یک نقطه، شما خودتان متوجه می شوید که از شر چیزهای غیر ضروری خلاص شده اید و زمان آن رسیده است که تنظیم دقیق سطح پایین انجام دهید. این قطعا نوع خاصی از هنر است. افراد زیادی هستند که کارهای غیر ضروری انجام می دهند، اما آنقدر سریع حرکت می کنند که زمانی برای نگرانی در مورد بهره وری ندارند. اما این تا زمانی است که این سوال به صراحت مطرح شود. معمولاً در 99 درصد مواقع هیچ کس اهمیتی نمی دهد که من چه کار می کنم، تا لحظه ای که یک چیز مهم در مسیر بحرانی پیش می آید که هیچ کس به آن اهمیت نمی دهد. و در اینجا همه شروع به ناله کردن شما می کنند که "چرا از همان ابتدا بی نقص کار نکرد." به طور کلی، همیشه چیزی برای بهبود عملکرد وجود دارد. اما در 99 درصد مواقع هیچ سرنخ ندارید! شما فقط سعی می کنید کاری را انجام دهید و در این فرآیند متوجه می شوید که چه چیزی مهم است. شما هرگز نمی توانید از قبل بدانید که این قطعه باید کامل باشد، بنابراین، در واقع، شما باید در همه چیز بی نقص باشید. اما این غیر ممکن است و شما آن را انجام نمی دهید. همیشه چیزهای زیادی برای اصلاح وجود دارد - و این کاملاً طبیعی است.

چگونه یک بازسازی بزرگ انجام دهیم

اندرو: چگونه روی یک اجرا کار می کنید؟ این یک مشکل مقطعی است. به عنوان مثال، آیا تا به حال مجبور شده اید روی مشکلاتی که از تلاقی بسیاری از عملکردهای موجود ناشی می شود کار کنید؟

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

مدل هزینه

اندرو: در یکی از پادکست ها در مورد مدل های هزینه در زمینه بهره وری صحبت کردید. میشه توضیح بدید منظورتون از این حرف چیه؟

صخره: قطعا. من در دورانی متولد شدم که عملکرد پردازنده بسیار مهم بود. و این دوران دوباره باز می گردد - سرنوشت بدون کنایه نیست. من زندگی را در زمان ماشین های هشت بیتی شروع کردم؛ اولین کامپیوتر من با 256 بایت کار می کرد. دقیقا بایت همه چیز خیلی کوچک بود. دستورالعمل‌ها باید شمارش می‌شد، و وقتی شروع به بالا رفتن از پشته زبان برنامه‌نویسی کردیم، زبان‌ها بیشتر و بیشتر شدند. اسمبلر وجود داشت، سپس Basic، سپس C، و C از بسیاری از جزئیات مانند تخصیص ثبت و انتخاب دستورالعمل مراقبت کردند. اما همه چیز در آنجا کاملاً واضح بود و اگر به نمونه ای از یک متغیر اشاره گر می کردم، بارگذاری دریافت می کردم و هزینه این دستورالعمل مشخص است. سخت افزار تعداد معینی چرخه ماشین را تولید می کند، بنابراین سرعت اجرای موارد مختلف را می توان به سادگی با جمع کردن تمام دستورالعمل هایی که قرار است اجرا کنید محاسبه کرد. هر مقایسه/تست/شعبه/تماس/بارگیری/فروشگاه را می توان جمع کرد و گفت: این زمان اجرا برای شماست. هنگام کار بر روی بهبود عملکرد، مطمئناً توجه خواهید کرد که چه اعدادی با چرخه های داغ کوچک مطابقت دارند. 
اما به محض اینکه به جاوا، پایتون و موارد مشابه سوئیچ می کنید، خیلی سریع از سخت افزارهای سطح پایین فاصله می گیرید. هزینه فراخوانی گیرنده در جاوا چقدر است؟ اگر JIT در HotSpot درست باشد خط کشی شده، بارگذاری می شود، اما اگر این کار را انجام نداد، یک فراخوانی تابع خواهد بود. از آنجایی که تماس روی یک حلقه داغ است، همه بهینه‌سازی‌های دیگر در آن حلقه را لغو می‌کند. بنابراین، هزینه واقعی بسیار بالاتر خواهد بود. و شما بلافاصله توانایی نگاه کردن به یک کد و درک اینکه ما باید آن را از نظر سرعت ساعت پردازنده، حافظه و حافظه پنهان استفاده شده اجرا کنیم، از دست می دهید. همه اینها فقط در صورتی جالب می شود که واقعا وارد اجرا شوید.
اکنون ما در شرایطی هستیم که سرعت پردازنده برای یک دهه به سختی افزایش یافته است. قدیم برگشته! دیگر نمی توانید روی عملکرد خوب تک رشته ای حساب کنید. اما اگر ناگهان وارد محاسبات موازی شوید، فوق‌العاده دشوار است، همه مانند جیمز باند به شما نگاه می‌کنند. شتاب دهی در اینجا معمولاً در جاهایی اتفاق می افتد که کسی چیزی را به هم ریخته است. همزمانی نیاز به کار زیادی دارد. برای به دست آوردن آن سرعت XNUMX برابری، باید مدل هزینه را درک کنید. قیمتش چیه و چقدره؟ و برای انجام این کار، باید بدانید که چگونه زبان روی سخت افزار زیرین قرار می گیرد.
مارتین تامپسون یک کلمه عالی برای وبلاگ خود انتخاب کرد همدردی مکانیکی! شما باید بدانید که سخت افزار قرار است چه کاری انجام دهد، دقیقاً چگونه این کار را انجام می دهد و چرا در وهله اول کاری را انجام می دهد. با استفاده از این، شروع شمارش دستورالعمل ها و فهمیدن اینکه زمان اجرا به کجا می رود، نسبتاً آسان است. اگر آموزش مناسب ندارید، فقط به دنبال یک گربه سیاه در یک اتاق تاریک هستید. من افرادی را می بینم که دائماً عملکرد را بهینه می کنند و نمی دانند چه کاری انجام می دهند. آنها خیلی رنج می برند و پیشرفت چندانی ندارند. و وقتی من همان کد را می‌گیرم، چند هک کوچک را انجام می‌دهم و سرعت پنج یا ده برابری می‌گیرم، اینطور است: خوب، این منصفانه نیست، ما قبلاً می‌دانستیم که شما بهتر هستید. حیرت آور. من در مورد چه چیزی صحبت می کنم ... مدل هزینه مربوط به این است که چه نوع کدی را می نویسید و به طور متوسط ​​در تصویر بزرگ چقدر سریع اجرا می شود.

اندرو: و چگونه می توانید چنین حجمی را در ذهن خود نگه دارید؟ آیا این با تجربه بیشتر به دست می آید یا؟ چنین تجربه ای از کجا می آید؟

صخره: خب، من تجربه ام را به ساده ترین راه بدست نیاوردم. من در آن روزها در اسمبلی برنامه نویسی کردم که می توانستید تک تک دستورالعمل ها را بفهمید. احمقانه به نظر می رسد، اما از آن زمان مجموعه دستورالعمل Z80 همیشه در ذهن من، در حافظه من باقی مانده است. من اسم افراد را در عرض یک دقیقه از صحبت کردن به خاطر نمی آورم، اما رمزی را که 40 سال پیش نوشته شده بود به یاد می آورم. خنده دار است، شبیه یک سندرم است.دانشمند احمق'.

آموزش بهینه سازی سطح پایین

اندرو: آیا راه آسان تری برای ورود وجود دارد؟

صخره: بله و خیر. سخت افزاری که همه ما استفاده می کنیم در طول زمان تغییر چندانی نکرده است. همه از x86 استفاده می کنند، به استثنای گوشی های هوشمند Arm. اگر نوعی جاسازی هاردکور انجام نمی دهید، همان کار را انجام می دهید. باشه بعد دستورالعمل ها نیز برای قرن ها تغییر نکرده است. باید بروی و در اسمبلی چیزی بنویسی. زیاد نیست، اما برای شروع به درک کافی است. شما لبخند می زنید، اما من کاملا جدی صحبت می کنم. شما باید تطابق بین زبان و سخت افزار را درک کنید. بعد از آن باید بروید و کمی بنویسید و یک کامپایلر اسباب بازی کوچک برای زبان یک اسباب بازی کوچک بسازید. اسباب بازی مانند به این معنی است که باید در مدت زمان معقولی ساخته شود. می تواند بسیار ساده باشد، اما باید دستورالعمل هایی را ایجاد کند. عمل تولید یک دستورالعمل به شما کمک می کند مدل هزینه را برای پل بین کدهای سطح بالایی که همه می نویسند و کد ماشینی که روی سخت افزار اجرا می شود، درک کنید. این مکاتبات در زمان نوشتن کامپایلر در مغز سوخته می شود. حتی ساده ترین کامپایلر. پس از آن، می توانید به جاوا و این واقعیت که شکاف معنایی آن بسیار عمیق تر است و ایجاد پل بر روی آن بسیار دشوارتر است، نگاه کنید. در جاوا، درک اینکه آیا پل ما خوب است یا بد، چه چیزی باعث از هم پاشیدن آن می شود و چه چیزی نمی شود بسیار دشوارتر است. اما شما به نوعی نقطه شروع نیاز دارید که در آن به کد نگاه کنید و بفهمید: "بله، این گیرنده باید هر بار در خط خطی شود." و سپس معلوم می شود که گاهی اوقات این اتفاق می افتد، به جز شرایطی که روش خیلی بزرگ می شود و JIT شروع به درون ریزی همه چیز می کند. عملکرد چنین مکان هایی را می توان فوراً پیش بینی کرد. معمولا دریافت‌کننده‌ها به خوبی کار می‌کنند، اما سپس به حلقه‌های داغ بزرگ نگاه می‌کنید و متوجه می‌شوید که برخی فراخوانی‌های تابع در اطراف آن شناور هستند که نمی‌دانند دارند چه می‌کنند. این مشکل در استفاده گسترده از گیرنده هاست، دلیل خط نخوردن آنها این است که مشخص نیست که گیرنده هستند یا خیر. اگر پایه کد فوق العاده کوچکی دارید، می توانید به سادگی آن را به خاطر بسپارید و سپس بگویید: این یک گیرنده است و این یک تنظیم کننده است. در یک پایگاه کد بزرگ، هر تابع تاریخچه خاص خود را دارد که به طور کلی برای کسی شناخته شده نیست. نمایه ساز می گوید که ما 24٪ از زمان را در برخی از حلقه ها از دست داده ایم و برای اینکه بفهمیم این حلقه چه کاری انجام می دهد، باید به هر تابع در داخل نگاه کنیم. درک این بدون مطالعه عملکرد غیرممکن است و این روند درک را به طور جدی کند می کند. به همین دلیل از گیر و ستتر استفاده نمی کنم، به سطح جدیدی رسیده ام!
مدل هزینه را از کجا تهیه کنیم؟ خوب، البته می توانید چیزی بخوانید... اما به نظر من بهترین راه این است که عمل کنید. ساخت یک کامپایلر کوچک بهترین راه برای درک مدل هزینه و جا دادن آن در ذهن شما خواهد بود. یک کامپایلر کوچک که برای برنامه نویسی مایکروویو مناسب باشد، وظیفه ای برای یک مبتدی است. خوب، منظورم این است که اگر قبلاً مهارت های برنامه نویسی دارید، پس این کافی است. همه این موارد مانند تجزیه رشته ای که به عنوان نوعی عبارت جبری دارید، استخراج دستورالعمل های عملیات ریاضی از آنجا به ترتیب صحیح، گرفتن مقادیر صحیح از رجیسترها - همه اینها به یکباره انجام می شود. و در حالی که این کار را انجام می دهید، در مغز شما نقش می بندد. من فکر می کنم همه می دانند که یک کامپایلر چه کاری انجام می دهد. و این به درک مدل هزینه می دهد.

نمونه های عملی بهبود عملکرد

اندرو: هنگام کار روی بهره وری به چه چیز دیگری باید توجه کرد؟

صخره: ساختارهای داده. در ضمن، بله، من مدت زیادی است که این کلاس ها را تدریس نکرده ام... مدرسه موشک. سرگرم کننده بود، اما به تلاش زیادی نیاز داشت و من هم یک زندگی دارم! خوب. بنابراین، در یکی از کلاس‌های بزرگ و جالب، «عملکرد شما کجا می‌رود»، به دانش‌آموزان مثال زدم: دو و نیم گیگابایت داده فین‌تک از یک فایل CSV خوانده شد و سپس آنها باید تعداد محصولات فروخته شده را محاسبه کنند. . داده های بازار تیک منظم. بسته های UDP از دهه 70 به فرمت متن تبدیل شدند. بورس کالای شیکاگو - انواع چیزهایی مانند کره، ذرت، سویا، چیزهایی از این قبیل. شمارش این محصولات، تعداد تراکنش ها، میانگین حجم جابجایی وجوه و کالا و ... ضروری بود. ریاضی معاملات بسیار ساده است: کد محصول را پیدا کنید (که 1 تا 2 کاراکتر در جدول هش است)، مبلغ را دریافت کنید، آن را به یکی از مجموعه‌های معاملاتی اضافه کنید، حجم اضافه کنید، ارزش اضافه کنید و چند چیز دیگر. ریاضی خیلی ساده پیاده سازی اسباب بازی بسیار ساده بود: همه چیز در یک فایل است، من فایل را می خوانم و در آن حرکت می کنم، رکوردهای فردی را به رشته های جاوا تقسیم می کنم، چیزهای لازم را در آنها جستجو می کنم و آنها را مطابق با ریاضیاتی که در بالا توضیح داده شد جمع می کنم. و با سرعت کم کار می کند.

با این رویکرد، واضح است که چه اتفاقی می افتد، و محاسبات موازی کمکی نمی کند، درست است؟ به نظر می رسد که افزایش پنج برابری عملکرد را می توان به سادگی با انتخاب ساختارهای داده مناسب به دست آورد. و این حتی برنامه نویسان با تجربه را شگفت زده می کند! در مورد خاص من، ترفند این بود که شما نباید تخصیص حافظه را در یک حلقه داغ انجام دهید. خوب، این تمام حقیقت نیست، اما به طور کلی - وقتی X به اندازه کافی بزرگ است، نباید "یک بار در X" را برجسته کنید. وقتی X دو و نیم گیگابایت است، نباید چیزی را «یک بار در هر حرف»، «یک بار در هر خط» یا «یک بار در هر فیلد»، چیزی شبیه به آن اختصاص دهید. این جایی است که زمان صرف می شود. این حتی چگونه کار می کند؟ تصور کنید من تماس میگیرم String.split() یا BufferedReader.readLine(). Readline یک رشته از مجموعه ای از بایت هایی که روی شبکه آمده است، یک بار برای هر خط، برای هر یک از صدها میلیون خط، می سازد. من این خط را می گیرم، آن را تجزیه می کنم و دور می اندازم. چرا آن را دور می اندازم - خوب، قبلاً آن را پردازش کرده ام، همین. بنابراین، برای هر بایت خوانده شده از این 2.7G، دو کاراکتر در خط نوشته می شود، یعنی از قبل 5.4G است، و من برای هیچ چیز دیگری به آنها نیاز ندارم، بنابراین آنها دور ریخته می شوند. اگر به پهنای باند حافظه نگاه کنید، ما 2.7G را بارگذاری می کنیم که از طریق گذرگاه حافظه و حافظه در پردازنده می گذرد و سپس دو برابر آن به خطی که در حافظه قرار دارد ارسال می شود و با ایجاد هر خط جدید همه اینها خراب می شود. اما من باید آن را بخوانم، سخت افزار آن را می خواند، حتی اگر همه چیز بعدا خراب شود. و باید آن را یادداشت کنم زیرا یک خط ایجاد کردم و کش ها پر هستند - کش نمی تواند 2.7G را در خود جای دهد. بنابراین، به ازای هر بایتی که می‌خوانم، دو بایت دیگر می‌خوانم و دو بایت دیگر می‌نویسم، و در نهایت نسبت 4:1 دارند - در این نسبت ما پهنای باند حافظه را تلف می‌کنیم. و سپس معلوم می شود که اگر انجام دهم String.split() – این آخرین باری نیست که این کار را انجام می دهم، ممکن است 6-7 فیلد دیگر داخل آن باشد. بنابراین کد کلاسیک خواندن CSV و سپس تجزیه رشته ها منجر به اتلاف پهنای باند حافظه در حدود 14:1 نسبت به آنچه واقعاً دوست دارید داشته باشید، می شود. اگر این انتخاب ها را دور بیندازید، می توانید سرعت پنج برابری دریافت کنید.

و آنقدرها هم سخت نیست. اگر از زاویه درست به کد نگاه کنید، وقتی متوجه مشکل شدید همه چیز بسیار ساده می شود. شما نباید تخصیص حافظه را به طور کامل متوقف کنید: تنها مشکل این است که چیزی را تخصیص می دهید و بلافاصله می میرد و در طول مسیر منبع مهمی را می سوزاند که در این مورد پهنای باند حافظه است. و همه اینها منجر به کاهش بهره وری می شود. در x86 معمولاً باید چرخه های پردازنده را به طور فعال بسوزانید، اما در اینجا شما تمام حافظه را خیلی زودتر سوزاندید. راه حل کاهش میزان ترشحات است. 
بخش دیگر مشکل این است که اگر پروفایلر را زمانی که نوار حافظه تمام می‌شود، درست زمانی که این اتفاق می‌افتد، اجرا کنید، معمولاً منتظر هستید تا حافظه پنهان بازگردد، زیرا پر از زباله‌هایی است که به تازگی تولید کرده‌اید، تمام آن خطوط. بنابراین، هر بارگیری یا عملیات ذخیره‌سازی کند می‌شود، زیرا منجر به از دست رفتن حافظه پنهان می‌شود - کل حافظه پنهان کند شده است و منتظر خروج زباله است. بنابراین، نمایه ساز فقط نویز تصادفی گرم را نشان می دهد که در کل حلقه آغشته شده است - هیچ دستورالعمل داغ یا مکانی جداگانه در کد وجود نخواهد داشت. فقط سر و صدا و اگر به چرخه های GC نگاه کنید، همه آنها نسل جوان و فوق العاده سریع هستند - میکروثانیه یا حداکثر میلی ثانیه. پس از همه، همه این خاطره فورا می میرد. شما میلیاردها گیگابایت را اختصاص می دهید و او آنها را می برد و آنها را می برد و دوباره آنها را می برد. همه اینها خیلی سریع اتفاق می افتد. به نظر می رسد که چرخه های GC ارزان، نویز گرم در کل چرخه وجود دارد، اما ما می خواهیم سرعت 5 برابری داشته باشیم. در این لحظه، چیزی باید در سر شما بسته شود و به صدا درآید: "این چرا؟" سرریز نوار حافظه در اشکال‌زدای کلاسیک نمایش داده نمی‌شود؛ باید اشکال‌زدای شمارنده عملکرد سخت‌افزار را اجرا کنید و خودتان و مستقیماً آن را ببینید. اما نمی توان به طور مستقیم از این سه علامت شک کرد. سومین علامت این است که وقتی به آنچه برجسته می کنید نگاه می کنید، از پروفایلر بپرسید، و او پاسخ می دهد: "شما یک میلیارد ردیف ایجاد کردید، اما GC رایگان کار کرد." به محض اینکه این اتفاق می افتد، متوجه می شوید که اشیاء زیادی ایجاد کرده اید و کل خط حافظه را سوزانده اید. راهی برای فهمیدن این موضوع وجود دارد، اما واضح نیست. 

مشکل در ساختار داده است: ساختار خالی زیربنای هر اتفاقی که می افتد، بسیار بزرگ است، 2.7G روی دیسک است، بنابراین تهیه یک کپی از این چیز بسیار نامطلوب است - می خواهید فوراً آن را از بافر بایت شبکه بارگیری کنید. به رجیسترها، به طوری که پنج بار در خط به عقب و جلو خواندن و نوشتن نشوند. متأسفانه جاوا به طور پیش فرض چنین کتابخانه ای را به عنوان بخشی از JDK به شما نمی دهد. اما این بی اهمیت است، درست است؟ اساساً، اینها 5 تا 10 خط کد هستند که برای پیاده‌سازی بارگذار رشته بافر خود استفاده می‌شوند، که رفتار کلاس رشته را تکرار می‌کند، در حالی که یک بسته‌بندی در اطراف بافر بایت زیرین است. در نتیجه، معلوم می‌شود که شما تقریباً به‌گونه‌ای کار می‌کنید که گویی با رشته‌ها کار می‌کنید، اما در واقع اشاره‌گرها به بافر در آنجا حرکت می‌کنند، و بایت‌های خام در جایی کپی نمی‌شوند، و بنابراین از همان بافرها بارها و بارها استفاده می‌شود. سیستم عامل خوشحال است که چیزهایی را که برای آنها طراحی شده است به عهده بگیرد، مانند بافر دوگانه مخفی این بافرهای بایت، و شما دیگر در جریان یک جریان بی پایان داده های غیر ضروری خرد نمی کنید. راستی، آیا متوجه هستید که هنگام کار با GC، تضمین شده است که هر تخصیص حافظه پس از آخرین چرخه GC برای پردازنده قابل مشاهده نباشد؟ بنابراین، همه اینها احتمالاً نمی توانند در حافظه پنهان باشند و سپس یک خطای 100٪ تضمین شده رخ می دهد. هنگام کار با یک اشاره گر، در x86، کم کردن یک ثبات از حافظه 1-2 چرخه ساعت طول می کشد، و به محض اینکه این اتفاق می افتد، شما می پردازید، پرداخت می کنید، پرداخت می کنید، زیرا حافظه تماما روشن است. نه حافظه پنهان - و این هزینه تخصیص حافظه است. ارزش واقعی.

به عبارت دیگر، ساختار داده سخت ترین چیز برای تغییر است. و هنگامی که متوجه شدید که ساختار داده اشتباهی را انتخاب کرده اید که بعداً عملکرد را از بین می برد، معمولاً کارهای زیادی برای انجام دادن وجود دارد، اما اگر این کار را نکنید، اوضاع بدتر می شود. اول از همه، شما باید به ساختار داده فکر کنید، این مهم است. هزینه اصلی در اینجا بر روی ساختارهای داده چربی است، که شروع به استفاده از آنها به سبک "من ساختار داده X را در ساختار داده Y کپی کردم زیرا شکل Y را بهتر دوست دارم." اما عملیات کپی (که ارزان به نظر می رسد) در واقع پهنای باند حافظه را هدر می دهد و اینجاست که تمام زمان اجرای تلف شده دفن می شود. اگر من یک رشته غول پیکر از JSON داشته باشم و بخواهم آن را به یک درخت DOM ساختاریافته از POJO یا چیزی دیگر تبدیل کنم، عملیات تجزیه آن رشته و ساختن POJO، و سپس دسترسی مجدد به POJO بعداً منجر به هزینه غیر ضروری خواهد شد - ارزان نیست مگر اینکه دور POJO ها خیلی بیشتر از دور رشته بدوید. بد نیست، در عوض می توانید سعی کنید رشته را رمزگشایی کنید و فقط آنچه را که نیاز دارید از آنجا استخراج کنید، بدون اینکه آن را به هیچ POJO تبدیل کنید. اگر همه اینها در مسیری اتفاق بیفتد که از آن به حداکثر کارایی نیاز است، بدون POJO برای شما، باید به نحوی مستقیماً وارد خط شوید.

چرا زبان برنامه نویسی خود را بسازید؟

اندرو: شما گفتید که برای درک مدل هزینه باید زبان کوچک خود را بنویسید...

صخره: نه یک زبان، بلکه یک کامپایلر. زبان و کامپایلر دو چیز متفاوت هستند. مهمترین تفاوت در سر شماست. 

اندرو: اتفاقا، تا جایی که من می دانم، شما در حال آزمایش زبان های خود هستید. برای چی؟

صخره: چون من میتوانم! من نیمه بازنشسته هستم، پس این سرگرمی من است. در تمام زندگی ام زبان دیگران را پیاده کرده ام. روی سبک کدنویسی هم خیلی کار کردم. و همچنین به این دلیل که من مشکلاتی را در زبان های دیگر می بینم. من می بینم که راه های بهتری برای انجام کارهای آشنا وجود دارد. و من از آنها استفاده خواهم کرد. من فقط از دیدن مشکلات در خودم، در جاوا، پایتون و هر زبان دیگری خسته شده ام. من اکنون در React Native، JavaScript و Elm به عنوان یک سرگرمی می نویسم که در مورد بازنشستگی نیست، بلکه در مورد کار فعال است. من همچنین در پایتون می نویسم و ​​به احتمال زیاد به کار بر روی یادگیری ماشینی برای backend های جاوا ادامه خواهم داد. زبان های محبوب زیادی وجود دارد و همه آنها ویژگی های جالبی دارند. هر کس در نوع خود خوب است و شما می توانید سعی کنید همه این ویژگی ها را در کنار هم قرار دهید. بنابراین، من در حال مطالعه چیزهایی هستم که به من علاقه دارند، رفتار زبان، و سعی می کنم به معناشناسی معقولی برسم. و تا اینجای کار من موفق هستم! در حال حاضر من با معناشناسی حافظه دست و پنجه نرم می کنم، زیرا می خواهم آن را مانند C و Java داشته باشم و یک مدل حافظه قوی و معناشناسی حافظه برای بارگذاری ها و ذخیره سازی ها داشته باشم. در عین حال، مانند Haskell استنتاج نوع خودکار داشته باشید. در اینجا، من سعی می کنم استنتاج نوع Haskell مانند را با کار حافظه در C و Java مخلوط کنم. این کاری است که من مثلاً در 2-3 ماه گذشته انجام می دهم.

اندرو: اگر زبانی بسازید که جنبه‌های بهتری از زبان‌های دیگر بگیرد، فکر می‌کنید کسی برعکس این کار را انجام می‌دهد: ایده‌های شما را بگیرد و از آنها استفاده کند؟

صخره: زبان های جدید دقیقاً به این شکل ظاهر می شوند! چرا جاوا شبیه C است؟ چون C سینتکس خوبی داشت که همه آن را می فهمیدند و جاوا از این نحو الهام گرفته بود، ایمنی نوع، بررسی محدوده آرایه، GC را اضافه می کرد، و همچنین برخی چیزها را از C بهبود می بخشیدند. اما آنها بسیار الهام گرفتند، درست است؟ همه روی شانه های غول هایی که قبل از شما آمده اند می ایستند - اینطوری پیشرفت می شود.

اندرو: همانطور که متوجه شدم، زبان شما در حافظه امن خواهد بود. آیا به پیاده سازی چیزی مانند چک کننده قرض از Rust فکر کرده اید؟ آیا به او نگاه کرده اید، نظر شما در مورد او چیست؟

صخره: خوب، من مدت‌هاست که C می‌نویسم، با این همه malloc و رایگان، و به صورت دستی طول عمر را مدیریت می‌کنم. می دانید، 90-95٪ از عمر کنترل شده دستی ساختار یکسانی دارد. و انجام دستی آن بسیار بسیار دردناک است. من می خواهم که کامپایلر به سادگی به شما بگوید در آنجا چه خبر است و با اقدامات خود به چه چیزی دست یافته اید. برای برخی چیزها، چک کننده قرض این کار را خارج از جعبه انجام می دهد. و باید به طور خودکار اطلاعات را نمایش دهد، همه چیز را بفهمد، و حتی من را با ارائه این درک تحمیل نکند. باید حداقل تجزیه و تحلیل فرار محلی را انجام دهد، و تنها در صورت شکست، باید یادداشت‌هایی را اضافه کند که طول عمر را توصیف کند - و چنین طرحی بسیار پیچیده‌تر از یک بررسی کننده قرض یا در واقع هر بررسی کننده حافظه موجود است. انتخاب بین "همه چیز خوب است" و "من چیزی نمی فهمم" - نه، باید چیز بهتری وجود داشته باشد. 
بنابراین، به عنوان کسی که کدهای زیادی در C نوشته است، فکر می کنم که داشتن پشتیبانی از کنترل طول عمر خودکار مهمترین چیز است. من همچنین از اینکه جاوا چقدر از حافظه استفاده می کند خسته شده ام و شکایت اصلی GC است. هنگامی که حافظه را در جاوا تخصیص می دهید، حافظه ای را که در آخرین چرخه GC محلی بود، پس نخواهید گرفت. این مورد در زبان هایی با مدیریت حافظه دقیق تر نیست. اگر با malloc تماس بگیرید، بلافاصله حافظه ای را که معمولاً استفاده می شد دریافت می کنید. معمولاً کارهای موقتی را با حافظه انجام می دهید و بلافاصله آن را برمی گردانید. و بلافاصله به استخر malloc برمی گردد و چرخه malloc بعدی دوباره آن را بیرون می کشد. بنابراین، استفاده واقعی از حافظه به مجموعه ای از اشیاء زنده در یک زمان معین، به علاوه نشت کاهش می یابد. و اگر همه چیز به روشی کاملاً نامناسب لو نرود، بیشتر حافظه در حافظه نهان و پردازنده به پایان می رسد و به سرعت کار می کند. اما نیاز به مقدار زیادی مدیریت حافظه دستی با malloc و رایگان دارد که به ترتیب مناسب و در مکان مناسب فراخوانی شده است. Rust می تواند به تنهایی از عهده این کار برآید، و در بسیاری از موارد عملکرد بهتری نیز ارائه می دهد، زیرا مصرف حافظه فقط به محاسبات فعلی محدود می شود - برخلاف انتظار برای چرخه GC بعدی برای آزاد کردن حافظه. در نتیجه، راه بسیار جالبی برای بهبود عملکرد به دست آوردیم. و بسیار قدرتمند - منظورم این است که من چنین کارهایی را هنگام پردازش داده ها برای فین تک انجام دادم و این به من اجازه داد تا حدود پنج برابر سرعت داشته باشم. این یک تقویت بسیار بزرگ است، به خصوص در دنیایی که پردازنده‌ها سریع‌تر نمی‌شوند و ما همچنان منتظر بهبود هستیم.

شغل مهندس عملکرد

اندرو: من همچنین می خواهم در مورد مشاغل به طور کلی سؤال کنم. شما با کار JIT خود در HotSpot به شهرت رسیدید و سپس به Azul که یک شرکت JVM نیز هست نقل مکان کردید. اما ما قبلاً بیشتر روی سخت افزار کار می کردیم تا نرم افزار. و سپس آنها ناگهان به Big Data و Machine Learning و سپس به کشف تقلب روی آوردند. چگونه این اتفاق افتاد؟ اینها زمینه های توسعه بسیار متفاوتی هستند.

صخره: من مدت زیادی است که برنامه نویسی می کنم و توانسته ام در کلاس های مختلف شرکت کنم. و وقتی مردم می گویند: "اوه، شما کسی هستید که JIT را برای جاوا انجام دادید!"، همیشه خنده دار است. اما قبل از آن، من روی شبیه سازی پست اسکریپت کار می کردم - زبانی که اپل زمانی برای چاپگرهای لیزری خود استفاده می کرد. و قبل از آن پیاده سازی زبان چهارم را انجام دادم. فکر می کنم موضوع مشترک برای من توسعه ابزار است. در تمام عمرم ابزارهایی می‌سازم که دیگران با آن برنامه‌های جالب خود را می‌نویسند. اما من همچنین درگیر توسعه سیستم‌های عامل، درایورها، اشکال‌زدای سطح هسته، زبان‌های توسعه سیستم‌عامل بودم که ابتدایی بودند، اما با گذشت زمان پیچیده‌تر و پیچیده‌تر شدند. اما موضوع اصلی همچنان توسعه ابزارها است. بخش بزرگی از زندگی من بین آزول و سان گذشت و در مورد جاوا بود. اما وقتی وارد Big Data و Machine Learning شدم، کلاه فانتزی ام را دوباره روی سر گذاشتم و گفتم: "اوه، اکنون ما یک مشکل بی اهمیت داریم و چیزهای جالب زیادی در حال انجام است و مردم کارهایی را انجام می دهند." این یک مسیر توسعه عالی است.

بله، من واقعا عاشق محاسبات توزیع شده هستم. اولین کار من به عنوان دانشجو در C، در یک پروژه تبلیغاتی بود. این محاسبات روی تراشه‌های Zilog Z80 توزیع شد که داده‌ها را برای OCR آنالوگ جمع‌آوری می‌کرد که توسط یک تحلیلگر آنالوگ واقعی تولید می‌شد. موضوع باحال و کاملا دیوانه کننده ای بود. اما مشکلاتی وجود داشت، بخشی به درستی تشخیص داده نمی شد، بنابراین باید یک عکس بردارید و آن را به فردی نشان دهید که قبلاً می توانست با چشمان خود بخواند و آنچه را که می گوید گزارش دهد، و بنابراین مشاغلی با داده وجود داشت، و این مشاغل زبان خود را داشتند. یک باطن وجود داشت که همه اینها را پردازش می کرد - Z80s که به موازات ترمینال های vt100 در حال اجرا هستند - یک نفر برای هر نفر، و یک مدل برنامه نویسی موازی در Z80 وجود داشت. برخی از حافظه های مشترک که توسط همه Z80 ها در یک پیکربندی ستاره مشترک است. backplane هم به اشتراک گذاشته شد و نیمی از RAM داخل شبکه به اشتراک گذاشته شد و نیمی دیگر خصوصی بود یا به چیز دیگری رفت. یک سیستم توزیع موازی پیچیده به طور معناداری با حافظه مشترک... نیمه مشترک. این کی بود... من حتی نمی توانم به خاطر بیاورم، جایی در اواسط دهه 80. خیلی وقت پیش. 
بله، بیایید فرض کنیم که 30 سال زمان بسیار زیادی قبل است. مشکلات مربوط به محاسبات توزیع شده برای مدت طولانی وجود داشته است؛ مردم مدتهاست که در حال جنگ هستند. بئوولف-خوشه ها چنین خوشه هایی شبیه به ... به عنوان مثال: اترنت وجود دارد و x86 سریع شما به این اترنت متصل است، و اکنون می خواهید حافظه مشترک جعلی دریافت کنید، زیرا در آن زمان هیچ کس نمی توانست کدگذاری محاسباتی توزیع شده را انجام دهد، بسیار دشوار بود و بنابراین وجود داشت. حافظه مشترک جعلی با صفحات حافظه حفاظتی در x86 بود، و اگر به این صفحه نوشتید، به سایر پردازنده‌ها گفتیم که اگر به همان حافظه مشترک دسترسی داشته باشند، باید از شما بارگیری شود، و بنابراین چیزی شبیه یک پروتکل برای پشتیبانی cache coherence ظاهر شد و نرم افزاری برای این کار. مفهوم جالب البته مشکل اصلی چیز دیگری بود. همه اینها کار می کرد، اما شما به سرعت با مشکلات عملکرد مواجه شدید، زیرا هیچ کس مدل های عملکرد را در سطح کافی خوب درک نکرد - چه الگوهای دسترسی به حافظه وجود داشت، چگونه مطمئن شوید که گره ها به طور بی پایان یکدیگر را پینگ نمی کنند و غیره.

چیزی که من در H2O به آن رسیدم این بود که این خود توسعه دهندگان هستند که مسئول تعیین محل موازی بودن و عدم وجود آن هستند. من یک مدل کدنویسی پیدا کردم که نوشتن کدهای با کارایی بالا را آسان و ساده می کرد. اما نوشتن کدهای کند کار دشواری است، بد به نظر می رسد. شما باید به طور جدی سعی کنید کدهای آهسته بنویسید، باید از روش های غیر استاندارد استفاده کنید. کد ترمز در نگاه اول قابل مشاهده است. در نتیجه، شما معمولا کدی می نویسید که سریع اجرا می شود، اما باید در مورد حافظه مشترک چه کاری انجام دهید. همه اینها به آرایه های بزرگ گره خورده است و رفتار در آنجا شبیه به آرایه های بزرگ غیر فرار در جاوا موازی است. منظورم این است که تصور کنید دو رشته در یک آرایه موازی بنویسند، یکی از آنها برنده است و دیگری بر این اساس بازنده است و شما نمی دانید کدام یک کدام است. اگر آنها فرار نباشند، پس ترتیب می تواند هر چیزی باشد که شما می خواهید - و این واقعاً خوب عمل می کند. مردم واقعاً به ترتیب عملیات اهمیت می‌دهند، فرار را در مکان‌های مناسب قرار می‌دهند، و انتظار مشکلات عملکرد مرتبط با حافظه را در مکان‌های مناسب دارند. در غیر این صورت، آنها به سادگی کد را به شکل حلقه هایی از 1 تا N می نویسند، که در آن N چند تریلیون است، به این امید که همه موارد پیچیده به طور خودکار موازی شوند - و در آنجا کار نمی کند. اما در H2O این نه جاوا است و نه اسکالا، اگر بخواهید می توانید آن را "جاوا منهای منهای" در نظر بگیرید. این یک سبک برنامه نویسی بسیار واضح است و شبیه به نوشتن کدهای ساده C یا جاوا با حلقه ها و آرایه ها است. اما در عین حال، حافظه را می توان در ترابایت پردازش کرد. من هنوز از H2O استفاده می کنم. من هر از گاهی از آن در پروژه های مختلف استفاده می کنم - و هنوز هم سریع ترین چیز است، ده ها برابر سریعتر از رقبای خود. اگر داده های بزرگ را با داده های ستونی انجام می دهید، شکست دادن H2O بسیار سخت است.

چالش های فنی

اندرو: بزرگترین چالش شما در تمام دوران حرفه ای شما چه بوده است؟

صخره: بحث فنی یا غیر فنی موضوع را داریم؟ من می گویم بزرگترین چالش ها، چالش های فنی نیستند. 
در مورد چالش های فنی. من به سادگی آنها را شکست دادم. من حتی نمی‌دانم بزرگ‌ترین آنها چه بود، اما موارد بسیار جالبی وجود داشتند که زمان زیادی را صرف کردند. وقتی به سان رفتم مطمئن بودم که یک کامپایلر سریع می سازم و یکسری از ارشد در جواب گفتند که من هرگز موفق نمی شوم. اما من این مسیر را دنبال کردم، یک کامپایلر در تخصیص دهنده ثبت نوشتم و بسیار سریع بود. سرعت آن به اندازه C1 مدرن بود، اما در آن زمان تخصیص دهنده بسیار کندتر بود، و در گذشته این یک مشکل بزرگ در ساختار داده بود. من برای نوشتن یک تخصیص دهنده ثبات گرافیکی به آن نیاز داشتم و معضل بین بیان کد و سرعت را که در آن دوره وجود داشت و بسیار مهم بود، درک نکردم. معلوم شد که ساختار داده معمولاً از اندازه حافظه نهان در x86 های آن زمان فراتر می رود، و بنابراین، اگر در ابتدا فرض می کردم که تخصیص دهنده ثبات 5-10 درصد از کل زمان جیتر را انجام می دهد، در واقع معلوم شد که 50 درصد.

با گذشت زمان، کامپایلر تمیزتر و کارآمدتر شد، در موارد بیشتری تولید کدهای وحشتناک را متوقف کرد و عملکرد به طور فزاینده ای شبیه آنچه که یک کامپایلر C تولید می کند، شد. . اگر کدی مانند C بنویسید، در موارد بیشتری عملکردی مانند C خواهید داشت. و هر چه جلوتر می رفتید، اغلب کدی دریافت می کردید که به طور مجانبی با سطح C منطبق بود، تخصیص دهنده ثبات شروع به کامل شدن می کرد... صرف نظر از اینکه کد شما سریع اجرا می شود یا کند. من به کار روی تخصیص دهنده ادامه دادم تا انتخاب بهتری داشته باشد. او کندتر و کندتر می شد، اما در مواردی که هیچ کس نمی توانست از عهده آن برآید، عملکرد بهتر و بهتری ارائه می داد. می‌توانستم وارد یک تخصیص‌دهنده ثبت شوم، یک ماه کار را در آنجا دفن کنم، و ناگهان کل کد 5٪ سریع‌تر اجرا می‌شود. این اتفاق هر چند وقت یک‌بار اتفاق افتاد و تخصیص‌دهنده ثبت به چیزی شبیه یک اثر هنری تبدیل شد - همه آن را دوست داشتند یا از آن متنفر بودند و افراد آکادمی سؤالاتی در مورد این موضوع پرسیدند "چرا همه چیز به این شکل انجام می شود" ، چرا که نه اسکن خطی، و چه تفاوتی دارد. پاسخ همچنان یکسان است: یک تخصیص دهنده بر اساس رنگ آمیزی نمودار به علاوه کار بسیار دقیق با کد بافر برابر است با یک سلاح پیروزی، بهترین ترکیبی که هیچ کس نمی تواند آن را شکست دهد. و این یک چیز نسبتاً غیر آشکار است. هر چیز دیگری که کامپایلر در آنجا انجام می دهد، چیزهای نسبتاً خوبی است، اگرچه آنها نیز به سطح هنری رسیده اند. من همیشه کارهایی انجام می دادم که قرار بود کامپایلر را به یک اثر هنری تبدیل کند. اما هیچ کدام از اینها چیز خارق العاده ای نبود - به جز تخصیص دهنده ثبت. ترفند این است که مراقب باشید قطع کردن تحت بار و، اگر این اتفاق بیفتد (در صورت تمایل می‌توانم با جزئیات بیشتر توضیح دهم)، به این معنی است که می‌توانید با شدت بیشتری خط خطی کنید، بدون خطر افتادن بیش از حد در برنامه عملکرد. در آن روزها، مجموعه‌ای از کامپایلرهای تمام‌مقیاس وجود داشتند که با سوت‌ها و سوت‌ها آویزان می‌شدند، که تخصیص‌دهنده‌های ثبت داشتند، اما هیچ کس دیگری نمی‌توانست این کار را انجام دهد.

مشکل این است که اگر روش هایی را اضافه کنید که مشمول افزایش و افزایش ناحیه درون خطی هستند، مجموعه مقادیر استفاده شده فوراً از تعداد ثبات ها پیشی می گیرد و باید آنها را برش دهید. سطح بحرانی معمولاً زمانی فرا می‌رسد که تخصیص‌دهنده تسلیم می‌شود، و یک کاندیدای خوب برای نشت ارزش دیگری را دارد، شما چیزهای به طور کلی وحشی را می‌فروشید. ارزش inlining در اینجا این است که شما بخشی از سربار، سربار برای تماس و ذخیره را از دست می دهید، می توانید مقادیر داخل را ببینید و می توانید آنها را بیشتر بهینه کنید. هزینه inlining این است که تعداد زیادی مقادیر زنده تشکیل می شود و اگر تخصیص دهنده ثبت شما بیش از حد لازم بسوزد، بلافاصله ضرر می کنید. بنابراین، بیشتر تخصیص‌دهنده‌ها یک مشکل دارند: وقتی خط داخلی از یک خط خاص عبور می‌کند، همه چیز در جهان شروع به کاهش می‌کند و بهره‌وری می‌تواند در توالت تخلیه شود. کسانی که کامپایلر را پیاده‌سازی می‌کنند برخی از اکتشافی‌ها را اضافه می‌کنند: به عنوان مثال، برای توقف درون‌خط‌سازی، شروع با اندازه‌های به اندازه کافی بزرگ، زیرا تخصیص‌ها همه چیز را خراب می‌کنند. اینگونه است که یک پیچ خوردگی در نمودار عملکرد شکل می گیرد - شما درون خطی، درون خطی، عملکرد به آرامی رشد می کند - و سپس بوم! – مثل جک تندرو می افتد چون زیاد خط زدی. قبل از ظهور جاوا همه چیز به این صورت بود. جاوا به اینلاین بسیار بیشتری نیاز دارد، بنابراین من مجبور شدم تخصیص دهنده خود را بسیار تهاجمی تر کنم تا به جای خراب شدن، سطح شود، و اگر بیش از حد خط خطی کنید، شروع به ریختن می کند، اما پس از آن لحظه "دیگر ریزش وجود ندارد" همچنان فرا می رسد. این مشاهدۀ جالبی است و از ناکجاآباد به من رسید، واضح نیست، اما نتیجه خوبی داشت. من خط کشی تهاجمی را انتخاب کردم و آن را به جاهایی برد که عملکرد جاوا و C در کنار هم کار می کنند. آنها واقعاً به هم نزدیک هستند - من می توانم کدهای جاوا را بنویسم که به طور قابل توجهی سریعتر از کد C و چیزهایی از این قبیل است، اما به طور متوسط، در تصویر بزرگ همه چیز، تقریباً قابل مقایسه هستند. فکر می‌کنم بخشی از این شایستگی، تخصیص‌دهنده ثبت است، که به من اجازه می‌دهد تا حد امکان احمقانه‌تر وارد خط کنم. من فقط هر چیزی را که می بینم خط می کنم. سوالی که در اینجا مطرح می شود این است که آیا تخصیص دهنده به خوبی کار می کند، آیا نتیجه به صورت هوشمندانه کار می کند یا خیر. این یک چالش بزرگ بود: درک همه اینها و عملی کردن آن.

کمی در مورد تخصیص رجیستر و چند هسته

ولادیمیر: مشکلاتی مانند تخصیص رجیستر نوعی موضوع ابدی و بی پایان به نظر می رسد. نمی دانم آیا تا به حال ایده ای وجود داشته که امیدوارکننده به نظر برسد و سپس در عمل شکست بخورد؟

صخره: قطعا! تخصیص ثبت منطقه ای است که در آن سعی می کنید برخی از اکتشافات را برای حل یک مسئله NP-complete پیدا کنید. و شما هرگز نمی توانید به یک راه حل کامل برسید، درست است؟ این به سادگی غیرممکن است. نگاه کنید، کامپایل Ahead of Time - همچنین ضعیف کار می کند. گفتگو در اینجا در مورد برخی از موارد متوسط ​​است. در مورد عملکرد معمولی، بنابراین می توانید بروید و چیزی را که فکر می کنید عملکرد معمولی خوبی است اندازه گیری کنید - بالاخره برای بهبود آن تلاش می کنید! تخصیص ثبت نام موضوعی است که همه چیز در مورد عملکرد است. هنگامی که اولین نمونه اولیه را دارید، کار می کند و آنچه مورد نیاز است را رنگ می کند، کار اجرا شروع می شود. شما باید یاد بگیرید که خوب اندازه گیری کنید. چرا مهم است؟ اگر داده های واضحی دارید، می توانید به مناطق مختلف نگاه کنید و ببینید: بله، اینجا کمک کرد، اما اینجاست که همه چیز خراب شد! چند ایده خوب مطرح می شود، شما اکتشافی جدید اضافه می کنید و ناگهان همه چیز به طور متوسط ​​کمی بهتر کار می کند. یا شروع نمی شود. من موارد زیادی داشتم که برای عملکرد پنج درصدی که توسعه ما را از تخصیص دهنده قبلی متمایز می کرد می جنگیدیم. و هر بار اینگونه به نظر می رسد: جایی برنده می شوی، جایی باخت. اگر ابزارهای تجزیه و تحلیل عملکرد خوبی دارید، می توانید ایده های بازنده را پیدا کنید و دلیل شکست آنها را درک کنید. شاید ارزش آن را داشته باشد که همه چیز را همانطور که هست رها کنیم، یا شاید رویکرد جدی تری برای تنظیم دقیق داشته باشیم، یا بیرون برویم و چیز دیگری را اصلاح کنیم. این یک سری چیز است! من این هک جالب را ساختم، اما به این یکی، و این یکی و این یکی هم نیاز دارم - و ترکیب کلی آنها بهبودهایی را به همراه دارد. و افراد تنها می توانند شکست بخورند. این ماهیت کار عملکردی روی مسائل NP-complete است.

ولادیمیر: آدم این احساس را پیدا می کند که مواردی مانند نقاشی در تخصیص دهنده ها مشکلی است که قبلاً حل شده است. خوب، با قضاوت بر اساس آنچه می گویید، برای شما تصمیم گرفته شده است، پس آیا ارزش آن را دارد...

صخره: به این صورت حل نمی شود. این شما هستید که باید آن را به "حل شده" تبدیل کنید. مشکلات سختی وجود دارد و باید حل شود. پس از انجام این کار، زمان کار بر روی بهره وری است. شما باید بر این اساس به این کار نزدیک شوید - معیارها را انجام دهید، معیارها را جمع آوری کنید، شرایطی را توضیح دهید که وقتی به نسخه قبلی برگشتید، هک قدیمی شما دوباره شروع به کار کرد (یا برعکس، متوقف شد). و تا زمانی که به چیزی نرسیدی تسلیم نشو. همانطور که قبلاً گفتم، اگر ایده های جالبی وجود داشته باشد که جواب نداد، اما در زمینه تخصیص ثبت ایده ها تقریباً بی پایان است. برای مثال می توانید نشریات علمی را بخوانید. اگرچه اکنون این منطقه بسیار کندتر شروع به حرکت کرده است و نسبت به دوران جوانی واضح تر شده است. با این حال، افراد بیشماری در این زمینه مشغول به کار هستند و تمام ایده های آنها ارزش امتحان دارد، همه آنها در بال منتظر هستند. و نمی توانید بگویید چقدر خوب هستند مگر اینکه آنها را امتحان کنید. چقدر آنها با هر چیز دیگری در تخصیص دهنده شما یکپارچه می شوند، زیرا یک تخصیص دهنده کارهای زیادی را انجام می دهد و برخی ایده ها در تخصیص دهنده خاص شما کار نمی کنند، اما در تخصیص دهنده دیگر به راحتی کار می کنند. راه اصلی برای برنده شدن برای تخصیص دهنده این است که چیزهای کند را به خارج از مسیر اصلی بکشید و آن را مجبور کنید تا در امتداد مرزهای مسیرهای آهسته تقسیم شود. بنابراین اگر می‌خواهید یک GC را اجرا کنید، مسیر آهسته را انتخاب کنید، بهینه سازی کنید، استثنا بیاورید، همه این موارد - می‌دانید که این موارد نسبتاً نادر هستند. و واقعا نادر هستند، من بررسی کردم. شما کار اضافی انجام می دهید و بسیاری از محدودیت ها را در این مسیرهای آهسته از بین می برد، اما واقعاً مهم نیست زیرا آنها کند هستند و به ندرت سفر می شوند. به عنوان مثال، یک اشاره گر تهی - هرگز اتفاق نمی افتد، درست است؟ شما باید چندین مسیر برای چیزهای مختلف داشته باشید، اما آنها نباید با مسیر اصلی تداخل داشته باشند. 

ولادیمیر: وقتی هزاران هسته همزمان وجود دارد، نظر شما در مورد چند هسته چیست؟ آیا این کار مفیدی است؟

صخره: موفقیت GPU نشان می دهد که کاملا مفید است!

ولادیمیر: کاملا تخصصی هستند. در مورد پردازنده های همه منظوره چطور؟

صخره: خب، این مدل کسب و کار آزول بود. پاسخ در دوره ای برگشت که مردم واقعاً عاشق عملکرد قابل پیش بینی بودند. در آن زمان نوشتن کد موازی دشوار بود. مدل کدگذاری H2O بسیار مقیاس پذیر است، اما یک مدل هدف عمومی نیست. شاید کمی کلی تر از استفاده از GPU باشد. آیا ما در مورد پیچیدگی توسعه چنین چیزی صحبت می کنیم یا پیچیدگی استفاده از آن؟ به عنوان مثال، آزول به من درس جالبی داد، یک درس نسبتاً غیر واضح: حافظه پنهان کوچک طبیعی است. 

بزرگترین چالش زندگی

ولادیمیر: چالش های غیر فنی چطور؟

صخره: بزرگترین چالش این بود که با مردم مهربان و مهربان نباشیم. و در نتیجه، من دائماً خود را در موقعیت های بسیار درگیری می دیدم. آنهایی که می‌دانستم همه چیز بد پیش می‌رود، اما نمی‌دانستند چگونه با آن مشکلات پیش بروند و نمی‌توانستند با آنها کنار بیایند. بسیاری از مشکلات درازمدت که برای چندین دهه به طول انجامید، از این طریق به وجود آمد. این واقعیت که جاوا دارای کامپایلرهای C1 و C2 است نتیجه مستقیم این موضوع است. این واقعیت که ده سال متوالی در جاوا کامپایل چند سطحی وجود نداشت نیز یک پیامد مستقیم است. بدیهی است که ما به چنین سیستمی نیاز داشتیم، اما مشخص نیست که چرا وجود نداشت. من با یک مهندس یا گروهی از مهندسان مشکل داشتم. روزی روزگاری، وقتی در سان شروع به کار کردم، بودم... باشه، نه تنها اون موقع، من عموما همیشه نظر خودم را در مورد همه چیز دارم. و من فکر کردم درست است که شما فقط می توانید این حقیقت خود را بگیرید و آن را سر به سر بگویید. مخصوصاً که بیشتر اوقات به طرز تکان دهنده ای حق با من بود. و اگر این رویکرد را دوست ندارید... به خصوص اگر آشکارا اشتباه می کنید و مزخرف می کنید... به طور کلی، افراد کمی می توانند این شکل از ارتباط را تحمل کنند. اگرچه بعضی ها مثل من می توانستند. من تمام زندگی ام را بر اساس اصول شایسته سالارانه بنا کرده ام. اگر اشتباهی به من نشان دادی، فوراً برمی گردم و می گویم: مزخرف گفتی. البته در عین حال پوزش می طلبم و با تمام این موارد محاسن را در صورت وجود یادداشت می کنم و اقدامات صحیح دیگری را انجام خواهم داد. از سوی دیگر، من در مورد درصد بسیار زیادی از کل زمان به طرز تکان دهنده ای درست می گویم. و در روابط با مردم خیلی خوب کار نمی کند. من سعی نمی کنم خوب باشم، اما این سوال را صراحتا می پرسم. "این هرگز جواب نمی دهد، زیرا یک، دو و سه." و آنها مانند "اوه!" عواقب دیگری نیز وجود داشت که احتمالاً بهتر است نادیده بگیرم: به عنوان مثال، آنهایی که منجر به طلاق از همسرم و ده سال افسردگی پس از آن شد.

چالش مبارزه با مردم است، با درک آنها از آنچه شما می توانید یا نمی توانید انجام دهید، چه چیزی مهم است و چه چیزی نیست. چالش های زیادی در مورد سبک کدنویسی وجود داشت. من هنوز کدهای زیادی می نویسم و ​​در آن روزها حتی مجبور بودم سرعتم را کم کنم زیرا کارهای موازی زیادی انجام می دادم و آنها را ضعیف انجام می دادم، به جای تمرکز روی یکی. با نگاهی به گذشته، نصف کد دستور JIT جاوا، دستور C2 را نوشتم. سریع‌ترین کدنویس بعدی نصف آهسته، کد بعدی به نصف آهسته نوشت و این کاهش تصاعدی بود. نفر هفتم در این ردیف بسیار بسیار کند بود - همیشه این اتفاق می افتد! من کدهای زیادی را لمس کردم. نگاه کردم که چه کسی چه نوشته است، بدون استثنا، به کد آنها خیره شدم، هر کدام را مرور کردم، و همچنان بیشتر از هر کدام از آنها به نوشتن ادامه دادم. این رویکرد با مردم خیلی خوب کار نمی کند. برخی از مردم این را دوست ندارند. و وقتی نمی توانند از عهده آن برآیند، انواع شکایت ها شروع می شود. مثلاً یک بار به من گفتند که کدنویسی را متوقف کنم زیرا من بیش از حد کد می نویسم و ​​تیم را به خطر می اندازد و همه اینها برای من شبیه یک شوخی بود: رفیق، اگر بقیه اعضای تیم ناپدید شوند و من به کدنویسی ادامه دهم، شما فقط نیمی از تیم ها را از دست می دهند. از طرف دیگر، اگر به نوشتن کد ادامه دهم و نیمی از تیم را از دست بدهید، مدیریت بسیار بدی به نظر می رسد. من هرگز واقعاً به آن فکر نکردم، هرگز در مورد آن صحبت نکردم، اما هنوز جایی در ذهنم بود. این فکر در پشت ذهنم می چرخید: "همه با من شوخی می کنی؟" بنابراین، بزرگترین مشکل من و روابطم با مردم بود. حالا خودم را خیلی بهتر درک می کنم، من برای مدت طولانی سرپرست تیم برنامه نویسان بودم، و اکنون مستقیماً به مردم می گویم: می دانید، من همانی هستم که هستم، و شما باید با من کنار بیایید - اشکالی ندارد اگر بایستم اینجا؟ و وقتی شروع به برخورد با آن کردند، همه چیز درست شد. در واقع، من نه بد هستم و نه خوب، هیچ نیت بد یا آرزوهای خودخواهانه ای ندارم، این فقط ذات من است و باید به نوعی با آن زندگی کنم.

اندرو: اخیراً همه شروع به صحبت در مورد خودآگاهی برای افراد درونگرا و به طور کلی مهارت های نرم کردند. در این مورد چه می توانید بگویید؟

صخره: بله، این بینش و درسی بود که از طلاقم از همسرم گرفتم. چیزی که از طلاق یاد گرفتم درک خودم بود. اینگونه شروع به درک دیگران کردم. درک کنید که این تعامل چگونه کار می کند. این منجر به کشفیات یکی پس از دیگری شد. این آگاهی وجود داشت که من کی هستم و چه چیزی را نمایندگی می کنم. من چه می‌کنم: یا درگیر کار هستم، یا از تعارض اجتناب می‌کنم، یا چیز دیگری - و این سطح از خودآگاهی واقعاً به کنترل خودم کمک می‌کند. بعد از این همه چیز خیلی راحت تر پیش می رود. چیزی که من نه تنها در خودم، بلکه در سایر برنامه نویسان نیز کشف کردم، ناتوانی در بیان کلامی افکار در شرایط استرس عاطفی است. به عنوان مثال، شما در حال برنامه نویسی نشسته اید، در حالت جریان، و سپس آنها به سمت شما می آیند و با هیستریک شروع به فریاد زدن می کنند که چیزی شکسته است و اکنون اقدامات شدید علیه شما انجام خواهد شد. و شما نمی توانید کلمه ای بگویید زیرا در حالت استرس عاطفی هستید. دانش به دست آمده به شما امکان می دهد برای این لحظه آماده شوید، از آن جان سالم به در ببرید و به سمت یک برنامه عقب نشینی بروید، پس از آن می توانید کاری انجام دهید. بنابراین بله، وقتی متوجه می‌شوید که همه چیز چگونه کار می‌کند، این یک رویداد بزرگ است که زندگی را تغییر می‌دهد. 
من خودم نتوانستم کلمات مناسب را پیدا کنم، اما دنباله اعمال را به یاد آوردم. نکته این است که این واکنش به همان اندازه که کلامی است فیزیکی است و شما نیاز به فضا دارید. چنین فضایی به معنای ذن. این دقیقاً همان چیزی است که باید توضیح داده شود، و سپس بلافاصله کنار بروید - صرفاً از نظر فیزیکی دور شوید. وقتی به صورت کلامی سکوت می کنم، می توانم موقعیت را از نظر احساسی پردازش کنم. همانطور که آدرنالین به مغز شما می رسد، شما را به حالت جنگ یا پرواز می برد، دیگر نمی توانید چیزی بگویید، نه - اکنون شما یک احمق، یک مهندس شلاق هستید، قادر به پاسخ مناسب یا حتی توقف حمله نیستید، و مهاجم آزاد است. برای حمله دوباره و دوباره ابتدا باید دوباره خودتان شوید، کنترل را دوباره به دست آورید، از حالت "جنگ یا پرواز" خارج شوید.

و برای این ما به فضای کلامی نیاز داریم. فقط فضای خالی اگر اصلاً چیزی می گویید، می توانید دقیقاً همین را بگویید و سپس بروید و واقعاً "فضایی" برای خود پیدا کنید: در پارک قدم بزنید، خود را زیر دوش ببندید - مهم نیست. نکته اصلی این است که به طور موقت از آن وضعیت جدا شوید. به محض اینکه حداقل برای چند ثانیه خاموش می‌شوید، کنترل برمی‌گردد، هوشیارانه فکر می‌کنید. "خوب، من یک جور احمق نیستم، کارهای احمقانه انجام نمی دهم، من یک فرد بسیار مفید هستم." هنگامی که توانستید خود را متقاعد کنید، زمان آن فرا رسیده است که به مرحله بعدی بروید: درک آنچه اتفاق افتاده است. شما مورد حمله قرار گرفتید، حمله از جایی بود که انتظارش را نداشتید، این یک کمین ناصادقانه و پست بود. این بد است. گام بعدی این است که بفهمیم چرا مهاجم به این نیاز داشته است. واقعا چرا؟ شاید چون خودش عصبانی است؟ چرا او دیوانه است؟ مثلاً چون خودش را خراب کرده و نمی تواند مسئولیت را بپذیرد؟ این راهی است که می توان با دقت تمام وضعیت را مدیریت کرد. اما این نیاز به فضایی برای مانور، فضای کلامی دارد. اولین قدم این است که تماس کلامی را قطع کنید. از بحث با کلمات بپرهیزید. آن را لغو کنید، هر چه سریعتر دور شوید. اگر مکالمه تلفنی است، فقط تماس را قطع کنید - این مهارتی است که از برقراری ارتباط با همسر سابقم آموختم. اگر مکالمه به جایی نمی رسد، فقط «خداحافظ» بگویید و تلفن را قطع کنید. از آن طرف تلفن: "بله بالله"، شما پاسخ می دهید: "آره، خداحافظ!" و قطع کن شما فقط مکالمه را تمام کنید. پنج دقیقه بعد، وقتی توانایی اندیشیدن معقول به شما باز می گردد، کمی خنک شده اید، فکر کردن در مورد همه چیز، چه اتفاقی افتاده و چه اتفاقی خواهد افتاد ممکن می شود. و شروع به فرموله کردن یک پاسخ متفکرانه کنید، نه اینکه فقط از روی احساسات واکنش نشان دهید. برای من، پیشرفت در خودآگاهی دقیقاً این واقعیت بود که در صورت استرس عاطفی نمی توانم صحبت کنم. خارج شدن از این حالت، فکر کردن و برنامه ریزی برای پاسخگویی و جبران مشکلات - اینها در مواردی که نمی توانید صحبت کنید، گام های درستی است. ساده ترین راه این است که از موقعیتی که در آن استرس عاطفی خود را نشان می دهد فرار کنید و به سادگی از شرکت در این استرس دست بردارید. بعد از آن شما قادر به تفکر می شوید، زمانی که می توانید فکر کنید، قادر به صحبت می شوید و غیره.

به هر حال ، در دادگاه ، وکیل مخالف سعی می کند این کار را با شما انجام دهد - اکنون مشخص است که چرا. چون او این توانایی را دارد که شما را تا حدی سرکوب کند که حتی نتوانید مثلاً اسم خود را تلفظ کنید. به معنای واقعی، شما نمی توانید صحبت کنید. اگر این اتفاق برای شما افتاد و اگر می‌دانید که خود را در جایی می‌بینید که درگیری‌های لفظی در آن جریان دارد، در جایی مانند دادگاه، می‌توانید با وکیل خود بیایید. وکیل مدافع شما می ایستد و جلوی حمله لفظی را می گیرد و به صورت کاملا قانونی این کار را انجام می دهد و فضای گم شده ذن به شما باز می گردد. به عنوان مثال، من مجبور شدم چند بار با خانواده ام تماس بگیرم، قاضی در این مورد کاملاً دوستانه بود، اما وکیل مخالف فریاد زد و بر سر من فریاد زد، من حتی نمی توانستم یک کلمه به زبان بیاورم. در این موارد استفاده از واسطه برای من بهترین کار را دارد. واسطه تمام این فشاری را که در یک جریان پیوسته به شما وارد می کند متوقف می کند، فضای ذن لازم را پیدا می کنید و با آن توانایی صحبت باز می گردد. این یک حوزه کامل دانش است که در آن چیزهای زیادی برای مطالعه وجود دارد، چیزهای زیادی برای کشف در درون خودتان، و همه اینها به تصمیمات استراتژیک سطح بالا تبدیل می شود که برای افراد مختلف متفاوت است. برخی از افراد مشکلاتی را که در بالا توضیح داده شد ندارند؛ معمولاً افرادی که فروشندگان حرفه ای هستند این مشکلات را ندارند. همه این افرادی که زندگی خود را با کلمات می گذرانند - خوانندگان مشهور، شاعران، رهبران مذهبی و سیاستمداران، همیشه حرفی برای گفتن دارند. آنها چنین مشکلاتی ندارند، اما من دارم.

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

می توانید گفتگوی خود را با کلیف در کنفرانس Hydra 2019 که در تاریخ 11 تا 12 جولای 2019 در سن پترزبورگ برگزار می شود، ادامه دهید. با گزارشی می آید "تجربه حافظه تراکنشی سخت افزار آزول". بلیط را می توان خریداری کرد در وب سایت رسمی.

منبع: www.habr.com

اضافه کردن نظر