سؤال

أنا أعمل على تطبيق وأحد أساليب التصميم يتضمن استخدامًا مكثفًا للغاية لـ instanceof المشغل أو العامل.على الرغم من أنني أعلم أن تصميم OO يحاول عمومًا تجنب الاستخدام instanceof, هذه قصة مختلفة وهذا السؤال يتعلق بالأداء فقط.كنت أتساءل عما إذا كان هناك أي تأثير على الأداء؟هل هو بنفس السرعة ==?

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

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

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

هل كانت مفيدة؟

المحلول

قام المترجمون الحديثون لـ JVM/JIC بإزالة أداء معظم العمليات "البطيئة" التقليدية، بما في ذلك مثيل، ومعالجة الاستثناء، والانعكاس، وما إلى ذلك.

كما كتب دونالد كنوث، "يجب أن ننسى الكفاءات الصغيرة، على سبيل المثال حوالي 97٪ من الوقت:التحسين المبكر هو أصل كل الشرور." ربما لن يمثل أداء مثيل مشكلة، لذا لا تضيع وقتك في التوصل إلى حلول غريبة حتى تتأكد من أن هذه هي المشكلة.

نصائح أخرى

يقترب

كتبت برنامج مرجعي لتقييم عمليات التنفيذ المختلفة:

  1. instanceof التنفيذ (كمرجع)
  2. كائن موجه عبر فئة مجردة و @Override طريقة الاختبار
  3. باستخدام تطبيق النوع الخاص
  4. getClass() == _.class تطبيق

إستعملت jmh لتشغيل المعيار باستخدام 100 مكالمة تمهيدية، و1000 تكرار تحت القياس، وبـ10 شوكات.لذلك تم قياس كل خيار بـ 10000 مرة، وهو ما يستغرق 12:18:57 لتشغيل المعيار بالكامل على جهاز MacBook Pro الخاص بي الذي يعمل بنظام macOS 10.12.4 وJava 1.8.يقيس المؤشر متوسط ​​الوقت لكل خيار.لمزيد من التفاصيل انظر التنفيذ الخاص بي على GitHub.

من أجل الإنتهاء:هناك الإصدار السابق من هذه الإجابة ومعياري.

نتائج

| Operation  | Runtime in nanoseconds per operation | Relative to instanceof |
|------------|--------------------------------------|------------------------|
| INSTANCEOF | 39,598 ± 0,022 ns/op                 | 100,00 %               |
| GETCLASS   | 39,687 ± 0,021 ns/op                 | 100,22 %               |
| TYPE       | 46,295 ± 0,026 ns/op                 | 116,91 %               |
| OO         | 48,078 ± 0,026 ns/op                 | 121,42 %               |

ليرة تركية ؛ د

في جافا 1.8 instanceof هو النهج الأسرع، على أية حال getClass() هو قريب جدا.

لقد قمت للتو بإجراء اختبار بسيط لمعرفة كيفية مقارنة أداء المثيل باستدعاء s.equals() البسيط لكائن سلسلة بحرف واحد فقط.

في حلقة 10.000.000، أعطاني مثيل 63-96 مللي ثانية، وأعطتني السلسلة يساوي 106-230 مللي ثانية

لقد استخدمت جافا jvm 6.

لذا، في اختباري البسيط، من الأسرع إجراء مثيل بدلاً من مقارنة سلسلة أحرف واحدة.

استخدام .equals() الخاص بـ Integer بدلاً من السلسلة أعطاني نفس النتيجة، فقط عندما استخدمت == كنت أسرع من المثيل بمقدار 20 مللي ثانية (في حلقة 10.000.000)

العناصر التي ستحدد تأثير الأداء هي:

  1. عدد الفئات المحتملة التي يمكن لعامل التشغيل إرجاعها بشكل صحيح
  2. توزيع بياناتك - هل يتم حل معظم حالات العمليات في المحاولة الأولى أو الثانية؟ستحتاج إلى وضع العمليات الأكثر احتمالية لإرجاع العمليات الحقيقية أولاً.
  3. بيئة النشر.يختلف التشغيل على Sun Solaris VM بشكل كبير عن Sun's Windows JVM.سيتم تشغيل Solaris في وضع "الخادم" افتراضيًا، بينما سيتم تشغيل Windows في وضع العميل.تحسينات JIT على Solaris ستجعل جميع الطرق قابلة للوصول بنفس الطريقة.

لقد أنشأت أ مقياس دقيق لأربع طرق مختلفة للإرسال.النتائج من سولاريس هي كما يلي، مع كون العدد الأصغر أسرع:

InstanceOf 3156
class== 2925 
OO 3083 
Id 3067 

الإجابة على سؤالك الأخير:ما لم يخبرك أحد المحللين أنك تقضي وقتًا سخيفًا في حالة:نعم، أنت تلتقط الأخطاء.

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

في الأوقات التي يتم فيها تحسين المترجمين بشكل كبير، من المحتمل أن تكون تخميناتك حول الاختناقات خاطئة تمامًا.

وبالروح الحقيقية لهذه الإجابة (التي أؤمن بها من كل قلبي):لا أعرف على الإطلاق مدى ارتباط مثيل و == بمجرد حصول مترجم jit على فرصة لتحسينه.

انا نسيت:لا تقم أبدًا بقياس الجولة الأولى.

لدي نفس السؤال، ولكن نظرًا لأنني لم أجد "مقاييس أداء" لحالة الاستخدام المشابهة لحالتي، فقد قمت بعمل المزيد من نماذج التعليمات البرمجية.على أجهزتي وJava 6 و7، الفرق بين مثيل التكرار وتشغيله 10 مليون هو

for 10 child classes - instanceof: 1200ms vs switch: 470ms
for 5 child classes  - instanceof:  375ms vs switch: 204ms

لذلك، يكون exampleof أبطأ حقًا، خاصة في عدد كبير من عبارات if-else-if، ولكن الفرق سيكون ضئيلًا في التطبيق الحقيقي.

import java.util.Date;

public class InstanceOfVsEnum {

    public static int c1, c2, c3, c4, c5, c6, c7, c8, c9, cA;

    public static class Handler {
        public enum Type { Type1, Type2, Type3, Type4, Type5, Type6, Type7, Type8, Type9, TypeA }
        protected Handler(Type type) { this.type = type; }
        public final Type type;

        public static void addHandlerInstanceOf(Handler h) {
            if( h instanceof H1) { c1++; }
            else if( h instanceof H2) { c2++; }
            else if( h instanceof H3) { c3++; }
            else if( h instanceof H4) { c4++; }
            else if( h instanceof H5) { c5++; }
            else if( h instanceof H6) { c6++; }
            else if( h instanceof H7) { c7++; }
            else if( h instanceof H8) { c8++; }
            else if( h instanceof H9) { c9++; }
            else if( h instanceof HA) { cA++; }
        }

        public static void addHandlerSwitch(Handler h) {
            switch( h.type ) {
                case Type1: c1++; break;
                case Type2: c2++; break;
                case Type3: c3++; break;
                case Type4: c4++; break;
                case Type5: c5++; break;
                case Type6: c6++; break;
                case Type7: c7++; break;
                case Type8: c8++; break;
                case Type9: c9++; break;
                case TypeA: cA++; break;
            }
        }
    }

    public static class H1 extends Handler { public H1() { super(Type.Type1); } }
    public static class H2 extends Handler { public H2() { super(Type.Type2); } }
    public static class H3 extends Handler { public H3() { super(Type.Type3); } }
    public static class H4 extends Handler { public H4() { super(Type.Type4); } }
    public static class H5 extends Handler { public H5() { super(Type.Type5); } }
    public static class H6 extends Handler { public H6() { super(Type.Type6); } }
    public static class H7 extends Handler { public H7() { super(Type.Type7); } }
    public static class H8 extends Handler { public H8() { super(Type.Type8); } }
    public static class H9 extends Handler { public H9() { super(Type.Type9); } }
    public static class HA extends Handler { public HA() { super(Type.TypeA); } }

    final static int cCycles = 10000000;

    public static void main(String[] args) {
        H1 h1 = new H1();
        H2 h2 = new H2();
        H3 h3 = new H3();
        H4 h4 = new H4();
        H5 h5 = new H5();
        H6 h6 = new H6();
        H7 h7 = new H7();
        H8 h8 = new H8();
        H9 h9 = new H9();
        HA hA = new HA();

        Date dtStart = new Date();
        for( int i = 0; i < cCycles; i++ ) {
            Handler.addHandlerInstanceOf(h1);
            Handler.addHandlerInstanceOf(h2);
            Handler.addHandlerInstanceOf(h3);
            Handler.addHandlerInstanceOf(h4);
            Handler.addHandlerInstanceOf(h5);
            Handler.addHandlerInstanceOf(h6);
            Handler.addHandlerInstanceOf(h7);
            Handler.addHandlerInstanceOf(h8);
            Handler.addHandlerInstanceOf(h9);
            Handler.addHandlerInstanceOf(hA);
        }
        System.out.println("Instance of - " + (new Date().getTime() - dtStart.getTime()));

        dtStart = new Date();
        for( int i = 0; i < cCycles; i++ ) {
            Handler.addHandlerSwitch(h1);
            Handler.addHandlerSwitch(h2);
            Handler.addHandlerSwitch(h3);
            Handler.addHandlerSwitch(h4);
            Handler.addHandlerSwitch(h5);
            Handler.addHandlerSwitch(h6);
            Handler.addHandlerSwitch(h7);
            Handler.addHandlerSwitch(h8);
            Handler.addHandlerSwitch(h9);
            Handler.addHandlerSwitch(hA);
        }
        System.out.println("Switch of - " + (new Date().getTime() - dtStart.getTime()));
    }
}

instanceof سريع حقًا، ولا يأخذ سوى عدد قليل من تعليمات وحدة المعالجة المركزية.

على ما يبدو، إذا كانت الطبقة X لا يحتوي على فئات فرعية محملة (يعرف JVM)، instanceof يمكن تحسينها على النحو التالي:

     x instanceof X    
==>  x.getClass()==X.class  
==>  x.classID == constant_X_ID

التكلفة الرئيسية هي مجرد قراءة!

لو X تم تحميل فئات فرعية، وهناك حاجة إلى عدد قليل من القراءات؛من المحتمل أن يكونوا في موقع مشترك، لذا فإن التكلفة الإضافية منخفضة جدًا أيضًا.

اخبارسعيدة يا جماعة!

من المحتمل أن يكون مثيل المثيل أكثر تكلفة من التساوي البسيط في معظم تطبيقات العالم الحقيقي (أي تلك التي تكون هناك حاجة فعلية لمثيلها، ولا يمكنك حلها عن طريق تجاوز طريقة شائعة، مثل كل الكتب المدرسية للمبتدئين وكذلك دميان أعلاه يقترح).

لماذا هذا؟لأن ما سيحدث على الأرجح هو أن لديك العديد من الواجهات، التي توفر بعض الوظائف (على سبيل المثال، الواجهات x وy وz)، وبعض الكائنات التي يمكن التعامل معها والتي قد (أو لا) تنفذ إحدى تلك الواجهات...ولكن ليس بشكل مباشر.قل مثلا عندي:

ث يمتد س

ينفذ ث

ب يمتد أ

C يمتد B، ينفذ y

D يمتد C، ينفذ z

لنفترض أنني أقوم بمعالجة مثيل D، الكائن d.تتطلب الحوسبة (مثيل d لـ x) استخدام d.getClass()، والتكرار عبر الواجهات التي تنفذها لمعرفة ما إذا كان أحدها == إلى x، وإذا لم يكن الأمر كذلك، فافعل ذلك مرة أخرى بشكل متكرر لجميع أسلافهم...في حالتنا، إذا قمت بإجراء استكشاف أولي موسع لتلك الشجرة، فسوف تحصل على 8 مقارنات على الأقل، بافتراض أن y وz لا يمددان أي شيء...

من المرجح أن يكون تعقيد شجرة الاشتقاق في العالم الحقيقي أعلى.في بعض الحالات، يمكن لـ JIT تحسين معظمها، إذا كان قادرًا على حل d مسبقًا باعتباره، في جميع الحالات الممكنة، مثيلًا لشيء يمتد x.ومع ذلك، من الناحية الواقعية، سوف تمر عبر هذه الشجرة في معظم الأوقات.

إذا أصبحت هذه مشكلة، أود أن أقترح استخدام خريطة معالج بدلاً من ذلك، وربط الفئة الملموسة للكائن بإغلاق يقوم بالمعالجة.إنه يزيل مرحلة اجتياز الشجرة لصالح رسم الخرائط المباشر.ومع ذلك، احذر من أنه إذا قمت بتعيين معالج للفئة C، فلن يتم التعرف على الكائن d الموجود أعلاه.

هنا سنتي 2، وآمل أن يساعدوا ...

"instanceof" هو في الواقع عامل تشغيل، مثل + أو -، وأعتقد أنه يحتوي على تعليمات كود JVM الخاصة به.يجب أن يكون سريعًا جدًا.

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

Instanceof سريع جدًا.يتلخص الأمر في رمز ثانوي يتم استخدامه لمقارنة مرجع الفئة.جرب بضعة ملايين من الأمثلة في حلقة وانظر بنفسك.

يذكر دميان وبولس نقطة جيدة؛ لكن, ، فإن موضع الكود الذي سيتم تنفيذه يعتمد حقًا على الطريقة التي تريد بها استخدام البيانات ...

أنا من أشد المعجبين بكائنات البيانات الصغيرة التي يمكن استخدامها بعدة طرق.إذا اتبعت نهج التجاوز (متعدد الأشكال)، فلا يمكن استخدام كائناتك إلا "بطريقة واحدة".

وهنا يأتي دور الأنماط...

يمكنك استخدام الإرسال المزدوج (كما في نمط الزائر) لتطلب من كل كائن "الاتصال بك" ويمرر نفسه - سيؤدي هذا إلى حل نوع الكائن. لكن (مرة أخرى) ستحتاج إلى فصل دراسي يمكنه "فعل الأشياء" بجميع الأنواع الفرعية الممكنة.

أفضل استخدام نمط الإستراتيجية، حيث يمكنك تسجيل الاستراتيجيات لكل نوع فرعي تريد التعامل معه.شيء من هذا القبيل ما يلي.لاحظ أن هذا يساعد فقط في مطابقة النوع تمامًا، ولكنه يتمتع بميزة أنه قابل للتوسيع - حيث يمكن للمساهمين الخارجيين إضافة أنواعهم ومعالجاتهم الخاصة.(هذا جيد للأطر الديناميكية مثل OSGi، حيث يمكن إضافة حزم جديدة)

نأمل أن يكون هذا مصدر إلهام لبعض الأفكار الأخرى ...

package com.javadude.sample;

import java.util.HashMap;
import java.util.Map;

public class StrategyExample {
    static class SomeCommonSuperType {}
    static class SubType1 extends SomeCommonSuperType {}
    static class SubType2 extends SomeCommonSuperType {}
    static class SubType3 extends SomeCommonSuperType {}

    static interface Handler<T extends SomeCommonSuperType> {
        Object handle(T object);
    }

    static class HandlerMap {
        private Map<Class<? extends SomeCommonSuperType>, Handler<? extends SomeCommonSuperType>> handlers_ =
            new HashMap<Class<? extends SomeCommonSuperType>, Handler<? extends SomeCommonSuperType>>();
        public <T extends SomeCommonSuperType> void add(Class<T> c, Handler<T> handler) {
            handlers_.put(c, handler);
        }
        @SuppressWarnings("unchecked")
        public <T extends SomeCommonSuperType> Object handle(T o) {
            return ((Handler<T>) handlers_.get(o.getClass())).handle(o);
        }
    }

    public static void main(String[] args) {
        HandlerMap handlerMap = new HandlerMap();

        handlerMap.add(SubType1.class, new Handler<SubType1>() {
            @Override public Object handle(SubType1 object) {
                System.out.println("Handling SubType1");
                return null;
            } });
        handlerMap.add(SubType2.class, new Handler<SubType2>() {
            @Override public Object handle(SubType2 object) {
                System.out.println("Handling SubType2");
                return null;
            } });
        handlerMap.add(SubType3.class, new Handler<SubType3>() {
            @Override public Object handle(SubType3 object) {
                System.out.println("Handling SubType3");
                return null;
            } });

        SubType1 subType1 = new SubType1();
        handlerMap.handle(subType1);
        SubType2 subType2 = new SubType2();
        handlerMap.handle(subType2);
        SubType3 subType3 = new SubType3();
        handlerMap.handle(subType3);
    }
}

يعتبر exampleof فعالاً للغاية، لذلك من غير المرجح أن يتأثر أدائك.ومع ذلك، فإن استخدام الكثير من المثيلات يشير إلى مشكلة في التصميم.

إذا كان بإمكانك استخدام xClass == String.class، فهذا أسرع.ملحوظة:لا تحتاج إلى مثيل للفصول النهائية.

من الصعب تحديد كيفية تنفيذ JVM معين لمثيل، ولكن في معظم الحالات، تكون الكائنات قابلة للمقارنة بالبنيات والفئات أيضًا وكل بنية كائن لها مؤشر إلى بنية الفئة التي هي مثيل لها.لذلك في الواقع مثيل لـ

if (o instanceof java.lang.String)

قد يكون بنفس سرعة رمز C التالي

if (objectStruct->iAmInstanceOf == &java_lang_String_class)

بافتراض وجود مترجم JIT ويقوم بعمل لائق.

مع الأخذ في الاعتبار أن هذا يعني فقط الوصول إلى المؤشر، والحصول على مؤشر عند إزاحة معينة يشير إليها المؤشر ومقارنتها بمؤشر آخر (وهو في الأساس نفس اختبار تساوي أرقام 32 بت)، أود أن أقول إن العملية يمكن في الواقع كن سريعًا جدًا.

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

حالة هو تحذير من سوء التصميم الموجه للكائنات.

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

لإعطاء مثال برمجة مبسطة صغيرة جدا.

if (SomeObject instanceOf Integer) {
  [do something]
}
if (SomeObject instanceOf Double) {
  [do something different]
}

إذا كانت البنية سيئة، فقد كان الخيار الأفضل هو أن يكون SomeObject هو الفئة الأصلية لفئتين فرعيتين حيث يتجاوز كل فئة فرعية طريقة (doSomething) بحيث يبدو الرمز على هذا النحو:

Someobject.doSomething();

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

بشكل عام، السبب وراء استياء عامل التشغيل "instanceof" في حالة كهذه (حيث يتحقق مثيل of من الفئات الفرعية لهذه الفئة الأساسية) هو أن ما يجب عليك فعله هو نقل العمليات إلى طريقة ما وتجاوزها بالشكل المناسب فئات فرعية.على سبيل المثال، إذا كان لديك:

if (o instanceof Class1)
   doThis();
else if (o instanceof Class2)
   doThat();
//...

يمكنك استبدال ذلك ب

o.doEverything();

ثم قم بتنفيذ "doEverything ()" في Class1 باستدعاء "doThis ()" وفي Class2 باستدعاء "doThat ()" وما إلى ذلك.

في إصدار Java الحديث، يكون عامل تشغيل مثيل أسرع مثل استدعاء أسلوب بسيط.هذا يعنى:

if(a instanceof AnyObject){
}

أسرع مثل:

if(a.getType() == XYZ){
}

شيء آخر هو إذا كنت بحاجة إلى تتالي العديد من مثيلات.ومن ثم يكون المحول الذي يتم الاتصال به مرة واحدة فقط getType() أسرع.

إذا كانت السرعة هي هدفك الوحيد، فإن استخدام ثوابت int لتحديد الفئات الفرعية يبدو أنه يختصر أجزاء من الثانية من الوقت

static final int ID_A = 0;
static final int ID_B = 1;
abstract class Base {
  final int id;
  Base(int i) { id = i; }
}
class A extends Base {
 A() { super(ID_A); }
}
class B extends Base {
 B() { super(ID_B); }
}
...
Base obj = ...
switch(obj.id) {
case  ID_A: .... break;
case  ID_B: .... break;
}

تصميم OO رهيب، ولكن إذا كان تحليل أدائك يشير إلى أن هذا هو المكان الذي يوجد فيه عنق الزجاجة، فربما.في الكود الخاص بي، يستغرق رمز الإرسال 10% من إجمالي وقت التنفيذ، وربما ساهم هذا في تحسين السرعة الإجمالية بنسبة 1%.

أكتب اختبار أداء بناءً على النموذج الأصلي jmh-Java-benchmark:2.21.JDK هو openjdk والإصدار هو 1.8.0_212.آلة الاختبار هي ماك برو.نتيجة الاختبار هي:

Benchmark                Mode  Cnt    Score   Error   Units
MyBenchmark.getClasses  thrpt   30  510.818 ± 4.190  ops/us
MyBenchmark.instanceOf  thrpt   30  503.826 ± 5.546  ops/us

تظهر النتيجة أن:getClass أفضل من exampleOf، وهو ما يتعارض مع الاختبارات الأخرى.ومع ذلك، لا أعرف السبب.

رمز الاختبار أدناه:

public class MyBenchmark {

public static final Object a = new LinkedHashMap<String, String>();

@Benchmark
@BenchmarkMode(Mode.Throughput)
@OutputTimeUnit(TimeUnit.MICROSECONDS)
public boolean instanceOf() {
    return a instanceof Map;
}

@Benchmark
@BenchmarkMode(Mode.Throughput)
@OutputTimeUnit(TimeUnit.MICROSECONDS)
public boolean getClasses() {
    return a.getClass() == HashMap.class;
}

public static void main(String[] args) throws RunnerException {
    Options opt =
        new OptionsBuilder().include(MyBenchmark.class.getSimpleName()).warmupIterations(20).measurementIterations(30).forks(1).build();
    new Runner(opt).run();
}
}

يجب عليك القياس/الملف الشخصي إذا كانت مشكلة الأداء في مشروعك حقًا.إذا كان الأمر كذلك فإنني أوصي بإعادة التصميم - إن أمكن.أنا متأكد تمامًا من أنك لا تستطيع التغلب على التنفيذ الأصلي للنظام الأساسي (المكتوب بلغة C).يجب عليك أيضًا مراعاة الميراث المتعدد في هذه الحالة.

يجب أن تخبرنا المزيد عن المشكلة، ربما يمكنك استخدام متجر ترابطي، على سبيل المثال.Map<Class, Object> إذا كنت مهتمًا فقط بأنواع الخرسانة.

فيما يتعلق بملاحظة بيتر لوري بأنك لا تحتاج إلى مثيل للفصول النهائية ويمكنك فقط استخدام المساواة المرجعية، كن حذرًا!على الرغم من أنه لا يمكن تمديد الفئات النهائية، إلا أنه لا يمكن ضمان تحميلها بواسطة نفس أداة تحميل الفئة.استخدم فقط x.getClass() == SomeFinal.class أو ما شابه ذلك إذا كنت متأكدًا تمامًا من وجود أداة تحميل فئة واحدة فقط قيد التشغيل لهذا القسم من التعليمات البرمجية.

أفضّل أيضًا أسلوب التعداد، لكني سأستخدم فئة أساسية مجردة لإجبار الفئات الفرعية على تنفيذ الأمر getType() طريقة.

public abstract class Base
{
  protected enum TYPE
  {
    DERIVED_A, DERIVED_B
  }

  public abstract TYPE getType();

  class DerivedA extends Base
  {
    @Override
    public TYPE getType()
    {
      return TYPE.DERIVED_A;
    }
  }

  class DerivedB extends Base
  {
    @Override
    public TYPE getType()
    {
      return TYPE.DERIVED_B;
    }
  }
}

اعتقدت أنه قد يكون من المفيد تقديم مثال مضاد للإجماع العام في هذه الصفحة على أن "مثيل" ليس مكلفًا بدرجة كافية للقلق.لقد وجدت أن لدي بعض التعليمات البرمجية في حلقة داخلية (في بعض المحاولات التاريخية للتحسين).

if (!(seq instanceof SingleItem)) {
  seq = seq.head();
}

حيث يؤدي استدعاء head() على عنصر واحد إلى إرجاع القيمة دون تغيير.استبدال الكود ب

seq = seq.head();

يمنحني تسريعًا من 269 مللي ثانية إلى 169 مللي ثانية، على الرغم من حقيقة أن هناك بعض الأشياء الثقيلة جدًا تحدث في الحلقة، مثل تحويل السلسلة إلى مزدوج.من الممكن بالطبع أن يكون التسريع ناتجًا عن إزالة الفرع الشرطي أكثر من إزالة مثيل العامل نفسه؛لكنني اعتقدت أنه يستحق الذكر.

أنت تركز على الشيء الخطأ.ربما لا يكون الفرق بين المثيل وأي طريقة أخرى للتحقق من نفس الشيء قابلاً للقياس.إذا كان الأداء أمرًا بالغ الأهمية، فمن المحتمل أن تكون لغة Java هي اللغة الخاطئة.السبب الرئيسي هو أنه لا يمكنك التحكم عندما يقرر VM أنه يريد جمع القمامة، الأمر الذي يمكن أن يأخذ وحدة المعالجة المركزية إلى 100٪ لعدة ثوانٍ في برنامج كبير (كان MagicDraw 10 رائعًا لذلك).ما لم تكن تتحكم في كل جهاز كمبيوتر سيتم تشغيل هذا البرنامج عليه، فلا يمكنك ضمان إصدار JVM الذي سيتم تشغيله عليه، والعديد من الإصدارات الأقدم كانت بها مشكلات كبيرة في السرعة.إذا كان تطبيقًا صغيرًا، فقد تكون موافقًا على استخدام Java، ولكن إذا كنت تقرأ البيانات وتتجاهلها باستمرار، فأنت كذلك سوف لاحظ عندما يبدأ GC.

مرخصة بموجب: CC-BY-SA مع الإسناد
لا تنتمي إلى StackOverflow
scroll top