Cliff Click هي Cratus 'CTO (أجهزة استشعار IoT لتحسين العملية) ، المؤسس والمؤسس المشارك للعديد من الشركات الناشئة (بما في ذلك Rocket Realtime School و Neurensic و H2O.ai) مع العديد من المخارج الناجحة. كتب كليف أول مترجم له في سن 15 (Pascal for TRS Z-80)! اشتهر بالعمل على C2 في Java (بحر العقد IR). أظهر هذا المترجم للعالم أن JIT يمكنها إنتاج كود عالي الجودة ، والذي أصبح أحد العوامل في جعل Java أحد المنصات البرمجية الحديثة الرئيسية. ثم ساعد Cliff Azul Systems في بناء حاسب مركزي 864 نواة باستخدام برنامج Java خالص يدعم توقف GC مؤقتًا على كومة سعة 500 جيجابايت لمدة 10 مللي ثانية. بشكل عام ، تمكنت كليف من العمل على جميع جوانب JVM.
هذه اللعبة هي مقابلة رائعة مع كليف. سنتحدث عن المواضيع التالية:
- الانتقال إلى تحسينات منخفضة المستوى
- كيف تفعل الكثير من إعادة البناء
- نموذج التكلفة
- التدريب على المستوى المنخفض الأمثل
- دراسات حالة لتحسين الإنتاجية
- لماذا خلق لغتك البرمجة الخاصة
- مهندس الأداء الوظيفي
- التحديات الفنية
- قليلا عن تخصيص التسجيل ومتعددة النوى
- التحدي الأكبر في الحياة
المقابلات التي أجراها:
- أندريه ساترين من خدمات الويب الأمازون. في حياته المهنية ، تمكن من العمل في مشاريع مختلفة تمامًا: فقد اختبر قاعدة بيانات NewSQL الموزعة في ياندكس ، ونظام الكشف السحابي في Kaspersky Lab ، واللعبة متعددة المستخدمين في Mail.ru ، وخدمة حساب صرف العملات في بنك Deutsche Bank. إنه مهتم باختبار الأنظمة الخلفية الموزعة على نطاق واسع.
- فلاديمير سيتنيكوف من Netcracker. منذ عشر سنوات ، كان يعمل على أداء وقابلية تطوير NetCracker OS ، وهو برنامج يستخدمه مشغلو الاتصالات لأتمتة عمليات إدارة معدات الشبكات والشبكات. إنه مهتم بقضايا أداء Java و Oracle Database. مؤلف أكثر من عشرة تحسينات في الأداء في برنامج تشغيل PostgreSQL JDBC الرسمي.
الانتقال إلى تحسينات منخفضة المستوى
أندريه : أنت شخص مشهور في عالم تجميع JIT ، في Java وتعمل على الأداء بشكل عام ، أليس كذلك؟
كليف : هذا كل شيء!
أندرو : لنبدأ بالأسئلة العامة حول العمل على الأداء. ما رأيك في الاختيار بين التحسينات عالية المستوى ومنخفضة المستوى مثل العمل على مستوى وحدة المعالجة المركزية؟
كليف : إنه سهل. أسرع رمز هو الذي لا يعمل أبداً. لذلك ، تحتاج دائمًا إلى البدء من مستوى عالٍ للعمل على الخوارزميات. أفضل O-تدوين سوف تغلب على O- أسوأ تدوين ، ما لم تتدخل بعض الثوابت الكبيرة إلى حد ما. الأشياء المنخفضة المستوى هي الأحدث. عادة ، إذا قمت بتحسين بقية المكدس بشكل جيد بما فيه الكفاية ، ولا يزال هناك شيء مثير للاهتمام اليسار - هذا هو المستوى المنخفض. ولكن كيف تبدأ من مستوى عال؟ كيف يمكن معرفة ما يكفي من العمل على مستوى عالٍ؟ حسنا ... لا مفر. لا توجد وصفات جاهزة. تحتاج إلى فهم المشكلة ، وتحديد ما الذي ستفعله (حتى لا تتخذ خطوات غير ضرورية في المستقبل) وبعد ذلك يمكنك الكشف عن ملف تعريف يمكنه قول شيء مفيد. في مرحلة ما ، أنت نفسك تدرك أنك تخلصت من الأشياء غير الضرورية ، وقد حان الوقت للقيام بضبط المستوى المنخفض. هذا بالتأكيد نوع خاص من الفن. كثير من الناس يقومون بأشياء غير ضرورية ، لكنهم يتحركون بسرعة بحيث لا يوجد لديهم وقت يهتمون بالأداء. ولكن هذا طالما أن السؤال لا يقف منتصبا. عادة ، 99٪ من الوقت لا يهتم أحد بما أقوم به ، حتى اللحظة التي لا يسير فيها شيء مهم يهتم به شخص ما على المسار الحرج. وهنا يبدأ الجميع في إزعاجك حول موضوع "لماذا لم ينجح هذا بشكل كامل من البداية." بشكل عام ، هناك دائمًا شيء ما لتحسين الأداء. لكن 99 ٪ من الوقت ليس لديك خيوط! أنت تحاول فقط الحصول على شيء ما للعمل وفي هذه العملية تفهم ما هو مهم. لا يمكنك أبدًا معرفة أن هذه القطعة تحتاج إلى أن تكون مثالية ، وبالتالي ، في الأساس ، يجب أن تكون مثاليًا في كل شيء. وهذا مستحيل ، ولا تفعل ذلك. هناك دائمًا مجموعة من الأشياء التي يجب إصلاحها - وهذا أمر طبيعي تمامًا.
كيف تفعل الكثير من إعادة البناء
أندرو : كيف تعمل على الأداء؟ هذه مشكلة شاملة. على سبيل المثال ، هل اضطررت إلى حل المشاكل الناشئة عن تقاطع كمية كبيرة من الوظائف الحالية؟
جرف : أحاول تجنب ذلك. إذا علمت أن الأداء سيصبح مشكلة ، فعندئذ أفكر في الأمر قبل أن أبدأ الترميز ، خاصة في هياكل البيانات. ولكن في كثير من الأحيان تكتشف كل هذا في وقت لاحق. ثم عليك أن تذهب إلى التدابير المتطرفة وأن تفعل ما أسميه "إعادة كتابة وقهر": تحتاج إلى الاستيلاء على قطعة كبيرة إلى حد ما. لا يزال يتعين إعادة كتابة جزء من الشفرة بسبب مشاكل في الأداء أو أي شيء آخر. مهما كان سبب إعادة كتابة الشفرة ، فمن الأفضل دائمًا إعادة كتابة جزء أكبر من مقطع أصغر. في هذه اللحظة ، يبدأ الجميع في الهز بالخوف: "يا إلهي ، لا يمكنك لمس الكثير من الكود!" ولكن ، في الواقع ، فإن هذا النهج يعمل دائمًا بشكل أفضل كثيرًا. تحتاج إلى مواجهة المشكلة الكبيرة فورًا ورسم دائرة كبيرة حولها والقول: سأعيد كتابة كل شيء داخل الدائرة. الحد أصغر بكثير من المحتوى بداخله الذي يجب استبداله. وإذا كان مثل هذا التحديد للحدود يسمح لك بالقيام بالعمل في الداخل بشكل مثالي - لديك يديك غير مقيدتين ، فافعل ما تريد. بمجرد أن تفهم المشكلة ، تصبح عملية إعادة الكتابة أسهل كثيرًا ، لذا تعض جزءًا كبيرًا!
في الوقت نفسه ، عندما تعيد كتابة أجزاء كبيرة وتفهم أن الأداء سيصبح مشكلة ، يمكنك أن تبدأ على الفور في القلق بشأنه. عادة ما يتحول هذا إلى أشياء بسيطة مثل "لا تقم بنسخ البيانات ، وإدارة البيانات بأقصى قدر ممكن ، وجعلها أصغر". في عمليات إعادة الكتابة الكبيرة ، توجد طرق قياسية لتحسين الأداء. ودائمًا ما تدور حول البيانات.
نموذج التكلفة
أندرو : في أحد المواد الصوتية ، تحدثت عن نماذج التكلفة في سياق الإنتاجية. هل يمكن ان توضح ما هو المقصود بهذا؟
كليف : بالطبع. لقد ولدت في عصر كان فيه أداء المعالج في غاية الأهمية. وهذا العصر يعود مرة أخرى - المصير لا يخلو من المفارقة. بدأت أعيش في أيام الأجهزة ذات الثمانية بتات ، وكان أول حاسوبي يعمل مع 256 بايت. انها بايت. كان كل شيء صغير جدا. اضطررنا إلى قراءة التعليمات وبمجرد أن بدأنا في رفع مجموعة لغات البرمجة ، أصبحت اللغات أكثر وأكثر. كان هناك مجمع ، ثم Basic ، ثم C ، و C تولى العمل مع العديد من التفاصيل ، مثل تخصيص التسجيل واختيار التعليمات. ولكن كان كل شيء واضحًا هناك ، وإذا قمت بعمل مؤشر لمثيل متغير ، فسوف أحمل ، وستكون التكلفة معروفة لهذه التعليمات. تنتج Iron عددًا معروفًا من دورات الماكينات ، وبالتالي يمكن حساب سرعة تنفيذ القطع المختلفة ببساطة عن طريق إضافة جميع الإرشادات التي كنت على وشك تشغيلها. يمكن طي كل مقارنة / اختبار / فرع / استدعاء / تحميل / متجر وقال: هنا لديك مهلة. عندما تقوم بتحسين الأداء ، ستلاحظ بالتأكيد نوع الأرقام التي تتوافق مع الدورات الساخنة الصغيرة.
ولكن بمجرد التبديل إلى Java و Python وأشياء مماثلة ، يمكنك الابتعاد بسرعة كبيرة عن الحديد ذي المستوى المنخفض. كم تكلفة مكالمة getter في Java؟ إذا كان JIT في HotSpot مضمنًا بشكل صحيح ، فسيتم تحميله ، ولكن إذا لم يحدث ، فسيكون استدعاء وظيفي. نظرًا لأن التحدي يكمن في الحلقة الساخنة ، فسيتم التراجع عن جميع التحسينات الأخرى في هذه الحلقة. لذلك ، ستكون القيمة الحقيقية أكبر بكثير. وستفقد على الفور القدرة على إلقاء نظرة على جزء من الكود وتفهم أنه يتعين علينا تنفيذه من حيث سرعة ساعة المعالج والذاكرة المستخدمة وذاكرة التخزين المؤقت. كل هذا يصبح مثيرا للاهتمام فقط إذا كنت حقا في حالة سكر في الأداء.
نحن الآن في وضع لا تتزايد فيه سرعة المعالجات تقريبًا منذ عشر سنوات. العصور القديمة عادت! لم يعد بإمكانك الاعتماد على أداء مترابط واحد جيد. لكن إذا انخرطت فجأة في الحوسبة المتوازية - فهذا أمر صعب بجنون ، فإن الجميع ينظر إليك باعتباره جيمس بوند. يحدث التسارع بعشرة أضعاف هنا عادة في تلك الأماكن التي يصفع فيها أحدهم شيئًا ما. التزامن يتطلب الكثير من العمل. للحصول على نفس التسارع بعشرة أضعاف ، تحتاج إلى فهم نموذج التكلفة. ماذا وكم يكلف. ولهذا تحتاج إلى فهم كيفية وضع اللسان على الحديد الأساسي.
مارتن طومسون لديه كلمة رائعة لبلده التعاطف الميكانيكية بلوق! عليك أن تفهم ما الذي سيفعله الحديد ، وكيف سيفعله بالضبط ، ولماذا يفعل عمومًا ما يفعله. باستخدام هذا ، من السهل جدًا بدء قراءة التعليمات ومعرفة الوقت الذي يتدفق فيه التنفيذ. إذا لم يكن لديك التدريب المناسب ، فأنت تبحث فقط عن قطة سوداء في غرفة مظلمة. أرى باستمرار أشخاصًا يقومون بتحسين الأداء وليس لديهم أي فكرة عما يفعلونه بحق الجحيم. إنها معذبة للغاية ولا تذهب حقًا إلى مكان ما. وعندما أأخذ نفس الكود ، أكمل بضع اختراقات صغيرة هناك وأحصل على تسارع خمس أو عشر مرات ، فهم مثل هذا: حسنًا ، إنه أمر غير أمين ، لقد علمنا بالفعل أنك أفضل. إنه لأمر مدهش. ما أتحدث عنه ... نموذج التكلفة يدور حول الرمز الذي تكتبه ومدى سرعة عمله في المتوسط في الصورة الإجمالية.
أندرو : وكيفية الحفاظ على مثل هذا الصوت في رأسك؟ هل تحقق ذلك من خلال المزيد من الخبرة أم؟ أين اكتسبت هذه التجربة؟
كليف : حسنًا ، تجربتي لم تكن أسهل طريقة. لقد قمت بالبرمجة في المجمع في وقت كان من الممكن فيه فهم كل تعليمات فردية. هذا يبدو سخيفًا ، لكن منذ ذلك الحين في رأسي ، في ذاكرتي ، ظلت مجموعة تعليمات Z80 إلى الأبد. لا أتذكر أسماء الأشخاص بعد دقيقة من المحادثة ، لكنني أتذكر الرمز المكتوب قبل 40 عامًا. مضحك ، يبدو وكأنه متلازمة " أحمق المستفادة ".
التدريب على المستوى المنخفض الأمثل
أندرو : هل هناك أي طريقة أبسط للدخول في العمل؟
جرف : نعم ولا. الحديد الذي نستخدمه جميعًا لم يتغير كثيرًا خلال هذا الوقت. الجميع يستخدم x86 ، باستثناء الهواتف الذكية Arm. إذا لم تقم ببعض التضمين المتشددين ، فلديك نفس الشيء. حسناً ، بعد ذلك. التعليمات ، أيضا ، لم تتغير منذ قرون. تحتاج إلى الذهاب وكتابة شيء في المجمع. قليلا ، ولكن يكفي أن تبدأ في فهم. أنت تبتسم ، ولكني جاد للغاية. من الضروري أن نفهم مراسلات اللغة والحديد. بعد ذلك ، عليك أن تذهب ، تبول قليلا ، وصنع مترجم لعبة صغير للغة لعبة صغيرة. "لعبة" تعني أنك تحتاج إلى جعلها في فترة زمنية معقولة. يمكن أن يكون بسيطًا جدًا ، لكن يجب أن يولد التعليمات. تتيح لنا عملية إنشاء التعليمات فهم نموذج التكلفة للجسر بين الكود العالي المستوى الذي يكتب عليه الجميع ورمز الجهاز الذي يعمل على الأجهزة. سيتم حرق هذه المراسلات في المخ في وقت كتابة المترجم. حتى أبسط مترجم. بعد ذلك ، يمكنك البدء في النظر إلى Java وحقيقة أن لديها فجوة دلالة أعمق ، وبناء الجسور فوقه أكثر صعوبة بكثير. في Java ، يصعب فهم ما إذا كان جسرنا جيدًا أو سيئًا ، مما سيجعله لا ينهار. لكنك تحتاج إلى نقطة بداية عندما تنظر إلى الكود وتفهم: "نعم ، يجب أن يكون هذا البرنامج مضمنًا في كل مرة". ثم اتضح أن هذا يحدث في بعض الأحيان ، باستثناء الموقف عندما تصبح الطريقة كبيرة للغاية وتبدأ JIT في تضمين كل شيء. يمكن التنبؤ بأداء مثل هذه الأماكن على الفور. عادة ما تعمل الحروف بشكل جيد ، لكنك تنظر إلى الحلقات الساخنة الكبيرة وتدرك أن هناك نوعًا من مكالمات الوظائف التي تطفو على السطح ولا تعرف ماذا يفعلون. هذه هي المشكلة مع الاستخدام الواسع النطاق للألعاب ، والسبب في عدم تماسكها - ليس من الواضح ما إذا كانت هذه مشكلة. إذا كان لديك قاعدة كود صغيرة جدًا ، فيمكنك تذكرها فقط ثم قول: هذه مشكلة ، ولكن هذا مضبط. في قاعدة الشفرة الكبيرة ، تعيش كل وظيفة قصتها الخاصة ، والتي ، بشكل عام ، غير معروفة لأحد. يقول الملامح أننا فقدنا 24 ٪ من وقتنا في نوع ما من الدورة ، ولكي نفهم ما تفعله هذه الدورة ، نحتاج إلى النظر في كل وظيفة في الداخل. من المستحيل فهم هذا دون دراسة الوظيفة ، وهذا يبطئ بشكل خطير عملية الفهم. هذا هو السبب في أنني لا أستخدم getters والمستوطنين ، لقد وصلت إلى مستوى جديد!
أين يمكن الحصول على نموذج التكلفة؟ حسنًا ، يمكنك قراءة شيء ما ، بالطبع ... لكنني أعتقد أن أفضل طريقة هي العمل. اصنع مترجمًا صغيرًا وسيكون هذا هو أفضل طريقة لتحقيق نموذج التكلفة وتثبيته في رأسك. مترجم صغير من شأنه أن يعمل لبرمجة الميكروويف هو مهمة للمبتدئين. حسنًا ، أعني ، إذا كان لديك بالفعل مهارات برمجية ، فيجب أن تكون كافية. كل هذه الأشياء مثل تحليل سلسلة ، والتي سيكون لديك نوع من التعبير الجبري ، اسحب تعليمات العمليات الرياضية من هناك بالترتيب الصحيح ، خذ القيم الصحيحة من السجلات - كل هذا يتم في وقت واحد. وبينما ستفعل ذلك ، سيتم طباعته في المخ. أعتقد أن الجميع يعرف ما يفعله المترجم. وهذا سيعطي فهمًا لنموذج التكلفة.
دراسات حالة لتحسين الإنتاجية
أندرو : ما الذي يستحق الاهتمام عند العمل على الأداء؟
جرف : هياكل البيانات. بالمناسبة ، نعم ، لم أدرس هذه الفصول لفترة طويلة ... مدرسة الصواريخ . كان الأمر ممتعًا ، لكن الأمر استغرق الكثير من الجهد للاستثمار ، ولدي حياة أيضًا! حسنا. لذلك ، في أحد الفصول الكبيرة والمثيرة للاهتمام ، "أين يذهب أدائك" ، أعطيت الطلاب مثالًا: تم قراءة اثنين ونصف غيغابايت من بيانات fintech من ملف CSV ثم اضطررنا إلى حساب عدد المنتجات المباعة. بيانات سوق التجزئة العادية. تم تحويل حزم UDP إلى تنسيق نصي منذ السبعينيات. تعد بورصة شيكاغو التجارية كل أنواع الأشياء مثل الزبدة والذرة وفول الصويا وما شابه. كان من الضروري حساب هذه المنتجات ، وعدد المعاملات ، ومتوسط حجم حركة الأموال والبضائع ، إلخ. هذه عملية تداول بسيطة إلى حد ما: ابحث عن رمز المنتج (هذه عبارة عن 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 رخيصة ، ضوضاء دافئة على مدار الدورة بأكملها ، لكننا نريد الحصول على تسارع 5x. في تلك اللحظة ، يجب إغلاق شيء ما في رأسي والصوت: "لماذا؟" لا يظهر تجاوز سعة النطاق الترددي في مصحح الأخطاء الكلاسيكي ، فأنت بحاجة إلى تشغيل مصحح أخطاء عداد أداء الأجهزة ورؤيته بنفسك ومباشرة. وليس مباشرة ، يمكن أن يشتبه في هذه الأعراض الثلاثة. العَرَض الثالث هو عندما تنظر إلى ما تبرزه ، اسأل المُنشئ ، وهو يجيب: "لقد صنعت مليار سطر ، لكن GC عملت مجانًا". بمجرد حدوث ذلك ، تدرك أنك أنتجت الكثير من الكائنات وأحرقت شريط الذاكرة بالكامل. هناك طريقة لمعرفة ذلك ، ولكنها ليست واضحة.
تكمن المشكلة في بنية البيانات: الهيكل العاري وراء كل ما يحدث ، إنه كبير جدًا ، إنه 2.7 جيجا بايت على القرص ، لذا فإن عمل نسخة من هذا الشيء أمر غير مرغوب فيه للغاية - أريد تحميله من المخزن المؤقت بايت الشبكة على الفور في السجلات حتى لا أقوم بالكتابة إلى السلسلة ذهابا وإيابا خمس مرات. لسوء الحظ ، فإن Java افتراضيًا لا يمنحك هذه المكتبة كجزء من JDK. لكن هذا تافه ، أليس كذلك؟ في الواقع ، هذه هي 5-10 سطور من التعليمات البرمجية التي سيتم استخدامها لتنفيذ محمل الأسطر المخزنة مؤقتًا الخاص بك ، والذي يكرر سلوك فئة السطر ، بينما يكون مجمّعًا حول المخزن المؤقت للبايت الأساسي. نتيجة لذلك ، اتضح أنك تعمل تقريبًا كما لو كنت مع السلاسل ، ولكن في الحقيقة هناك مؤشرات تحريك إلى المخزن المؤقت ، ولا يتم نسخ البايتات الخام في أي مكان ، وبهذه الطريقة يتم إعادة استخدام المخازن المؤقتة نفسها ، مرة تلو الأخرى ، ويسر نظام التشغيل الأشياء التي صُممت من أجلها ، مثل التخزين المؤقت المزدوج المخفي لهذه المخازن المؤقتة للبايت ، وأنت نفسك لم تعد تطحن مجموعة لا نهائية من البيانات غير الضرورية. بالمناسبة ، أنت تفهم ، عند العمل مع GC ، أنه مضمون أن كل تخصيص للذاكرة لن يكون مرئيًا للمعالج بعد دورة GC الأخيرة؟ لذلك ، لا يمكن بأي حال أن يكون ذلك في ذاكرة التخزين المؤقت ، ومن ثم يحدث تفويت مضمون بنسبة 100٪. عند العمل مع مؤشر على x86 ، يستغرق طرح سجل من الذاكرة 1-2 دورات ، وبمجرد حدوث ذلك ، فأنت تدفع ، وتدفع ، وتدفع ، لأن الذاكرة كلها موجودة في ذاكرة التخزين المؤقت تسعة - وهذه هي تكلفة تخصيص الذاكرة. القيمة الحالية.
بمعنى آخر ، هياكل البيانات هي الأكثر صعوبة في التغيير. وبمجرد أن تدرك أنك اخترت بنية البيانات الخاطئة التي ستقتل الإنتاجية في المستقبل ، فإنك تحتاج عادة إلى تعزيز العمل الأساسي ، ولكن إذا لم تفعل ذلك ، فسيكون الأمر أسوأ. بادئ ذي بدء ، تحتاج إلى التفكير في هياكل البيانات ، وهذا أمر مهم. تكمن التكلفة الرئيسية هنا في بنيات البيانات الغامقة ، والتي تبدأ في استخدامها في نمط "لقد قمت بنسخ بنية البيانات X في بنية البيانات Y ، لأنني أحب الشكل بشكل أفضل." لكن عملية النسخ (التي تبدو رخيصة) تقضي بالفعل قطاعًا من الذاكرة وهنا يتم دفن جميع أوقات التشغيل المفقودة. إذا كان لدي سلسلة عملاقة مع JSON وأريد تحويلها إلى شجرة DOM منظمة من POJO أو شيء من هذا القبيل ، فإن عملية تحليل هذه السلسلة وبناء POJO ، ومن ثم فإن مكالمة جديدة إلى POJO في المستقبل ستكون بلا قيمة - إنها ليست مكلفة. إلا إذا كنت ستعمل على POJO في كثير من الأحيان أكثر من على خط. مرتجلاً ، بدلاً من ذلك ، يمكنك محاولة فك تشفير السلسلة وسحب ما تحتاج إليه فقط ، دون تحويله إلى أي POJOs. إذا حدث كل هذا على المسار الذي يتطلب منه الحد الأقصى من الأداء ، فلن تحتاج إلى POJOs - فأنت بحاجة إلى البحث بطريقة مباشرة في السطر.
لماذا خلق لغتك البرمجة الخاصة
أندريه : قلت أنك لفهم نموذج التكلفة ، عليك كتابة لغتك الصغيرة الصغيرة ...
جرف : ليست لغة ، ولكن مترجم. اللغة والمترجم هما شيئان مختلفان. الفرق الأكثر أهمية هو في رأسك.
أندريه : بالمناسبة ، على حد علمي ، أنت تجرب إنشاء لغاتك الخاصة. لماذا؟
جرف : لأنني أستطيع! أنا متقاعد نصف ، لذلك هذه هوايتي. كنت أطبق لغات شخص آخر طوال حياتي. كما عملت بجد على أسلوب الترميز. وأيضا لأنني أرى مشاكل في لغات أخرى. أرى أن هناك طرقًا أفضل للقيام بالأشياء المعتادة. وأود أن استخدامها. لقد تعبت من رؤية المشاكل في نفسي ، في جافا ، في بيثون ، بأي لغة أخرى. أنا أكتب على React Native و JavaScript و Elm كهواية ، وهي ليست عن التقاعد بل عن العمل النشط. وأكتب أيضًا في Python ، وعلى الأرجح ، سأستمر في العمل على التعلم الآلي لواجهات Java الخلفية. هناك العديد من اللغات الشائعة ولديها ميزات مثيرة للاهتمام. الجميع جيد في شيء خاص بهم ويمكنك محاولة الجمع بين كل هذه الرقائق. لذلك ، أنا أدرس الأشياء التي تهمني ، سلوك اللغة ، وأحاول التوصل إلى دلالات معقولة. وحتى الآن أنا أفعل ذلك! في الوقت الحالي ، أنا أعاني من دلالات الذاكرة ، لأنني أريد أن أمتلكها في C و Java ، وأن أحصل على نموذج ذاكرة قوي ودلالات ذاكرة للأحمال والمخازن. في الوقت نفسه ، يكون لديك الاستدلال التلقائي للنوع كما في Haskell. هنا ، أحاول أن أخلط بين الاستدلال الشبيه بـ Haskell والذاكرة العاملة في C و Java. لقد كنت أفعل ذلك منذ 2-3 أشهر ، على سبيل المثال.
أندريه : إذا كنت تبني لغة تأخذ جوانب أفضل من اللغات الأخرى ، هل تعتقد أن شخصًا ما سيفعل عكس ذلك: خذ أفكارك واستخدمها؟
جرف : هذه هي الطريقة التي تظهر لغات جديدة! لماذا تشبه جافا ج؟ نظرًا لأن لغة C لديها بناء جملة جيد وفهمه الجميع واستلهم Java من بناء الجملة هذا ، مضيفًا أمان النوع ، والتحقق من حدود المصفوفات ، GC ، وقاموا بتحسين بعض الأشياء من C. لقد أضافوا قواعد خاصة بهم. ولكن كانت مستوحاة قليلا جدا ، أليس كذلك؟ يقف الجميع على أكتاف العمالقة الذين سبقونا - هكذا يتم إحراز تقدم.
أندرو : كما أفهمها ، ستكون لغتك آمنة فيما يتعلق باستخدام الذاكرة. هل فكرت في تنفيذ شيء مثل مدقق الاقتراض من رست؟ نظرت إليه ، كيف كان يحبك؟
كليف : حسنًا ، كنت أكتب لغة C للأعمار ، مع كل هذه الأشياء الحرة والمجانية ، وأنا أدير العمر يدويًا. كما تعلمون ، فإن 90-95٪ من مدة الحياة المدارة يدويًا لها نفس البنية. وهذا مؤلم جدًا جدًا للقيام بذلك يدويًا. أود المترجم أن يقول ببساطة ما يحدث هناك وما حققته من خلال أفعالك. بالنسبة لبعض الأشياء ، يقوم مدقق الاقتراض بذلك من خارج الصندوق. وعليه أن يعرض المعلومات تلقائيًا ، ويفهم كل شيء ، ولا يثقل كاهل لي لكي أذكر هذا الفهم. يجب أن يقوم بتحليل هروب محلي على الأقل ، وفقط إذا لم ينجح ، فأنت بحاجة إلى إضافة تعليقات توضيحية من شأنها أن تصف مدى الحياة - ومثل هذا المخطط أكثر تعقيدًا من مدقق الاقتراض أو أي مدقق ذاكرة موجود. الاختيار بين "كل شيء على ما يرام" و "لم أفهم أي شيء" - لا ، يجب أن يكون هناك شيء أفضل.
لذا ، بصفتي شخصًا كتب الكثير من كود C ، أعتقد أن الحصول على الدعم للتحكم التلقائي في العمر هو أهم شيء. تعبت من مقدار استخدام Java للذاكرة والشكوى الرئيسية في GC. عند تخصيص ذاكرة في Java ، فلن تقوم بإرجاع الذاكرة التي كانت محلية على حلقة GC الأخيرة. في اللغات ذات الإدارة الأكثر دقة للذاكرة ، هذا ليس كذلك. إذا اتصلت malloc ، فستحصل على الفور على الذاكرة التي كانت تستخدم عادةً. عادة ما تفعل بعض الأشياء المؤقتة مع ذاكرتك وإعادتها على الفور. وتعود على الفور إلى تجمع malloc ، وتقوم الدورة malloc التالية بسحبها مرة أخرى. لذلك ، يتم تقليل استخدام الذاكرة الفعلي إلى مجموعة من الكائنات الحية في وقت معين ، بالإضافة إلى التسريبات. وإذا لم يتدفق كل شيء بطريقة غير لائقة ، فإن معظم الذاكرة تستقر في ذاكرة التخزين المؤقت والمعالج ، وتعمل بسرعة. لكنه يتطلب الكثير من إدارة الذاكرة اليدوية مع malloc ومجانية ، ودعا في الترتيب الصحيح ، في المكان المناسب. يمكن لصدأ نفسه معالجة هذا بشكل صحيح وفي كومة من الحالات تعطي أداء أكبر ، حيث يتم تقليل استهلاك الذاكرة فقط إلى الحسابات الحالية - على عكس انتظار دورة GC التالية لتحرير الذاكرة. نتيجة لذلك ، حصلنا على طريقة ممتعة للغاية لتحسين الأداء. وقوية جدًا - بمعنى أنني فعلت مثل هذه الأشياء عند معالجة البيانات الخاصة بالتقنية ، وهذا سمح لي بالحصول على تسارع خمس مرات. هذا تسارع كبير ، خاصة في عالم لا تتسارع فيه المعالجات ، ونواصل جميعًا انتظار التحسينات.
أندرو : أود أيضًا أن أسأل عن المهنة ككل. لقد اشتهرت بالعمل في JIT في HotSpot ثم انتقلت إلى Azul - وهذه أيضًا شركة JVM. لكنها كانت تعمل بالفعل في الحديد أكثر من البرمجيات. ثم تحولت فجأة إلى Big Data and Machine Learning ، ثم إلى كشف الاحتيال. كيف حدث ذلك؟ هذه هي مجالات مختلفة جدا للتنمية.
كليف : لقد قمت بالبرمجة لبعض الوقت الآن وتمكنت من تسجيل الوصول في فصول مختلفة جدًا. وعندما يقول الناس ، "أوه ، أنت الشخص الذي صنع JIT لجافا!" ، إنه أمر مضحك دائمًا. لكن قبل ذلك ، كنت منخرطًا في استنساخ PostScript - اللغة التي كانت تستخدمها Apple من قبل لطابعات الليزر. وقبل ذلك قام بتنفيذ اللغة الرابعة. أعتقد أن الموضوع المشترك بالنسبة لي هو تطوير الأدوات. طيلة حياتي ، صنع أدوات تمكن الآخرين من كتابة برامجهم الرائعة. لكنني كنت مشتركًا أيضًا في تطوير أنظمة التشغيل ، وبرامج التشغيل ، وأجهزة تصحيح الأخطاء على مستوى kernel ، ولغات تطوير نظام التشغيل ، والذي بدأ بشكل تافه ، ولكن بمرور الوقت أصبح كل شيء معقدًا ومعقدًا. لكن الموضوع الرئيسي ، مع ذلك ، هو تطوير الأدوات. ذهب جزء كبير من الحياة بين Azul و Sun ، وكان حول Java. لكن عندما بدأت Big Data and Machine Learning ، وضعت قبعة أمامي مرة أخرى وقلت: "أوه ، والآن لدينا مشكلة غير تافهة ، وهنا الكثير من الأشياء المثيرة للاهتمام والأشخاص الذين يقومون بشيء ما" يحدث. هذا هو مسار التنمية العظيم يستحق اتخاذ.
نعم ، أنا حقا أحب الحوسبة الموزعة. كانت وظيفتي الأولى كطالب في C ، في مشروع إعلاني. تم توزيع هذه الحوسبة على شرائح Zilog Z80 ، التي جمعت بيانات للتعرف على النص البصري التمثيلي التي ينتجها محلل تمثيلي حقيقي. لقد كان موضوعًا رائعًا وغير طبيعي تمامًا. ولكن كانت هناك مشاكل ، لم يتم التعرف على جزء صحيح بشكل صحيح ، لذلك كان من الضروري الحصول على صورة وإظهارها لشخص قرأ بالفعل بأعينه وأبلغ ما قيل هناك ، وبالتالي كان هناك متاعب للبيانات ، وهذه الوظيفة كانت لغتها الخاصة . كان هناك الخلفية التي تعاملت مع كل هذا - يعمل بالتوازي مع Z80 مع تشغيل محطات vt100 - واحد للشخص ، وكان هناك نموذج برمجة موازية على Z80. قطعة شائعة معينة من الذاكرة مشتركة بين جميع Z80 داخل تكوين نجمة ؛ تمت مشاركة لوحة الكترونية معززة ، وتمت مشاركة نصف ذاكرة الوصول العشوائي داخل الشبكة ، وكان النصف الآخر خاصًا أو تم إنفاقه على شيء آخر. نظام توزيع متوازٍ معقد معقد مع ذاكرة مشتركة نصف مشتركة. عندما كان ... بالفعل لا تذكر ، في مكان ما في منتصف الثمانينات. منذ وقت طويل جدا.
نعم ، سوف نفترض أن 30 عامًا هي فترة طويلة ، والمهام المرتبطة بالحوسبة الموزعة موجودة لفترة طويلة ، وقد قاتل الناس منذ فترة طويلة مع مجموعات بيوولف . تبدو مثل هذه المجموعات ... على سبيل المثال: هناك Ethernet وسريع x86 الخاص بك متصلاً بشبكة Ethernet هذه ، وتريد الآن الحصول على ذاكرة مشتركة مزيفة ، لأنه لا يمكن لأحد بعد ذلك القيام بترميز الحوسبة الموزعة ، فقد كانت معقدة للغاية وبالتالي كانت الذاكرة المشتركة المزيفة مع الحماية صفحات ذاكرة x86 ، وإذا كنت قد كتبت إلى هذه الصفحة ، فقد أخبرنا المعالجات الأخرى أنه إذا تمكنوا من الوصول إلى نفس الذاكرة المشتركة ، فسيتعين تنزيلها منك ، وبالتالي ظهر شيء مثل بروتوكول دعم تماسك ذاكرة التخزين المؤقت والبرامج لهذا الغرض. مفهوم مثير للاهتمام. المشكلة الحقيقية ، بالطبع ، كانت مختلفة. نجح كل هذا ، لكنك سرعان ما واجهت مشاكل في الأداء ، لأنه لم يفهم أحد نماذج الأداء بمستوى جيد بما فيه الكفاية - ما هي أنماط الوصول إلى الذاكرة الموجودة ، وكيفية التأكد من أن العقد لا تتواصل مع بعضها البعض إلى ما لا نهاية ، وما إلى ذلك.
في H2O ، توصلت إلى هذا: المطورين أنفسهم مسؤولون عن تحديد مكان التوازي المخفي وأين لا يكون. توصلت إلى نموذج ترميز أن كتابة التعليمات البرمجية عالية الأداء كانت سهلة وبسيطة. لكن كتابة رمز بطيء أمر صعب ، سيبدو سيئًا. تحتاج إلى محاولة جدية لكتابة رمز بطيء ، عليك استخدام أساليب غير قياسية. رمز الكبح مرئي في لمحة. نتيجة لذلك ، عادةً ما يتم كتابة التعليمات البرمجية التي تعمل بسرعة ، ولكن عليك معرفة ما يجب القيام به في حالة الذاكرة المشتركة. كل هذا مرتبط بمصفوفات كبيرة والسلوك هناك يشبه المصفوفات الكبيرة غير المتقلبة في جافا المتوازية. أعني ، تخيل أن اثنين من المواضيع تكتب إلى صفيف متوازي ، أحدهما يفوز ، والآخر ، على التوالي ، يخسر ، وأنت لا تعرف أي منهم هو من. إذا لم تكن متقلبة ، فيمكن أن يكون الأمر أي شيء - وهو يعمل بشكل جيد بالفعل. يهتم الناس حقًا بترتيب العمليات ، وهم يتقلبون بشكل صحيح ، ويتوقعون حدوث مشاكل في الذاكرة في الأماكن الصحيحة. خلاف ذلك ، فإنهم ببساطة يكتبون الكود في شكل دورات من 1 إلى N ، حيث N عبارة عن بعض التريليونات ، على أمل أن تصبح جميع الحالات المعقدة تلقائيًا موازية - وهذا لا يعمل هناك. لكن في H2O هذا ليس Java ولا Scala ، يمكنك اعتباره "Java ناقص" إذا كنت تريد. هذا أسلوب برمجة مفهوم للغاية وهو مشابه لكتابة رمز C أو Java بسيط مع حلقات ومصفوفات. ولكن في الوقت نفسه ، يمكن معالجة الذاكرة مع تيرابايت. ما زلت أستخدم H2O. – , . Big Data , H2O.
: ?
: ? , – .
. . , , , , . Sun, , , , . , , . , C1, , – . , . , x86- , , 5-10 , 50 .
, , , , C. , , - , C . C, C . , , C, - … , . , . , , . , , 5% . - – , « », , . : , , . . , – , . , . - – . , , ( , ), , , . , , , .
, , , , , , . , , , - . , , , . , , , , . , : , . , , - : , , - , . – , , – ! – , . Java. Java , , , , – , « ». , , . , Java C . – Java, C , , , . , – , . , . , , . : .
: - . , , - , ?
: ! – , NP- - . , ? . , Ahead of Time – . - . , , – , ! – , . , , . . ? , : , , - ! - , . . , , . : - , - . , , . , , , , - . ! , , , – . . NP- .
: , – . , , , , …
: . «». . , . – , , , ( , ). , - . , , , . , , . , . , , . , , . , , - , – . – . , GC, , , , – , . , . , , . , – , ? , .
: , ? ?
: GPU , !
: . ?
: , - Azul. , . . H2O , . , GPU. ? , Azul, : – .
: ?
: , … . , . , , , , . , , . , Java C1 C2 – . , Java – . , , – . … . - , Sun, … , , . , . , . … … , . , , . . - , : . , , , , , , . , . . , . « , , ». : «!». , , , : , .
– , , , . . , , , , . , Java JIT, C2. , – . , – ! . , , , , , , . . . , . , , , , : , , . , – . , , - . : « ?». , . , , : , , – ? , . , , , , , , - .
: , -. ?
: , , . – . . , . . . : , , - – . . , , – , . , , , , - , . , . , , - . , , – , .
, . , – , , . , . , – . , . , , « », , – , , , , . , , « ».
. . - , , «»: , – . – . , , . «, -, , ». , : , . , , . . – , . , ? , ? ? , ? . , . – . . , . – – , . , « » . : «--», : «, !» . . , , , , . , . , . , – , . – , . , , , .
, – , . , , . , . , , , , . , , . , , , , . . , , , . , , , , . , , , . , – , , , . , .
: … . , . . Hydra!
Hydra 2019, 11-12 2019 -. «The Azul Hardware Transactional Memory experience» . .