لقد وضعنا المسودة أخيرًاJEP “الثوابت المحسوبة”عامة ولا أطيق الانتظار لأخبركم المزيد عنها!ComputedConstantالكائنات هي أصحاب قيمة فائقة السرعة غير قابلة للتغيير ويمكن تهيئتها بشكل مستقل عن وقت إنشائها. كميزة إضافية ، قد يتم تحسين هذه الكائنات في المستقبل بشكل أكبر عبر“مكثفات”التي قد تصبح متاحة في النهاية من خلال المشروعليدن.
خلفية
في كثير من الأحيان نستخدمstaticالحقول لعقد الكائنات التي تمت تهيئتها مرة واحدة فقط:
// ordinary static initializationprivate static final Logger LOGGER=Logger.getLogger("com.foo.Bar");...LOGGER.log(...);
الLOGGERسيتم تهيئة المتغير دون قيد أو شرط بمجرد أن يتم تحميل الفئة التي تم الإعلان عنها (يحدث التحميل عند الإشارة إلى الفئة الأولى).
تتمثل إحدى طرق منع تهيئة جميع الحقول الثابتة في الفصل الدراسي في نفس الوقت في استخدام الامتدادلغة حامل الفئةالسماح لنا بتأجيل التهيئة حتى نحتاج بالفعل إلى المتغير:
// Initialization-on-demand holder idiomLogger logger() { class Holder { static final Logger LOGGER=Logger.getLogger("com.foo.Bar"); } return Holder.LOGGER;}...logger().log(...);
بينما يعمل هذا جيدًا من الناحية النظرية ، إلا أن هناك عيوبًا كبيرة:
- كل ثابت يحتاج إلى فصل سيحتاجملكهعقد فئة (إضافة بصمة ثابتة فوق)
- يعمل فقط إذا كانت الثوابت المنفصلة مستقلة
- يعمل فقط من أجل
staticالمتغيرات وليس على سبيل المثال المتغيرات والكائنات
طريقة أخرى لاستخدامالتحقق مرتين من لغة القفلالتي يمكن استخدامها أيضًا لتأجيل التهيئة. هذا يعمل لكليهماstaticالمتغيرات ومتغيرات الحالة والكائنات:
// Double-checked locking idiomclass Foo { private volatile Logger logger; public Logger logger() { Logger v=logger; if (v==null) { synchronized (this) { v=logger; if (v==null) { logger=v=Logger.getLogger("com.foo.Bar"); } } } return v; }}...foo.logger().log(...);
لا توجد طريقة لـ JVM (الحالية) لتحديد أن ملفloggerيكونرتيببمعنى أنه لا يمكن تغييره إلا منnullإلى قيمةمرة واحدةوبعد ذلك سيبقى دائما. لذلك ، فإن JVM غير قادر على تطبيق الطي المستمر والتحسينات الأخرى. أيضا ، لأنloggerيحتاج إلى التصريحvolatileهناك غرامة أداء صغيرة تدفع لكل وصول.
الComputedConstantيأتي الفصل إلى الإنقاذ هنا ويقدم أفضل ما في العالمين: تهيئة مرنة وأداء جيد!
ثابت محسوب
هنا هو كيفComputedConstantيمكن استخدامها مع مثال المسجل:
class Bar { // 1. Declare a computed constant value private static final ComputedConstant<Logger> LOGGER= ComputedConstant.of( () -> Logger.getLogger("com.foo.Bar") ); static Logger logger() { // 2. Access the computed value // (evaluation made before the first access) return LOGGER.get(); }}
هذا مشابه في الروح لمصطلح حامل الفئة ، ويقدم نفس الأداء ، وخصائص الطي الثابت ، وسلامة الخيط ، ولكنه أبسط ويتحمل بصمة ثابتة أقل نظرًا لعدم الحاجة إلى فئة إضافية.
المعايير
لقد أجريت بعض المعايير على جهاز Mac Pro M1 ARM الخاص بي وتشير النتائج الأولية إلى أداء ممتاز لـstaticComputedConstantمجالات:
Benchmark Mode Cnt Score Error UnitsstaticHolder avgt 15 0.561 ? 0.002 ns/opdoubleChecked avgt 15 1.122 ? 0.003 ns/opconstant avgt 15 0.563 ? 0.002 ns/op // static ComputedConstant
كما يتضح ، أComputedConstantله نفس أداء الحامل الثابت (ولكن بدون بصمة فئة إضافية) وأداء أفضل بكثير من متغير قفل مزدوج التحقق.
مجموعات ComputedConstant
حتى الان جيدة جدا. ومع ذلك ، فإن الجوهرة المخفية في JEP هي القدرة على الحصول على مجموعات منComputedConstantعناصر. يتم تحقيق ذلك باستخدام طريقة المصنع التي لا توفر واحدةComputedConstant(مع مزودها) ولكن كلهListلComputedConstantالعناصر التي تتم معالجتها بواسطة مصمم خرائط واحد يمكنه تهيئة جميع العناصر في القائمة. هذا يسمح لعدد كبير من المتغيرات ليتم التعامل معها عبر ملفأعزبlist ، وبالتالي توفير مساحة مقارنة بوجود العديد من الثوابت الفردية وتهيئة lambdas (على سبيل المثال).
مثل أComputedConstantمتغير ، أListيتم إنشاء المتغير من خلال توفير مخطط عنصر – عادةً في شكل تعبير lambda ، والذي يستخدم لحساب القيمة المرتبطة بالعنصر i منListعند الوصول إلى قيمة العنصر لأول مرة:
class Fibonacci { static final ListInteger>> FIBONACCI= ComputedConstant.of(1_000, Fibonacci::fib); static int fib(int n) { return (n <2) ? n : FIBONACCI.get(n - 1) + FIBONACCI.get(n - 2); } int[] fibs=IntStream.range(0, 10) .map(Fibonacci::fib) .toArray(); // { 0, 1, 1, 2, 3, 5, 8, 13, 21, 34 }}
لاحظ كيف يوجد حقل واحد فقط من النوعListللتهيئة – يتم تنفيذ كل عملية حسابية أخرى عند الطلب عندما يكون العنصر المقابل في القائمةFIBONACCIيتم الوصول إليه.
عندما تعتمد عملية حسابية على المزيد من العمليات الحسابية الفرعية ، فإنها تستحث رسمًا بيانيًا للتبعية ، حيث تكون كل عملية حسابية عقدة في الرسم البياني ، ولها حواف صفرية أو أكثر لكل عقد من عقد الحساب الفرعي التي تعتمد عليها. على سبيل المثال ، الرسم البياني للتبعية المرتبط بـfib(5)يرد أدناه:
___________fib(5)___________ / ____fib(4)____ ____fib(3)____ / / fib(3) fib(2) fib(2) fib(1) / / / fib(2) fib(1) fib(1) fib(0) fib(1) fib(0)
تسمح واجهة برمجة التطبيقات المحسوبة الثابتة بنمذجة هذا بشكل نظيف ، مع الحفاظ على ضمانات الطي الثابت الجيدة وسلامة التحديثات في حالة الوصول متعدد الخيوط.
مجموعات المعايير
تم تشغيل هذه المعايير على نفس النظام الأساسي كما هو مذكور أعلاه وتظهر مجموعات منComputedConstantتتمتع العناصر بنفس مزايا الأداء التي تتمتع بها العناصر الفردية:
Benchmark Mode Cnt Score Error UnitsstaticHolder avgt 15 0.570 ? 0.005 ns/op // int[] in a holder classdoubleChecked avgt 15 1.124 ? 0.044 ns/opconstant avgt 15 0.562 ? 0.005 ns/op // List
مرة أخرى ، فإنComputedConstantالساعات في سرعة مصفوفة ثابتة أصلية مع توفير مرونة أفضل بكثير عند التهيئة.
أداء المثيل
يتفوق أداء متغيرات وكائنات المثيل على أصحابها الذين يستخدمون المصطلح المزدوج الموضح أعلاه كما يمكن رؤيته في المعايير أدناه:
Benchmark Mode Cnt Score Error UnitsdoubleChecked avgt 15 1.259 ? 0.023 ns/opconstant avgt 15 0.728 ? 0.022 ns/op // ComputedConstant
لذا،ComputedConstantأسرع بنسبة 40٪ من فئة الحامل المزدوج التي تم اختبارها على جهازي.
أين هي؟
في وقت كتابة هذا المقال ،ComputedConstantغير متاح بعد في مستودع JDK الرئيسي. تحقق من القسم التالي للحصول على رابط لشفرة المصدر المقترحة.
موارد
شكر وتقدير
تمت كتابة أجزاء من النص في هذه المقالة بواسطة موريزيو سيمادامور