مقابلة رائعة مع Cliff Click، الأب الروحي لتجميع JIT في Java

مقابلة رائعة مع Cliff Click، الأب الروحي لتجميع JIT في Javaانقر فوق الهاوية - المدير التنفيذي للتكنولوجيا في Cratus (أجهزة استشعار إنترنت الأشياء لتحسين العمليات)، المؤسس والمؤسس المشارك للعديد من الشركات الناشئة (بما في ذلك Rocket Realtime School وNeurensic وH2O.ai) مع العديد من عمليات الخروج الناجحة. كتب كليف أول مترجم له في سن 15 عامًا (باسكال لـ TRS Z-80)! اشتهر بعمله على C2 في Java (بحر العقد IR). أظهر هذا المترجم للعالم أن JIT يمكن أن تنتج تعليمات برمجية عالية الجودة، والتي كانت أحد العوامل في ظهور Java كواحدة من منصات البرامج الحديثة الرئيسية. بعد ذلك، ساعد Cliff شركة Azul Systems في بناء حاسب مركزي ذو 864 نواة باستخدام برنامج Java خالص يدعم توقف GC مؤقتًا على كومة تخزينية سعة 500 جيجابايت خلال 10 مللي ثانية. بشكل عام، تمكن Cliff من العمل على جميع جوانب JVM.

 
يعد هذا habrapost مقابلة رائعة مع كليف. سنتحدث في المواضيع التالية:

  • الانتقال إلى التحسينات ذات المستوى المنخفض
  • كيفية القيام بإعادة هيكلة كبيرة
  • نموذج التكلفة
  • التدريب الأمثل على مستوى منخفض
  • أمثلة عملية لتحسين الأداء
  • لماذا تنشئ لغة البرمجة الخاصة بك؟
  • مهنة مهندس الأداء
  • التحديات التقنية
  • قليلا عن تخصيص التسجيل والنوى المتعددة
  • التحدي الأكبر في الحياة

المقابلة تجري بواسطة :

  • أندريه ساتارين من أمازون ويب سيرفيسز. في حياته المهنية، تمكن من العمل في مشاريع مختلفة تماما: اختبر قاعدة بيانات NewSQL الموزعة في Yandex، ونظام الكشف السحابي في Kaspersky Lab، وهي لعبة متعددة اللاعبين في Mail.ru وخدمة حساب أسعار صرف العملات الأجنبية في Deutsche Bank. مهتم باختبار الأنظمة الخلفية والأنظمة الموزعة واسعة النطاق.
  • فلاديمير سيتنيكوف من نتكراكر. عشر سنوات من العمل على أداء وقابلية التوسع لنظام التشغيل NetCracker، وهو برنامج يستخدمه مشغلو الاتصالات لأتمتة عمليات إدارة الشبكة ومعدات الشبكة. مهتم بقضايا أداء Java وOracle Database. مؤلف أكثر من عشرة تحسينات في الأداء في برنامج تشغيل PostgreSQL JDBC الرسمي.

الانتقال إلى التحسينات ذات المستوى المنخفض

أندرو: أنت اسم كبير في عالم تجميع JIT وجافا وأعمال الأداء بشكل عام، أليس كذلك؟ 

جرف: انها كذلك!

أندرو: لنبدأ ببعض الأسئلة العامة حول أداء العمل. ما رأيك في الاختيار بين التحسينات عالية المستوى ومنخفضة المستوى مثل العمل على مستوى وحدة المعالجة المركزية؟

جرف: نعم، كل شيء بسيط هنا. أسرع رمز هو الذي لا يعمل أبدًا. لذلك، تحتاج دائمًا إلى البدء بمستوى عالٍ، والعمل على الخوارزميات. تدوين O الأفضل سوف يتفوق على تدوين O الأسوأ، ما لم تتدخل بعض الثوابت الكبيرة بما فيه الكفاية. الأشياء ذات المستوى المنخفض تستمر. عادةً، إذا قمت بتحسين بقية مجموعتك بشكل جيد بما فيه الكفاية ولا تزال هناك بعض الأشياء المثيرة للاهتمام، فهذا مستوى منخفض. ولكن كيف تبدأ من مستوى عال؟ كيف تعرف أنه قد تم إنجاز ما يكفي من العمل رفيع المستوى؟ حسنا... مستحيل. لا توجد وصفات جاهزة. أنت بحاجة إلى فهم المشكلة، وتحديد ما ستفعله (حتى لا تتخذ خطوات غير ضرورية في المستقبل) وبعد ذلك يمكنك الكشف عن ملف التعريف الذي يمكن أن يقول شيئًا مفيدًا. في مرحلة ما، ستدرك أنك تخلصت من الأشياء غير الضرورية وحان الوقت لإجراء بعض الضبط الدقيق على المستوى المنخفض. هذا بالتأكيد نوع خاص من الفن. هناك الكثير من الأشخاص يقومون بأشياء غير ضرورية، لكنهم يتحركون بسرعة كبيرة بحيث لا يكون لديهم وقت للقلق بشأن الإنتاجية. ولكن هذا حتى يطرح السؤال بصراحة. عادة في 99% من الوقت لا يهتم أحد بما أفعله، حتى اللحظة التي يأتي فيها شيء مهم على المسار الحرج الذي لا يهتم به أحد. وهنا يبدأ الجميع في إزعاجك بشأن "لماذا لم يعمل الأمر بشكل مثالي منذ البداية". بشكل عام، هناك دائمًا شيء لتحسين الأداء. لكن في 99% من الحالات لا يكون لديك أي عملاء محتملين! أنت فقط تحاول إنجاح شيء ما، وفي هذه العملية تكتشف ما هو مهم. لا يمكنك أبدًا أن تعرف مقدمًا أن هذه القطعة يجب أن تكون مثالية، لذلك، في الواقع، عليك أن تكون مثاليًا في كل شيء. ولكن هذا مستحيل وأنت لا تفعل ذلك. هناك دائمًا الكثير من الأشياء التي يجب إصلاحها، وهذا طبيعي تمامًا.

كيفية القيام بإعادة هيكلة كبيرة

أندرو: كيف تعمل على الأداء؟ وهذه مشكلة شاملة. على سبيل المثال، هل سبق لك أن اضطررت إلى العمل على حل المشكلات التي تنشأ من تقاطع الكثير من الوظائف الموجودة؟

جرف: أحاول تجنب ذلك. إذا كنت أعرف أن الأداء سيكون مشكلة، أفكر في الأمر قبل أن أبدأ في البرمجة، خاصة فيما يتعلق بهياكل البيانات. لكن غالبًا ما تكتشف كل هذا لاحقًا. وبعد ذلك عليك أن تتخذ إجراءات متطرفة وتفعل ما أسميه "إعادة الكتابة والغزو": تحتاج إلى الاستيلاء على قطعة كبيرة بما يكفي. لا يزال يتعين إعادة كتابة بعض التعليمات البرمجية بسبب مشاكل في الأداء أو أي شيء آخر. مهما كان سبب إعادة كتابة التعليمات البرمجية، فمن الأفضل دائمًا إعادة كتابة جزء أكبر من جزء أصغر. في هذه اللحظة، يبدأ الجميع في الارتعاش من الخوف: "يا إلهي، لا يمكنك لمس الكثير من التعليمات البرمجية!" ولكن في الواقع، يعمل هذا النهج دائمًا بشكل أفضل بكثير. عليك أن تتعامل على الفور مع مشكلة كبيرة، وأن ترسم دائرة كبيرة حولها وتقول: سأعيد كتابة كل شيء داخل الدائرة. الحدود أصغر بكثير من المحتوى الموجود بداخلها والذي يجب استبداله. وإذا كان هذا الترسيم للحدود يسمح لك بالقيام بالعمل في الداخل بشكل مثالي، فإن يديك حرتان، افعل ما تريد. بمجرد فهم المشكلة، تصبح عملية إعادة الكتابة أسهل بكثير، لذا خذ قضمة كبيرة!
وفي الوقت نفسه، عندما تقوم بإعادة كتابة كبيرة وتدرك أن الأداء سيكون مشكلة، يمكنك أن تبدأ على الفور في القلق بشأن ذلك. يتحول هذا عادةً إلى أشياء بسيطة مثل "لا تنسخ البيانات، وقم بإدارة البيانات ببساطة قدر الإمكان، واجعلها صغيرة." في عمليات إعادة الكتابة الكبيرة، هناك طرق قياسية لتحسين الأداء. وهي تدور دائمًا حول البيانات.

نموذج التكلفة

أندرو: تحدثت في أحد المدونات الصوتية عن نماذج التكلفة في سياق الإنتاجية. هل يمكنك توضيح ما تقصده بهذا؟

جرف: بالتأكيد. لقد ولدت في عصر كان فيه أداء المعالج في غاية الأهمية. ويعود هذا العصر مرة أخرى - فالقدر لا يخلو من السخرية. بدأت أعيش في أيام الآلات ذات الثماني بتات، حيث كان جهاز الكمبيوتر الأول الخاص بي يعمل بسعة 256 بايت. بايت بالضبط. كان كل شيء صغيرًا جدًا. كان لا بد من حساب التعليمات، وعندما بدأنا في التقدم في مجموعة لغات البرمجة، اكتسبت اللغات المزيد والمزيد. كان هناك Assembler، ثم Basic، ثم C، وC اهتم بالكثير من التفاصيل، مثل تخصيص التسجيل واختيار التعليمات. لكن كل شيء كان واضحًا تمامًا هناك، وإذا قمت بوضع مؤشر على مثيل لمتغير، فسأحصل على تحميل، وتكلفة هذه التعليمات معروفة. تنتج الأجهزة عددًا معينًا من دورات الماكينة، لذلك يمكن حساب سرعة تنفيذ الأشياء المختلفة ببساطة عن طريق جمع كافة التعليمات التي ستقوم بتشغيلها. يمكن إضافة كل مقارنة/اختبار/فرع/استدعاء/تحميل/متجر والقول: هذا هو وقت التنفيذ المناسب لك. عند العمل على تحسين الأداء، سوف تنتبه بالتأكيد إلى الأرقام التي تتوافق مع الدورات الساخنة الصغيرة. 
ولكن بمجرد التبديل إلى Java وPython والأشياء المشابهة، فإنك تبتعد بسرعة كبيرة عن الأجهزة ذات المستوى المنخفض. ما هي تكلفة استدعاء getter في Java؟ إذا كان JIT في HotSpot صحيحًا مضمنة، سيتم تحميله، ولكن إذا لم يفعل ذلك، فسيتم استدعاء دالة. نظرًا لأن المكالمة تجري في حلقة فعالة، فإنها ستتجاوز كافة التحسينات الأخرى في تلك الحلقة. ولذلك فإن التكلفة الحقيقية ستكون أعلى من ذلك بكثير. وستفقد على الفور القدرة على النظر إلى جزء من التعليمات البرمجية وفهم أنه يجب علينا تنفيذه من حيث سرعة ساعة المعالج والذاكرة وذاكرة التخزين المؤقت المستخدمة. كل هذا يصبح مثيرًا للاهتمام فقط إذا دخلت حقًا في الأداء.
الآن نجد أنفسنا في موقف لم تزد فيه سرعات المعالج إلا بالكاد لمدة عقد من الزمن. الأيام الخوالي عادت! لم يعد بإمكانك الاعتماد على الأداء الجيد للخيط الواحد. ولكن إذا دخلت فجأة في الحوسبة المتوازية، فسيكون الأمر صعبًا للغاية، فالجميع ينظر إليك مثل جيمس بوند. عادة ما تحدث تسارعات بمقدار عشرة أضعاف هنا في الأماكن التي أفسد فيها شخص ما شيئًا ما. يتطلب التزامن الكثير من العمل. للحصول على هذا التسريع بمقدار XNUMXx، تحتاج إلى فهم نموذج التكلفة. ماذا وكم تكلف؟ وللقيام بذلك، عليك أن تفهم كيف يتناسب اللسان مع الأجهزة الأساسية.
اختار مارتن طومسون كلمة عظيمة لمدونته التعاطف الميكانيكي! أنت بحاجة إلى فهم ما ستفعله الأجهزة، وكيف ستفعل ذلك بالضبط، ولماذا تفعل ما تفعله في المقام الأول. باستخدام هذا، من السهل جدًا البدء في حساب التعليمات ومعرفة أين يتجه وقت التنفيذ. إذا لم يكن لديك التدريب المناسب، فأنت فقط تبحث عن قطة سوداء في غرفة مظلمة. أرى أشخاصًا يقومون بتحسين الأداء طوال الوقت وليس لديهم أي فكرة عما يفعلونه بحق الجحيم. إنهم يعانون كثيرًا ولا يحرزون تقدمًا كبيرًا. وعندما آخذ نفس الجزء من التعليمات البرمجية، وأدخل بعض الحيل الصغيرة وأحصل على تسريع بمقدار خمسة أو عشرة أضعاف، فإنهم يقولون: حسنًا، هذا ليس عدلاً، لقد عرفنا بالفعل أنك أفضل. مدهش. ما الذي أتحدث عنه... يدور نموذج التكلفة حول نوع الكود الذي تكتبه ومدى سرعة تشغيله في المتوسط ​​في الصورة الكبيرة.

أندرو: وكيف يمكنك الاحتفاظ بهذا الحجم في رأسك؟ فهل يتحقق ذلك بمزيد من الخبرة أم؟ من أين تأتي هذه الخبرة؟

جرف: حسنًا، لم أحصل على تجربتي بأسهل طريقة. لقد قمت بالبرمجة في Assembly في الأيام التي كان بإمكانك فيها فهم كل التعليمات. يبدو الأمر غبيا، ولكن منذ ذلك الحين ظلت مجموعة التعليمات Z80 دائما في رأسي، في ذاكرتي. لا أتذكر أسماء الأشخاص خلال دقيقة واحدة من الحديث، لكني أتذكر كودًا مكتوبًا منذ 40 عامًا. إنه أمر مضحك، يبدو وكأنه متلازمة "عالم احمق".

التدريب الأمثل على مستوى منخفض

أندرو: هل هناك طريقة أسهل للدخول؟

جرف: نعم و لا. الأجهزة التي نستخدمها جميعًا لم تتغير كثيرًا بمرور الوقت. يستخدم الجميع الإصدار x86، باستثناء هواتف Arm الذكية. إذا كنت لا تقوم بنوع من التضمين المتشدد، فأنت تفعل نفس الشيء. حسنا، التالي. التعليمات أيضًا لم تتغير لعدة قرون. عليك أن تذهب وتكتب شيئًا ما في التجميع. ليس كثيرًا، لكنه كافٍ للبدء في الفهم. أنت تبتسم، ولكنني أتحدث بجدية تامة. أنت بحاجة إلى فهم المراسلات بين اللغة والأجهزة. بعد ذلك عليك أن تذهب وتكتب قليلاً وتصنع مترجمًا صغيرًا للعبة صغيرة من لغة الألعاب. تشبه اللعبة يعني أنه يجب صنعها في فترة زمنية معقولة. يمكن أن يكون الأمر بسيطًا للغاية، لكن يجب أن يُنشئ تعليمات. ستساعدك عملية إنشاء التعليمات على فهم نموذج التكلفة للجسر بين التعليمات البرمجية عالية المستوى التي يكتبها الجميع ورمز الجهاز الذي يتم تشغيله على الأجهزة. سيتم حرق هذه المراسلات في الدماغ في وقت كتابة المترجم. حتى أبسط مترجم. بعد ذلك، يمكنك البدء في النظر إلى Java وحقيقة أن الهوة الدلالية الخاصة بها أعمق بكثير، وأن بناء الجسور فوقها أكثر صعوبة. في Java، يكون من الصعب جدًا فهم ما إذا كان الجسر الخاص بنا قد أصبح جيدًا أم سيئًا، وما الذي سيؤدي إلى انهياره وما الذي لن يؤدي إليه. لكنك تحتاج إلى نقطة بداية حيث تنظر إلى الكود وتفهم: "نعم، يجب أن يكون هذا الحرف مُضمنًا في كل مرة." ثم اتضح أن هذا يحدث في بعض الأحيان، باستثناء الموقف عندما تصبح الطريقة كبيرة جدًا، ويبدأ JIT في تضمين كل شيء. ويمكن التنبؤ بأداء مثل هذه الأماكن على الفور. عادة ما تعمل الحروف بشكل جيد، ولكن بعد ذلك تنظر إلى الحلقات الساخنة الكبيرة وتدرك أن هناك بعض استدعاءات الوظائف العائمة هناك والتي لا تعرف ما تفعله. هذه هي مشكلة الاستخدام الواسع النطاق للأحرف المستخدمة في الحروف، والسبب في عدم إدراجها هو أنه ليس من الواضح ما إذا كانت عبارة عن الحروف الأبجدية أم لا. إذا كان لديك قاعدة تعليمات برمجية صغيرة جدًا، فيمكنك ببساطة أن تتذكرها ثم تقول: هذا هو getter، وهذا هو setter. في قاعدة أكواد كبيرة، تعيش كل وظيفة قصتها الخاصة، والتي بشكل عام غير معروفة لأي شخص. يقول المحلل أننا فقدنا 24% من الوقت في بعض الحلقات، ولكي نفهم ما تفعله هذه الحلقة، نحتاج إلى النظر إلى كل وظيفة بداخلها. من المستحيل فهم ذلك دون دراسة الوظيفة، وهذا يبطئ عملية الفهم بشكل خطير. لهذا السبب لا أستخدم الحروف والأدوات، لقد وصلت إلى مستوى جديد!
أين يمكن الحصول على نموذج التكلفة؟ حسنًا، يمكنك قراءة شيء ما بالطبع... لكنني أعتقد أن أفضل طريقة هي التصرف. سيكون إنشاء مترجم صغير هو أفضل طريقة لفهم نموذج التكلفة وملاءمته في عقلك. يعد المترجم الصغير المناسب لبرمجة الميكروويف مهمة للمبتدئين. حسنًا، أعني، إذا كان لديك بالفعل مهارات البرمجة، فيجب أن يكون ذلك كافيًا. كل هذه الأشياء مثل تحليل سلسلة لديك كنوع من التعبير الجبري، واستخراج تعليمات العمليات الرياضية من هناك بالترتيب الصحيح، وأخذ القيم الصحيحة من السجلات - كل هذا يتم في وقت واحد. وأثناء قيامك بذلك، سيتم طبعه في دماغك. أعتقد أن الجميع يعرف ما يفعله المترجم. وهذا سيعطي فهمًا لنموذج التكلفة.

أمثلة عملية لتحسين الأداء

أندرو: ما الذي يجب عليك الانتباه إليه أيضًا عند العمل على الإنتاجية؟

جرف: هياكل البيانات. بالمناسبة، نعم، لم أقم بتدريس هذه الفصول منذ فترة طويلة... مدرسة الصواريخ. لقد كان الأمر ممتعًا، لكنه تطلب الكثير من الجهد، ولدي أيضًا حياة! نعم. لذلك، في أحد الفصول الكبيرة والمثيرة للاهتمام، "أين يتجه أدائك"، أعطيت الطلاب مثالاً: تمت قراءة 70 غيغابايت من بيانات التكنولوجيا المالية من ملف CSV ثم كان عليهم حساب عدد المنتجات المباعة . بيانات سوق القراد العادية. تم تحويل حزم UDP إلى تنسيق نصي منذ السبعينيات. بورصة شيكاغو التجارية - كل أنواع الأشياء مثل الزبدة والذرة وفول الصويا وأشياء من هذا القبيل. وكان من الضروري حساب هذه المنتجات، وعدد المعاملات، ومتوسط ​​حجم حركة الأموال والبضائع، وما إلى ذلك. إنها عملية حسابية بسيطة جدًا للتداول: ابحث عن رمز المنتج (الذي يتكون من حرف أو حرفين في جدول التجزئة)، واحصل على المبلغ، وأضفه إلى إحدى مجموعات التداول، وأضف الحجم، وأضف القيمة، وأشياء أخرى. الرياضيات بسيطة جدا. كان تنفيذ اللعبة واضحًا للغاية: كل شيء موجود في ملف، وأقرأ الملف وأتنقل خلاله، وأقسم السجلات الفردية إلى سلاسل Java، وأبحث عن الأشياء الضرورية فيها وأضيفها وفقًا للرياضيات الموضحة أعلاه. ويعمل بسرعة منخفضة.

مع هذا النهج، من الواضح ما يحدث، والحوسبة المتوازية لن تساعد، أليس كذلك؟ وتبين أنه يمكن تحقيق زيادة في الأداء بمقدار خمسة أضعاف ببساطة عن طريق اختيار هياكل البيانات الصحيحة. وهذا يفاجئ حتى المبرمجين ذوي الخبرة! في حالتي الخاصة، كانت الحيلة هي أنه لا ينبغي عليك إجراء عمليات تخصيص للذاكرة في حلقة فعالة. حسنًا، هذه ليست الحقيقة كاملة، ولكن بشكل عام - لا يجب عليك تسليط الضوء على "مرة واحدة في X" عندما يكون X كبيرًا بدرجة كافية. عندما تكون X XNUMX غيغابايت، لا ينبغي عليك تخصيص أي شيء "مرة لكل حرف"، أو "مرة لكل سطر"، أو "مرة لكل حقل"، أو أي شيء من هذا القبيل. هذا هو المكان الذي يقضي فيه الوقت. كيف يعمل هذا حتى؟ تخيل لي إجراء مكالمة String.split() أو BufferedReader.readLine(). Readline ينشئ سلسلة من مجموعة البايتات التي تأتي عبر الشبكة، مرة واحدة لكل سطر، لكل من مئات الملايين من الخطوط. آخذ هذا الخط وأحلله وأرميه بعيدًا. لماذا أرميها بعيدًا - حسنًا، لقد قمت بمعالجتها بالفعل، هذا كل شيء. لذلك، لكل بايت يتم قراءته من 2.7G، سيتم كتابة حرفين في السطر، أي 5.4G بالفعل، ولست بحاجة إليهما لأي شيء آخر، لذلك يتم التخلص منهما. إذا نظرت إلى عرض النطاق الترددي للذاكرة، فإننا نقوم بتحميل 2.7G الذي يمر عبر ناقل الذاكرة والذاكرة في المعالج، ثم يتم إرسال ضعف ذلك إلى الخط الموجود في الذاكرة، وكل هذا يتآكل عند إنشاء كل سطر جديد. لكنني بحاجة لقراءته، يقرأه الجهاز، حتى لو تآكل كل شيء لاحقًا. ويجب أن أكتبه لأنني أنشأت سطرًا وذاكرة التخزين المؤقت ممتلئة - لا يمكن لذاكرة التخزين المؤقت استيعاب 2.7 جيجا بايت. لذا، مقابل كل بايت أقرأه، أقرأ بايتين إضافيين وأكتب بايتين إضافيين، وفي النهاية لديهم نسبة 4:1 - وبهذه النسبة، فإننا نهدر النطاق الترددي للذاكرة. وبعد ذلك اتضح أنه إذا قمت بذلك String.split() – هذه ليست المرة الأخيرة التي أقوم فيها بذلك، فقد يكون هناك 6-7 حقول أخرى بالداخل. لذا فإن الكود الكلاسيكي لقراءة ملف CSV ثم تحليل السلاسل يؤدي إلى إهدار النطاق الترددي للذاكرة بحوالي 14:1 مقارنةً بما ترغب في الحصول عليه بالفعل. إذا قمت بتجاهل هذه التحديدات، يمكنك الحصول على تسريع خمسة أضعاف.

وهذا ليس بهذه الصعوبة. إذا نظرت إلى الكود من الزاوية اليمنى، يصبح الأمر بسيطًا للغاية بمجرد أن تدرك المشكلة. لا يجب أن تتوقف عن تخصيص الذاكرة تمامًا: المشكلة الوحيدة هي أنك تخصص شيئًا ما ويموت على الفور، وعلى طول الطريق يحرق موردًا مهمًا، وهو في هذه الحالة النطاق الترددي للذاكرة. وكل هذا يؤدي إلى انخفاض في الإنتاجية. في نظام التشغيل x86، تحتاج عادةً إلى حرق دورات المعالج بشكل نشط، ولكن هنا قمت بحرق الذاكرة بأكملها في وقت أبكر بكثير. الحل هو تقليل كمية الإفرازات. 
الجزء الآخر من المشكلة هو أنه إذا قمت بتشغيل ملف التعريف عندما ينفد شريط الذاكرة، مباشرة عند حدوث ذلك، فأنت عادةً تنتظر عودة ذاكرة التخزين المؤقت لأنها مليئة بالقمامة التي أنتجتها للتو، كل تلك السطور. لذلك، تصبح كل عملية تحميل أو تخزين بطيئة، لأنها تؤدي إلى فقدان ذاكرة التخزين المؤقت - أصبحت ذاكرة التخزين المؤقت بأكملها بطيئة، في انتظار خروج البيانات المهملة منها. لذلك، سيُظهر منشئ ملفات التعريف ضوضاء عشوائية دافئة متناثرة في جميع أنحاء الحلقة بأكملها - ولن يكون هناك أي تعليمات أو مكان ساخن منفصل في الكود. الضوضاء فقط. وإذا نظرت إلى دورات GC، فستجدها كلها من جيل الشباب وبسرعة فائقة - ميكروثانية أو ميلي ثانية كحد أقصى. بعد كل شيء، كل هذه الذاكرة تموت على الفور. أنت تخصص مليارات الجيجابايت، وهو يقطعها، ويقطعها، ويقطعها مرة أخرى. كل هذا يحدث بسرعة كبيرة. اتضح أن هناك دورات GC رخيصة، وضوضاء دافئة طوال الدورة بأكملها، لكننا نريد الحصول على تسريع 5x. في هذه اللحظة يجب أن يغلق شيء ما في رأسك ويصدر صوتًا: "لماذا هذا؟!" لا يتم عرض تجاوز سعة شريط الذاكرة في مصحح الأخطاء الكلاسيكي؛ تحتاج إلى تشغيل مصحح أخطاء عداد أداء الأجهزة ورؤيته بنفسك وبشكل مباشر. لكن هذا لا يمكن الشك فيه بشكل مباشر من خلال هذه الأعراض الثلاثة. العَرَض الثالث هو عندما تنظر إلى ما أبرزته، اسأل منشئ ملف التعريف، فيجيب: "لقد أنشأت مليار صف، لكن GC يعمل مجانًا". بمجرد حدوث ذلك، ستدرك أنك قمت بإنشاء عدد كبير جدًا من الكائنات وأحرقت مسار الذاكرة بأكمله. هناك طريقة لمعرفة ذلك، لكنها ليست واضحة. 

تكمن المشكلة في بنية البيانات: البنية العارية الكامنة وراء كل ما يحدث، وهي كبيرة جدًا، ويبلغ حجمها 2.7 جيجا بايت على القرص، لذا فإن إنشاء نسخة من هذا الشيء أمر غير مرغوب فيه للغاية - فأنت تريد تحميله من المخزن المؤقت بايت للشبكة على الفور في السجلات، حتى لا تتم القراءة والكتابة على السطر ذهابًا وإيابًا خمس مرات. لسوء الحظ، لا توفر لك Java مثل هذه المكتبة كجزء من JDK بشكل افتراضي. ولكن هذا أمر تافه، أليس كذلك؟ في الأساس، هذه هي 5-10 أسطر من التعليمات البرمجية التي سيتم استخدامها لتنفيذ أداة تحميل السلسلة المخزنة مؤقتًا، والتي تكرر سلوك فئة السلسلة، بينما تكون غلافًا حول المخزن المؤقت للبايت الأساسي. نتيجة لذلك، اتضح أنك تعمل تقريبًا كما لو كنت تعمل مع سلاسل، ولكن في الواقع تتحرك المؤشرات إلى المخزن المؤقت هناك، ولا يتم نسخ البايتات الأولية في أي مكان، وبالتالي يتم إعادة استخدام نفس المخازن المؤقتة مرارًا وتكرارًا، و يسعد نظام التشغيل أن يأخذ على عاتقه الأشياء التي تم تصميمه من أجلها، مثل التخزين المؤقت المزدوج المخفي لمخازن البايت المؤقتة هذه، ولم تعد تطحن من خلال دفق لا نهاية له من البيانات غير الضرورية. بالمناسبة، هل تفهم أنه عند العمل مع GC، يتم ضمان أن كل تخصيص للذاكرة لن يكون مرئيًا للمعالج بعد دورة GC الأخيرة؟ لذلك، لا يمكن أن يكون كل هذا موجودًا في ذاكرة التخزين المؤقت، ومن ثم يحدث خطأ مضمون بنسبة 100%. عند العمل باستخدام مؤشر، على x86، يستغرق طرح السجل من الذاكرة 1-2 دورات على مدار الساعة، وبمجرد حدوث ذلك، تدفع، تدفع، تدفع، لأن الذاكرة كلها قيد التشغيل تسعة مخابئ – وهذه هي تكلفة تخصيص الذاكرة. القيمة الحقيقية.

وبعبارة أخرى، فإن هياكل البيانات هي أصعب شيء يمكن تغييره. وبمجرد أن تدرك أنك اخترت بنية البيانات الخاطئة التي ستؤدي إلى توقف الأداء لاحقًا، فعادةً ما يكون هناك الكثير من العمل الذي يتعين عليك القيام به، ولكن إذا لم تفعل ذلك، فستزداد الأمور سوءًا. أولا وقبل كل شيء، عليك أن تفكر في هياكل البيانات، وهذا أمر مهم. تقع التكلفة الرئيسية هنا على هياكل البيانات السميكة، والتي بدأ استخدامها بأسلوب "لقد قمت بنسخ بنية البيانات X إلى بنية البيانات Y لأنني أحب شكل Y بشكل أفضل." لكن عملية النسخ (التي تبدو رخيصة) تهدر في الواقع النطاق الترددي للذاكرة، وهذا هو المكان الذي يتم فيه دفن كل وقت التنفيذ الضائع. إذا كان لدي سلسلة عملاقة من JSON وأريد تحويلها إلى شجرة DOM منظمة من POJOs أو شيء من هذا القبيل، فإن عملية تحليل تلك السلسلة وبناء POJO، ثم الوصول إلى POJO مرة أخرى لاحقًا، ستؤدي إلى تكلفة غير ضرورية - إنها ليست رخيصة. إلا إذا كنت تركض حول POJOs كثيرًا أكثر مما تركض حول سلسلة. مرتجلاً، يمكنك بدلاً من ذلك محاولة فك تشفير السلسلة واستخراج ما تحتاجه فقط من هناك، دون تحويلها إلى أي POJO. إذا حدث كل هذا على المسار الذي يتطلب أقصى قدر من الأداء، ولا توجد POJOs بالنسبة لك، فأنت بحاجة إلى البحث في السطر بطريقة ما مباشرة.

لماذا تنشئ لغة البرمجة الخاصة بك؟

أندرو: قلت أنه من أجل فهم نموذج التكلفة، تحتاج إلى كتابة لغتك الصغيرة ...

جرف: ليست لغة، ولكن مترجم. اللغة والمترجم شيئان مختلفان. الفرق الأكثر أهمية هو في رأسك. 

أندرو: بالمناسبة، على حد علمي، أنت تقوم بتجربة إنشاء لغاتك الخاصة. لماذا؟

جرف: لأني أستطيع! أنا شبه متقاعد، وهذه هوايتي. طوال حياتي كنت أطبق لغات الآخرين. لقد عملت أيضًا كثيرًا على أسلوب الترميز الخاص بي. وأيضًا لأنني أرى مشاكل في اللغات الأخرى. أرى أن هناك طرقًا أفضل للقيام بالأشياء المألوفة. وسأستخدمها. لقد سئمت من رؤية المشاكل في نفسي، في Java، في Python، في أي لغة أخرى. أكتب الآن باستخدام React Native وJavaScript وElm كهواية لا تتعلق بالتقاعد، بل بالعمل النشط. أنا أكتب أيضًا بلغة Python، وعلى الأرجح سأواصل العمل على التعلم الآلي لواجهات Java الخلفية. هناك العديد من اللغات الشائعة وجميعها لديها ميزات مثيرة للاهتمام. كل شخص جيد بطريقته الخاصة ويمكنك محاولة الجمع بين كل هذه الميزات معًا. لذا، فأنا أدرس الأشياء التي تهمني، سلوك اللغة، وأحاول التوصل إلى دلالات منطقية. وحتى الآن أنا ناجح! في الوقت الحالي، أعاني من دلالات الذاكرة، لأنني أريد أن يكون الأمر كما هو الحال في C وJava، وأن أحصل على نموذج ذاكرة قوي ودلالات ذاكرة للتحميل والمخازن. وفي الوقت نفسه، احصل على استنتاج تلقائي للنوع كما هو الحال في هاسكل. هنا، أحاول مزج استنتاج النوع المشابه لـ Haskell مع عمل الذاكرة في كل من C وJava. هذا ما كنت أفعله خلال الشهرين أو الثلاثة أشهر الماضية، على سبيل المثال.

أندرو: إذا قمت ببناء لغة تأخذ جوانب أفضل من اللغات الأخرى، هل تعتقد أن شخص ما سوف يفعل العكس: يأخذ أفكارك ويستخدمها؟

جرف: هذا هو بالضبط كيف تظهر اللغات الجديدة! لماذا تشبه Java لغة C؟ نظرًا لأن لغة C كانت لديها بنية جيدة يفهمها الجميع، وقد استلهمت Java من هذه البنية، مضيفة أمان النوع، والتحقق من حدود المصفوفة، وGC، كما قاموا أيضًا بتحسين بعض الأشياء من لغة C. لقد أضافوا أشياء خاصة بهم. لكنهم ألهموا الكثير، أليس كذلك؟ الجميع يقف على أكتاف العمالقة الذين سبقوك، هكذا يتم إحراز التقدم.

أندرو: كما أفهمها، ستكون لغتك آمنة للذاكرة. هل فكرت في تنفيذ شيء مثل مدقق الاقتراض من Rust؟ هل نظرت إليه، ما رأيك فيه؟

جرف: حسنًا، لقد كنت أكتب لغة C منذ زمن طويل، مع كل هذا المالوك والمجاني، وإدارة العمر يدويًا. كما تعلمون، 90-95% من فترة الحياة التي يتم التحكم فيها يدويًا لها نفس البنية. ومن المؤلم جدًا القيام بذلك يدويًا. أود أن يخبرك المترجم ببساطة بما يجري هناك وما حققته من خلال أفعالك. بالنسبة لبعض الأشياء، يقوم مدقق الاقتراض بذلك خارج الصندوق. ويجب أن يعرض المعلومات تلقائيًا، ويفهم كل شيء، ولا يثقل كاهلي بتقديم هذا الفهم. يجب أن تقوم على الأقل بتحليل الهروب المحلي، وفقط في حالة فشلها، فإنها تحتاج إلى إضافة تعليقات توضيحية للنوع تصف مدى الحياة - ومثل هذا المخطط أكثر تعقيدًا بكثير من مدقق الاقتراض، أو في الواقع أي مدقق ذاكرة موجود. الاختيار بين "كل شيء على ما يرام" و "أنا لا أفهم أي شيء" - لا، يجب أن يكون هناك شيء أفضل. 
لذا، باعتباري شخصًا كتب الكثير من الأكواد البرمجية بلغة C، أعتقد أن الحصول على دعم للتحكم التلقائي مدى الحياة هو الشيء الأكثر أهمية. لقد سئمت أيضًا من مقدار استخدام Java للذاكرة والشكوى الرئيسية هي GC. عندما تقوم بتخصيص الذاكرة في Java، فلن تتمكن من استعادة الذاكرة التي كانت محلية في دورة GC الأخيرة. هذا ليس هو الحال في اللغات ذات الإدارة الأكثر دقة للذاكرة. إذا اتصلت بـ malloc، فستحصل على الفور على الذاكرة التي تم استخدامها للتو. عادةً ما تقوم ببعض الأشياء المؤقتة بالذاكرة وتعيدها على الفور. ويعود على الفور إلى حوض سباحة malloc، وتقوم دورة malloc التالية بسحبه مرة أخرى. لذلك، يتم تقليل استخدام الذاكرة الفعلي إلى مجموعة الكائنات الحية في وقت معين، بالإضافة إلى التسريبات. وإذا لم يتسرب كل شيء بطريقة غير لائقة تمامًا، فإن معظم الذاكرة تنتهي في ذاكرة التخزين المؤقت والمعالج، وتعمل بسرعة. ولكنه يتطلب الكثير من إدارة الذاكرة اليدوية باستخدام malloc واستدعاء مجاني بالترتيب الصحيح وفي المكان المناسب. يمكن لـ Rust التعامل مع هذا بشكل صحيح من تلقاء نفسه، وفي كثير من الحالات يقدم أداء أفضل، حيث يتم تضييق استهلاك الذاكرة إلى الحساب الحالي فقط - بدلاً من انتظار دورة GC التالية لتحرير الذاكرة. ونتيجة لذلك، حصلنا على طريقة مثيرة للاهتمام للغاية لتحسين الأداء. وهو قوي جدًا - أعني أنني فعلت مثل هذه الأشياء عند معالجة البيانات الخاصة بالتكنولوجيا المالية، وهذا سمح لي بالحصول على سرعة تصل إلى خمس مرات تقريبًا. يعد هذا بمثابة دفعة كبيرة جدًا، خاصة في عالم لا تزداد فيه سرعة المعالجات وما زلنا ننتظر التحسينات.

مهنة مهندس الأداء

أندرو: أود أيضًا أن أسأل عن الوظائف بشكل عام. لقد صعدت إلى الشهرة من خلال عملك في JIT في HotSpot ثم انتقلت إلى Azul، وهي أيضًا إحدى شركات JVM. لكننا كنا نعمل بالفعل على الأجهزة أكثر من البرامج. ثم تحولوا فجأة إلى البيانات الضخمة والتعلم الآلي، ثم إلى اكتشاف الاحتيال. كيف حدث هذا؟ هذه مجالات مختلفة جدًا للتنمية.

جرف: لقد قمت بالبرمجة لفترة طويلة وتمكنت من حضور الكثير من الفصول المختلفة. وعندما يقول الناس: "أوه، أنت من قام بتنفيذ JIT لـ Java!"، يكون الأمر مضحكًا دائمًا. ولكن قبل ذلك، كنت أعمل على نسخة من لغة PostScript، وهي اللغة التي استخدمتها شركة Apple ذات مرة في طابعات الليزر الخاصة بها. وقبل ذلك قمت بتطبيق اللغة الرابعة. أعتقد أن الموضوع المشترك بالنسبة لي هو تطوير الأدوات. طوال حياتي كنت أصنع الأدوات التي يكتب بها الآخرون برامجهم الرائعة. لكنني شاركت أيضًا في تطوير أنظمة التشغيل، وبرامج التشغيل، ومصححات الأخطاء على مستوى kernel، ولغات تطوير نظام التشغيل، والتي بدأت تافهة، ولكنها أصبحت بمرور الوقت أكثر تعقيدًا. لكن الموضوع الرئيسي لا يزال هو تطوير الأدوات. لقد مر جزء كبير من حياتي بين أزول وصن، وكان الأمر يتعلق بجافا. ولكن عندما دخلت في مجال البيانات الضخمة والتعلم الآلي، ارتديت قبعتي الفاخرة مرة أخرى وقلت: "أوه، الآن لدينا مشكلة غير تافهة، وهناك الكثير من الأشياء المثيرة للاهتمام التي تحدث والأشخاص الذين يقومون بأشياء". هذا طريق تطوير عظيم يجب اتباعه.

نعم، أنا حقًا أحب الحوسبة الموزعة. كانت وظيفتي الأولى عندما كنت طالبًا في لغة C، في مشروع إعلاني. تم توزيع هذه الحوسبة على شرائح Zilog Z80 التي جمعت البيانات للتعرف الضوئي على الحروف التناظري، والتي تم إنتاجها بواسطة محلل تناظري حقيقي. لقد كان موضوعًا رائعًا ومجنونًا تمامًا. ولكن كانت هناك مشاكل، لم يتم التعرف على جزء ما بشكل صحيح، لذلك كان عليك إخراج صورة وإظهارها لشخص يمكنه بالفعل القراءة بأعينه والإبلاغ عما تقوله، وبالتالي كانت هناك وظائف تحتوي على بيانات، وهذه الوظائف كان لديهم لغتهم الخاصة. كانت هناك واجهة خلفية تعالج كل هذا - تعمل أجهزة Z80 بالتوازي مع تشغيل محطات vt100 - واحد لكل شخص، وكان هناك نموذج برمجة متوازي على Z80. جزء مشترك من الذاكرة مشترك بين جميع سيارات Z80 ضمن التكوين النجمي؛ تمت أيضًا مشاركة اللوحة الإلكترونية المعززة، وتم مشاركة نصف ذاكرة الوصول العشوائي داخل الشبكة، والنصف الآخر كان خاصًا أو ذهب إلى شيء آخر. نظام موزع متوازي ومعقد بشكل هادف مع ذاكرة مشتركة وشبه مشتركة. متى كان هذا... لا أستطيع حتى أن أتذكر، في مكان ما في منتصف الثمانينات. منذ وقت طويل جدا. 
نعم، لنفترض أن ثلاثين عاما هي فترة طويلة جدا. فالمشاكل المرتبطة بالحوسبة الموزعة كانت موجودة منذ فترة طويلة؛ وكان الناس لفترة طويلة في حالة حرب مع الحوسبة الموزعة. بياولف-عناقيد المجموعات. تبدو هذه المجموعات كما يلي... على سبيل المثال: يوجد Ethernet وجهاز x86 السريع الخاص بك متصل بهذا Ethernet، والآن تريد الحصول على ذاكرة مشتركة مزيفة، لأنه لم يكن بإمكان أحد القيام بترميز الحوسبة الموزعة في ذلك الوقت، كان الأمر صعبًا للغاية وبالتالي كان هناك كانت ذاكرة مشتركة مزيفة مع صفحات ذاكرة حماية على x86، وإذا كتبت إلى هذه الصفحة، فقلنا للمعالجات الأخرى أنه إذا وصلوا إلى نفس الذاكرة المشتركة، فسيلزم تحميلها منك، وبالتالي شيء يشبه بروتوكول الدعم ظهر تماسك ذاكرة التخزين المؤقت والبرمجيات لهذا الغرض. مفهوم مثير للاهتمام. المشكلة الحقيقية، بالطبع، كانت شيئًا آخر. نجح كل هذا، ولكن سرعان ما واجهت مشاكل في الأداء، لأنه لم يفهم أحد نماذج الأداء بمستوى جيد بما فيه الكفاية - ما هي أنماط الوصول إلى الذاكرة الموجودة، وكيفية التأكد من أن العقد لا تقوم باختبار اتصال بعضها البعض إلى ما لا نهاية، وما إلى ذلك.

ما توصلت إليه في H2O هو أن المطورين أنفسهم هم المسؤولون عن تحديد مكان إخفاء التوازي وأين لا يكون. لقد توصلت إلى نموذج ترميز يجعل كتابة التعليمات البرمجية عالية الأداء أمرًا سهلاً وبسيطًا. لكن كتابة تعليمات برمجية بطيئة التشغيل أمر صعب، وسيبدو سيئًا. تحتاج إلى محاولة كتابة تعليمات برمجية بطيئة بجدية، وسيتعين عليك استخدام أساليب غير قياسية. رمز الكبح مرئي للوهلة الأولى. ونتيجة لذلك، عادةً ما تكتب تعليمات برمجية تعمل بسرعة، ولكن عليك معرفة ما يجب فعله في حالة الذاكرة المشتركة. كل هذا مرتبط بمصفوفات كبيرة والسلوك هناك مشابه للمصفوفات الكبيرة غير المتطايرة في Java المتوازية. أعني، تخيل أن خيطين يكتبان إلى مصفوفة متوازية، أحدهما يفوز، والآخر يخسر، ولا تعرف أيهما. إذا لم تكن متقلبة، فيمكن أن يكون الأمر كما تريد - وهذا يعمل بشكل جيد حقا. يهتم الناس حقًا بترتيب العمليات، فهم يضعون العناصر المتقلبة في الأماكن الصحيحة، ويتوقعون حدوث مشكلات في الأداء متعلقة بالذاكرة في الأماكن الصحيحة. بخلاف ذلك، فإنهم سيكتبون ببساطة التعليمات البرمجية في شكل حلقات من 1 إلى N، حيث N هي بضعة تريليونات، على أمل أن تصبح جميع الحالات المعقدة متوازية تلقائيًا - وهذا لا يعمل هناك. لكن في H2O، هذه ليست Java ولا Scala، يمكنك اعتبارها "Java ناقص ناقص" إذا أردت. يعد هذا أسلوب برمجة واضحًا للغاية ويشبه كتابة تعليمات برمجية بسيطة بلغة C أو Java باستخدام الحلقات والمصفوفات. ولكن في الوقت نفسه، يمكن معالجة الذاكرة بالتيرابايت. ما زلت أستخدم H2O. أستخدمه من وقت لآخر في مشاريع مختلفة - ولا يزال أسرع شيء، أسرع بعشرات المرات من منافسيه. إذا كنت تتعامل مع البيانات الضخمة ببيانات عمودية، فمن الصعب جدًا التغلب على H2O.

التحديات التقنية

أندرو: ما هو التحدي الأكبر الذي واجهته في حياتك المهنية بأكملها؟

جرف: هل نناقش الجزء الفني أو غير الفني من المسألة؟ أود أن أقول إن أكبر التحديات ليست تقنية. 
أما بالنسبة للتحديات التقنية. لقد هزمتهم ببساطة. لا أعرف حتى ما هو أكبرها، ولكن كانت هناك بعض الأشياء المثيرة للاهتمام التي استغرقت وقتًا طويلاً، وصراعًا ذهنيًا. عندما ذهبت إلى Sun، كنت متأكدًا من أنني سأقوم بإنشاء مترجم سريع، وقال مجموعة من كبار السن ردًا على ذلك إنني لن أنجح أبدًا. لكنني اتبعت هذا المسار، وكتبت مترجمًا إلى مُخصص السجل، وكان سريعًا جدًا. لقد كان سريعًا مثل C1 الحديث، لكن المُخصص كان أبطأ بكثير في ذلك الوقت، وبعد فوات الأوان كانت هناك مشكلة كبيرة في بنية البيانات. كنت بحاجة إليه لكتابة مُخصص تسجيل رسومي ولم أفهم المعضلة بين تعبير الكود والسرعة، التي كانت موجودة في تلك الحقبة وكانت مهمة جدًا. اتضح أن بنية البيانات عادة ما تتجاوز حجم ذاكرة التخزين المؤقت على x86s في ذلك الوقت، وبالتالي، إذا افترضت في البداية أن مخصص التسجيل سيعمل بنسبة 5-10 بالمائة من إجمالي وقت الارتعاش، فقد تبين في الواقع أنه 50 في المئة.

مع مرور الوقت، أصبح المترجم أكثر نظافة وكفاءة، وتوقف عن توليد تعليمات برمجية رهيبة في المزيد من الحالات، وبدأ الأداء بشكل متزايد يشبه ما ينتجه مترجم C. ما لم تكتب، بالطبع، بعض الهراء الذي حتى لغة C لا تزيد سرعته . إذا كتبت تعليمات برمجية مثل C، فستحصل على أداء مثل C في المزيد من الحالات. وكلما ذهبت أبعد، كلما حصلت على رمز يتزامن بشكل غير مقارب مع المستوى C، بدأ مُخصص السجل يبدو وكأنه شيء مكتمل... بغض النظر عما إذا كان الكود الخاص بك يعمل بسرعة أو ببطء. واصلت العمل على المُخصص لجعله يقوم باختيارات أفضل. لقد أصبح أبطأ وأبطأ، لكنه قدم أداء أفضل وأفضل في الحالات التي لم يتمكن أي شخص آخر من التعامل معها. يمكنني الغوص في مُخصص التسجيل، ودفن شهر من العمل هناك، وفجأة سيبدأ الكود بأكمله في التنفيذ بشكل أسرع بنسبة 5٪. حدث هذا مرة تلو الأخرى وأصبح مُخصص التسجيل بمثابة عمل فني - أحبه الجميع أو كرهوه، وطرح الأشخاص من الأكاديمية أسئلة حول موضوع "لماذا يتم كل شيء بهذه الطريقة"، لماذا لا مسح الخط، وما هو الفرق. الجواب لا يزال هو نفسه: إن المُخصص المعتمد على تلوين الرسم البياني بالإضافة إلى العمل الدقيق للغاية مع الكود العازل يساوي سلاح النصر، وهو أفضل مزيج لا يستطيع أحد هزيمته. وهذا شيء غير واضح إلى حد ما. كل شيء آخر يفعله المترجم هو أشياء مدروسة جيدًا إلى حد ما، على الرغم من أنها وصلت أيضًا إلى مستوى الفن. لقد قمت دائمًا بأشياء كان من المفترض أن تحول المترجم إلى عمل فني. لكن لم يكن أي من هذا شيئًا غير عادي - باستثناء مُخصص السجل. الحيلة هي أن نكون حذرين انهي الأمر تحت الحمل، وإذا حدث هذا (يمكنني أن أشرح بمزيد من التفصيل إذا كنت مهتمًا)، فهذا يعني أنه يمكنك التضمين بشكل أكثر قوة، دون التعرض لخطر الوقوع في مشكلة في جدول الأداء. في تلك الأيام، كان هناك مجموعة من المترجمين على نطاق واسع، معلقين بالحلي والصفارات، وكان لديهم مخصصات مسجلة، لكن لم يكن بإمكان أي شخص آخر القيام بذلك.

تكمن المشكلة في أنه إذا قمت بإضافة طرق تخضع للتضمين وزيادة مساحة التضمين وزيادتها، فإن مجموعة القيم المستخدمة تفوق على الفور عدد السجلات، وسيتعين عليك قطعها. عادة ما يأتي المستوى الحرج عندما يستسلم المُخصص، ويكون أحد المرشحين الجيدين للتسرب يستحق آخر، فسوف تبيع بعض الأشياء الجامحة بشكل عام. تتمثل قيمة التضمين هنا في فقدان جزء من النفقات العامة للاتصال والحفظ، ويمكنك رؤية القيم بالداخل ويمكنك تحسينها بشكل أكبر. تكلفة التضمين هي أنه يتم تكوين عدد كبير من القيم الحية، وإذا احترق مخصص السجل الخاص بك أكثر من اللازم، فستخسر على الفور. لذلك، يواجه معظم الموزعين مشكلة: عندما تتجاوز البطانة خطًا معينًا، يبدأ كل شيء في العالم في التخفيض ويمكن التخلص من الإنتاجية في المرحاض. يضيف أولئك الذين يقومون بتنفيذ المترجم بعض الاستدلال: على سبيل المثال، التوقف عن التضمين، بدءًا من حجم كبير بما فيه الكفاية، لأن المخصصات ستدمر كل شيء. هذه هي الطريقة التي يتم بها تشكيل التواء في الرسم البياني للأداء - أنت مضمن، مضمن، ينمو الأداء ببطء - ثم يزدهر! - يسقط مثل الرافعة السريعة لأنك اصطفت كثيرًا. هذه هي الطريقة التي كان يعمل بها كل شيء قبل ظهور جافا. تتطلب Java الكثير من التضمين، لذلك اضطررت إلى جعل المُخصص الخاص بي أكثر عدوانية بحيث يتم ضبطه بدلاً من أن يتعطل، وإذا قمت بتضمينه أكثر من اللازم، فإنه يبدأ في الانسكاب، ولكن بعد ذلك تأتي لحظة "لا مزيد من الإراقة". هذه ملاحظة مثيرة للاهتمام وقد جاءتني من العدم، ولم تكن واضحة، لكنها أتت بثمارها جيدًا. لقد بدأت عملية التضمين العدوانية وأخذتني إلى أماكن يعمل فيها أداء Java وC جنبًا إلى جنب. إنهما قريبان جدًا - يمكنني كتابة كود Java أسرع بكثير من كود C وأشياء من هذا القبيل، ولكن في المتوسط، في الصورة الكبيرة للأشياء، فهي قابلة للمقارنة تقريبًا. أعتقد أن جزءًا من هذه الميزة هو مُخصص التسجيل، والذي يسمح لي بالتضمين بغباء قدر الإمكان. أنا فقط أضمن كل ما أراه. السؤال هنا هو ما إذا كان المُخصص يعمل بشكل جيد، وما إذا كانت النتيجة هي كود يعمل بذكاء. لقد كان هذا تحديًا كبيرًا: فهم كل هذا وإنجاحه.

قليلا عن تخصيص التسجيل والنوى المتعددة

فلاديمير: تبدو مشاكل مثل تخصيص التسجيل وكأنها موضوع أبدي لا نهاية له. أتساءل عما إذا كانت هناك فكرة بدت واعدة ثم فشلت في التنفيذ؟

جرف: بالتأكيد! تخصيص السجل هو المجال الذي تحاول فيه العثور على بعض الاستدلالات لحل مشكلة NP-Complete. ولا يمكنك أبدًا التوصل إلى حل مثالي، أليس كذلك؟ وهذا ببساطة مستحيل. انظر، تجميع Ahead of Time - إنه يعمل أيضًا بشكل سيء. الحديث هنا يدور حول بعض الحالات المتوسطة. فيما يتعلق بالأداء النموذجي، حتى تتمكن من قياس شيء تعتقد أنه أداء نموذجي جيد - ففي النهاية، أنت تعمل على تحسينه! تخصيص التسجيل هو موضوع يتعلق بالأداء. بمجرد حصولك على النموذج الأولي، فإنه يعمل ويرسم ما هو مطلوب، ويبدأ عمل الأداء. عليك أن تتعلم القياس جيدًا. لماذا هو مهم؟ إذا كانت لديك بيانات واضحة، فيمكنك النظر إلى مناطق مختلفة ورؤية: نعم، لقد ساعد ذلك هنا، ولكن هذا هو المكان الذي تحطم فيه كل شيء! تظهر بعض الأفكار الجيدة، وتضيف استدلالات جديدة وفجأة يبدأ كل شيء في العمل بشكل أفضل قليلاً في المتوسط. أو لا يبدأ. كان لدي مجموعة من الحالات حيث كنا نناضل من أجل أداء الخمسة بالمائة الذي ميز تطورنا عن المُخصص السابق. وفي كل مرة يبدو الأمر هكذا: في مكان ما تفوز، وفي مكان ما تخسر. إذا كان لديك أدوات جيدة لتحليل الأداء، فيمكنك العثور على الأفكار الخاسرة وفهم سبب فشلها. ربما يكون من المفيد ترك كل شيء كما هو، أو ربما اتباع نهج أكثر جدية في الضبط الدقيق، أو الخروج وإصلاح شيء آخر. إنها مجموعة كاملة من الأشياء! لقد قمت بعمل هذا الاختراق الرائع، ولكنني أحتاج أيضًا إلى هذا وهذا وهذا - ومجموعهم الإجمالي يعطي بعض التحسينات. ويمكن أن يفشل المنعزلون. هذه هي طبيعة العمل الأداءي على مشاكل NP-Complete.

فلاديمير: يشعر المرء أن أشياء مثل الرسم في المخصصات هي مشكلة تم حلها بالفعل. حسنًا، لقد تقرر الأمر بالنسبة لك، بناءً على ما تقوله، فهل الأمر يستحق ذلك إذن...

جرف: لم يتم حلها على هذا النحو. أنت من يجب أن يحولها إلى "محلولة". هناك مشاكل صعبة ويجب حلها. بمجرد الانتهاء من ذلك، حان الوقت للعمل على الإنتاجية. يتعين عليك التعامل مع هذا العمل وفقًا لذلك - إجراء معايير، وجمع المقاييس، وشرح المواقف عندما، عند العودة إلى الإصدار السابق، بدأ الاختراق القديم في العمل مرة أخرى (أو العكس، توقف). ولا تستسلم حتى تحقق شيئاً. كما قلت بالفعل، إذا كانت هناك أفكار رائعة لم تنجح، ولكن في مجال تخصيص سجلات الأفكار، فهي لا نهاية لها تقريبًا. يمكنك، على سبيل المثال، قراءة المنشورات العلمية. على الرغم من أن هذه المنطقة بدأت الآن تتحرك بشكل أبطأ بكثير وأصبحت أكثر وضوحًا مما كانت عليه في شبابها. ومع ذلك، هناك عدد لا يحصى من الأشخاص الذين يعملون في هذا المجال وجميع أفكارهم تستحق المحاولة، وجميعهم في انتظارهم. ولا يمكنك معرفة مدى جودتها إلا إذا قمت بتجربتها. ما مدى جودة تكاملها مع كل شيء آخر في المُخصص الخاص بك، لأن المُخصص يقوم بالكثير من الأشياء، وبعض الأفكار لن تعمل في المُخصص المحدد الخاص بك، ولكن في مُخصص آخر ستنجح بسهولة. الطريقة الرئيسية للفوز للمخصص هي سحب الأشياء البطيئة خارج المسار الرئيسي وإجبارها على الانقسام على طول حدود المسارات البطيئة. لذلك، إذا كنت ترغب في تشغيل GC، فاتبع المسار البطيء، وقم بإلغاء التحسين، وطرح استثناء، وكل هذه الأشياء - فأنت تعلم أن هذه الأشياء نادرة نسبيًا. لقد تحققت من أنهم نادرون حقًا. أنت تقوم بعمل إضافي ويزيل الكثير من القيود المفروضة على هذه المسارات البطيئة، لكن هذا لا يهم حقًا لأنها بطيئة ونادرا ما يتم السفر إليها. على سبيل المثال، مؤشر فارغ - لا يحدث أبدًا، أليس كذلك؟ يجب أن يكون لديك عدة مسارات لأشياء مختلفة، لكن لا ينبغي أن تتداخل مع المسار الرئيسي. 

فلاديمير: ما رأيك في النوى المتعددة، عندما يكون هناك آلاف النوى في وقت واحد؟ هل هذا شيء مفيد؟

جرف: نجاح GPU يدل على أنه مفيد للغاية!

فلاديمير: إنهم متخصصون تمامًا. ماذا عن المعالجات للأغراض العامة؟

جرف: حسنًا، كان هذا هو نموذج عمل Azul. جاءت الإجابة في عصر أحب فيه الناس حقًا الأداء المتوقع. كان من الصعب كتابة كود متوازي في ذلك الوقت. يعتبر نموذج الترميز H2O قابلاً للتطوير بشكل كبير، ولكنه ليس نموذجًا للأغراض العامة. ربما يكون الأمر أكثر عمومية قليلاً من استخدام وحدة معالجة الرسومات. هل نتحدث عن تعقيد تطوير مثل هذا الشيء أم تعقيد استخدامه؟ على سبيل المثال، علمني Azul درسًا مثيرًا للاهتمام، وهو درس غير واضح إلى حد ما: تعد المخابئ الصغيرة أمرًا طبيعيًا. 

التحدي الأكبر في الحياة

فلاديمير: وماذا عن التحديات غير التقنية؟

جرف: التحدي الأكبر لم يكن أن تكون... لطيفًا ولطيفًا مع الناس. ونتيجة لذلك، وجدت نفسي باستمرار في مواقف الصراع للغاية. تلك التي كنت أعلم أن الأمور تسير فيها بشكل خاطئ، لكنني لم أكن أعرف كيفية المضي قدمًا في حل هذه المشكلات ولم أتمكن من التعامل معها. ونشأت بهذه الطريقة العديد من المشاكل طويلة الأمد، والتي استمرت لعقود من الزمن. حقيقة أن Java تحتوي على مترجمين C1 وC2 هي نتيجة مباشرة لذلك. حقيقة عدم وجود تجميع متعدد المستويات في Java لمدة عشر سنوات متتالية هي أيضًا نتيجة مباشرة. ومن الواضح أننا كنا في حاجة إلى مثل هذا النظام، ولكن ليس من الواضح سبب عدم وجوده. واجهت مشاكل مع مهندس واحد... أو مجموعة من المهندسين. ذات مرة، عندما بدأت العمل في شركة صن، كنت... حسنًا، ليس عندها فقط، دائمًا ما يكون لدي رأيي الخاص في كل شيء. واعتقدت أنه من الصحيح أنه يمكنك أخذ هذه الحقيقة الخاصة بك وإخبارها وجهاً لوجه. خاصة وأنني كنت على حق بشكل صادم معظم الوقت. وإذا كنت لا تحب هذا الأسلوب... خاصة إذا كنت مخطئًا بشكل واضح وتفعل هراء... بشكل عام، قلة من الناس يمكنهم تحمل هذا النوع من التواصل. على الرغم من أن البعض يستطيع، مثلي. لقد بنيت حياتي كلها على مبادئ الجدارة. إذا أظهرت لي شيئًا خاطئًا، فسوف أستدير على الفور وأقول: لقد قلت هراء. وفي نفس الوقت أعتذر بالطبع، وكل ذلك وسألاحظ الوقائع إن وجدت، وأتخذ الإجراءات الصحيحة الأخرى. ومن ناحية أخرى، أنا على حق بشكل صادم فيما يتعلق بنسبة كبيرة بشكل صادم من إجمالي الوقت. ولا يعمل بشكل جيد في العلاقات مع الناس. لا أحاول أن أكون لطيفًا، لكني أطرح السؤال بصراحة. "هذا لن ينجح أبدًا، لأن واحد واثنين وثلاثة." وكانوا مثل، "أوه!" وكانت هناك عواقب أخرى ربما كان من الأفضل تجاهلها: على سبيل المثال، تلك التي أدت إلى الطلاق من زوجتي وعشر سنوات من الاكتئاب بعد ذلك.

التحدي هو صراع مع الناس، مع إدراكهم لما يمكنك وما لا يمكنك فعله، وما هو مهم وما هو غير مهم. كان هناك العديد من التحديات حول أسلوب البرمجة. ما زلت أكتب الكثير من الأكواد البرمجية، وفي تلك الأيام اضطررت إلى التباطؤ لأنني كنت أقوم بالعديد من المهام المتوازية وأقوم بها بشكل سيء، بدلاً من التركيز على واحدة. بالنظر إلى الوراء، كتبت نصف التعليمات البرمجية لأمر Java JIT، الأمر C2. ثاني أسرع مبرمج كتب كان بطيئًا إلى النصف، والنصف التالي بطيئًا، وكان الانخفاض هائلاً. كان الشخص السابع في هذا الصف بطيئًا جدًا جدًا - وهذا يحدث دائمًا! لقد لمست الكثير من التعليمات البرمجية. نظرت إلى من كتب ماذا، دون استثناء، حدقت في الكود الخاص بهم، وراجعت كل واحد منهم، وما زلت أواصل الكتابة بنفسي أكثر من أي منهم. هذا النهج لا يعمل بشكل جيد مع الناس. بعض الناس لا يحبون هذا. وعندما لا يستطيعون التعامل مع الأمر، تبدأ كل أنواع الشكاوى. على سبيل المثال، طُلب مني ذات مرة أن أتوقف عن البرمجة لأنني كنت أكتب الكثير من التعليمات البرمجية وكان ذلك يعرض الفريق للخطر، وبدا الأمر كله وكأنه مزحة بالنسبة لي: يا صديقي، إذا اختفى بقية الفريق وواصلت كتابة التعليمات البرمجية، فسوف سوف نخسر نصف الفرق فقط. من ناحية أخرى، إذا واصلت كتابة التعليمات البرمجية وخسرت نصف الفريق، فهذا يبدو بمثابة إدارة سيئة للغاية. لم أفكر في الأمر مطلقًا، ولم أتحدث عنه أبدًا، لكنه كان لا يزال في مكان ما في رأسي. كانت الفكرة تدور في ذهني: "هل تمزحون معي جميعًا؟" لذا، كانت المشكلة الأكبر هي أنا وعلاقاتي مع الناس. الآن أفهم نفسي بشكل أفضل بكثير، لقد كنت قائد فريق للمبرمجين لفترة طويلة، والآن أقول للناس بشكل مباشر: كما تعلمون، أنا من أنا، وسيتعين عليكم التعامل معي - هل من الجيد أن أقف هنا؟ وعندما بدأوا في التعامل معها، كل شيء سار على ما يرام. في الواقع، أنا لست سيئًا ولا جيدًا، وليس لدي أي نوايا سيئة أو تطلعات أنانية، إنه جوهري فقط، وأحتاج إلى التعايش معه بطريقة ما.

أندرو: بدأ الجميع مؤخرًا يتحدثون عن الوعي الذاتي للانطوائيين، والمهارات الناعمة بشكل عام. ماذا يمكنك أن تقول عن هذا؟

جرف: نعم، تلك كانت البصيرة والدرس الذي تعلمته من طلاقي من زوجتي. ما تعلمته من الطلاق هو فهم نفسي. هكذا بدأت أفهم الآخرين. افهم كيف يعمل هذا التفاعل. أدى هذا إلى الاكتشافات الواحدة تلو الأخرى. كان هناك وعي بمن أنا وما أمثله. ماذا أفعل: إما أن أكون منشغلًا بالمهمة، أو أتجنب الصراع، أو أي شيء آخر - وهذا المستوى من الوعي الذاتي يساعد حقًا في السيطرة على نفسي. بعد هذا يصبح كل شيء أسهل بكثير. الشيء الوحيد الذي اكتشفته ليس فقط في نفسي، ولكن أيضًا في المبرمجين الآخرين هو عدم القدرة على التعبير عن الأفكار لفظيًا عندما تكون في حالة من الضغط العاطفي. على سبيل المثال، أنت تجلس هناك وتقوم بالبرمجة، في حالة من التدفق، ثم يركضون إليك ويبدأون بالصراخ بشكل هستيري بأن شيئًا ما قد تم كسره وسيتم الآن اتخاذ إجراءات صارمة ضدك. ولا يمكنك أن تقول كلمة واحدة لأنك في حالة من الضغط العاطفي. تتيح لك المعرفة المكتسبة الاستعداد لهذه اللحظة والبقاء على قيد الحياة والانتقال إلى خطة التراجع، وبعد ذلك يمكنك القيام بشيء ما. لذا، نعم، عندما تبدأ في إدراك كيفية عمل كل شيء، فهذا حدث ضخم يغير حياتك. 
أنا نفسي لم أتمكن من العثور على الكلمات الصحيحة، لكنني تذكرت تسلسل الإجراءات. النقطة المهمة هي أن رد الفعل هذا هو جسدي بقدر ما هو لفظي، وتحتاج إلى مساحة. مثل هذه المساحة، بمعنى زن. هذا هو بالضبط ما يجب شرحه، ثم التنحي جانبًا على الفور - الابتعاد جسديًا بحتًا. عندما أبقى صامتًا لفظيًا، أستطيع معالجة الموقف عاطفيًا. عندما يصل الأدرينالين إلى دماغك، ويحولك إلى وضع القتال أو الطيران، لم يعد بإمكانك قول أي شيء، لا - الآن أنت أحمق، مهندس جلد، غير قادر على الاستجابة اللائقة أو حتى إيقاف الهجوم، والمهاجم حر للهجوم مرارا وتكرارا. يجب عليك أولا أن تصبح نفسك مرة أخرى، واستعادة السيطرة، والخروج من وضع "القتال أو الهروب".

ولهذا نحتاج إلى مساحة لفظية. مجرد مساحة حرة. إذا قلت أي شيء على الإطلاق، فيمكنك أن تقول ذلك بالضبط، ثم اذهب وابحث عن "مساحة" لنفسك حقًا: اذهب للنزهة في الحديقة، واغلق على نفسك في الحمام - لا يهم. الشيء الرئيسي هو الانفصال مؤقتًا عن هذا الموقف. بمجرد إيقاف تشغيله لبضع ثوانٍ على الأقل، يعود التحكم، وتبدأ في التفكير بعقلانية. "حسنًا، أنا لست أحمقًا، ولا أفعل أشياء غبية، أنا شخص مفيد جدًا." بمجرد أن تتمكن من إقناع نفسك، فقد حان الوقت للانتقال إلى المرحلة التالية: فهم ما حدث. لقد تعرضت للهجوم، وجاء الهجوم من حيث لم تتوقعه، لقد كان كمينًا غير أمين وحقيرًا. هذا سيء. والخطوة التالية هي فهم سبب حاجة المهاجم إلى ذلك. حقا لماذا؟ ربما لأنه هو نفسه غاضب؟ لماذا هو مجنون؟ على سبيل المثال لأنه أفسد نفسه ولا يستطيع تحمل المسؤولية؟ هذه هي الطريقة للتعامل مع الوضع برمته بعناية. لكن هذا يتطلب مساحة للمناورة ومساحة لفظية. الخطوة الأولى هي قطع الاتصال اللفظي. تجنب النقاش بالكلمات. قم بإلغاء الأمر، وابتعد في أسرع وقت ممكن. إذا كانت محادثة هاتفية، فقط أغلق الخط - هذه مهارة تعلمتها من التواصل مع زوجتي السابقة. إذا لم تسير المحادثة على ما يرام، فقط قل "وداعًا" وأغلق الخط. ومن الجانب الآخر من الهاتف: "بلا بلا بلا"، تجيب: "نعم، إلى اللقاء!" وأغلق الخط. أنت فقط تنهي المحادثة. بعد خمس دقائق، عندما تعود إليك القدرة على التفكير بشكل معقول، فقد تبرد قليلاً، ويصبح من الممكن التفكير في كل شيء، وما حدث وما سيحدث بعد ذلك. وابدأ في صياغة استجابة مدروسة، بدلاً من مجرد الرد بدافع العاطفة. بالنسبة لي، كان الاختراق في الوعي الذاتي هو على وجه التحديد حقيقة أنني لا أستطيع التحدث في حالة الضغط العاطفي. الخروج من هذه الحالة والتفكير والتخطيط لكيفية الاستجابة للمشكلات والتعويض عنها - هذه هي الخطوات الصحيحة في حالة عدم قدرتك على التحدث. أسهل طريقة هي الهروب من الموقف الذي يظهر فيه التوتر العاطفي والتوقف ببساطة عن المشاركة في هذا التوتر. وبعد ذلك تصبح قادرًا على التفكير، وعندما تستطيع التفكير تصبح قادرًا على التحدث، وهكذا.

بالمناسبة، في المحكمة، يحاول المحامي المنافس أن يفعل ذلك معك - الآن أصبح السبب واضحًا. لأنه لديه القدرة على قمعك لدرجة أنك لا تستطيع حتى نطق اسمك، على سبيل المثال. بالمعنى الحقيقي للغاية، لن تكون قادرًا على التحدث. إذا حدث لك هذا، وإذا كنت تعلم أنك ستجد نفسك في مكان تحتدم فيه المعارك اللفظية، في مكان مثل المحكمة، فيمكنك أن تأتي مع محاميك. المحامي سيدافع عنك ويوقف الاعتداء اللفظي، وسيفعل ذلك بطريقة قانونية تماما، وستعود لك مساحة الزن المفقودة. على سبيل المثال، اضطررت إلى الاتصال بعائلتي عدة مرات، وكان القاضي ودودًا للغاية بشأن هذا الأمر، لكن المحامي المنافس صرخ في وجهي، ولم أتمكن حتى من الحصول على كلمة واحدة. في هذه الحالات، فإن استخدام الوسيط هو الأفضل بالنسبة لي. يوقف الوسيط كل هذا الضغط الذي ينهمر عليك في تيار مستمر، تجد مساحة Zen اللازمة، وتعود معها القدرة على الكلام. هذا مجال كامل من المعرفة حيث يوجد الكثير مما يجب دراسته، والكثير مما يمكن اكتشافه داخل نفسك، وكل هذا يتحول إلى قرارات استراتيجية عالية المستوى تختلف باختلاف الأشخاص. بعض الأشخاص لا يعانون من المشاكل المذكورة أعلاه، وعادةً لا يعاني منها الأشخاص الذين هم مندوبي المبيعات المحترفين. كل هؤلاء الأشخاص الذين يكسبون عيشهم من الكلمات، من مشاهير المطربين والشعراء والزعماء الدينيين والسياسيين، لديهم دائمًا ما يقولونه. ليس لديهم مثل هذه المشاكل، ولكن لدي.

أندرو: كان غير متوقع. عظيم، لقد تحدثنا كثيرًا بالفعل وحان الوقت لإنهاء هذه المقابلة. سنلتقي بالتأكيد في المؤتمر وسنتمكن من مواصلة هذا الحوار. نراكم في هيدرا!

يمكنك مواصلة محادثتك مع كليف في مؤتمر هيدرا 2019 الذي سيعقد في الفترة من 11 إلى 12 يوليو 2019 في سانت بطرسبرغ. وسوف يأتي مع التقرير "تجربة ذاكرة معاملات Azul Hardware". يمكن شراء التذاكر على الموقع الرسمي.

المصدر: www.habr.com

إضافة تعليق