لقد وضعنا المسودة أخيرًا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 الخاص بي وتشير النتائج الأولية إلى أداء ممتاز لـstatic
ComputedConstant
مجالات:
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 الرئيسي. تحقق من القسم التالي للحصول على رابط لشفرة المصدر المقترحة.
موارد
شكر وتقدير
تمت كتابة أجزاء من النص في هذه المقالة بواسطة موريزيو سيمادامور