مقارنة المصفوفات الصغيرة مقابل المصفوفاتالقوائم في جافا:هل رمز القياس الخاص بي خاطئ؟

StackOverflow https://stackoverflow.com/questions/2227947

سؤال

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

أنا أقارن array الأداء على عدد كبير من القراءات للقوائم الصغيرة ArrayList وأيضًا لمجرد وجود المتغيرات في متناول اليد.أجد أن المصفوفة تتفوق ArrayList بعامل 2.5 و حتى يتفوق على مجرد وجود مراجع الكائن.

ما أود معرفته هو:

  1. هل المعيار الخاص بي بخير؟ لقد قمت بتبديل ترتيب الاختبارات وعدد مرات التشغيل دون أي تغيير.لقد استخدمت أيضًا المللي ثانية بدلاً من النانو ثانية دون جدوى.
  2. هل يجب علي تحديد أي خيارات Java لتقليل هذا الاختلاف؟
  3. إذا كان هذا الاختلاف حقيقيا، في هذه الحالة لا ينبغي لي أن أفضل Test[] ل ArrayList<Test> في هذه الحالة ووضع الكود اللازم لتحويلها؟ من الواضح أنني أقرأ أكثر بكثير من الكتابة.

JVM هو Java 1.6.0_17 على OSX وهو يعمل بالتأكيد في وضع Hotspot.

  public class ArraysVsLists {

    static int RUNS = 100000;

    public static void main(String[] args) {
        long t1;
        long t2;

        Test test1 = new Test();
        test1.thing = (int)Math.round(100*Math.random());
        Test test2 = new Test();
        test2.thing = (int)Math.round(100*Math.random());

        t1 = System.nanoTime();

        for (int i=0; i<RUNS; i++) {
            test1.changeThing(i);
            test2.changeThing(i);
        }

        t2 = System.nanoTime();
        System.out.println((t2-t1) + " How long NO collection");

        ArrayList<Test> list = new ArrayList<Test>(1);
        list.add(test1);
        list.add(test2);
        // tried this too: helps a tiny tiny bit 
        list.trimToSize();

        t1= System.nanoTime();

        for (int i=0; i<RUNS; i++) {
            for (Test eachTest : list) {
                eachTest.changeThing(i);
            }
        }

        t2 = System.nanoTime();
        System.out.println((t2-t1) + " How long collection");


        Test[] array = new Test[2];
        list.toArray(array);

        t1= System.nanoTime();

        for (int i=0; i<RUNS; i++) {
            for (Test test : array) {
                test.changeThing(i);
            }
        }

        t2 = System.nanoTime();
        System.out.println((t2-t1) + " How long array ");

    }
}

class Test {
    int thing;
    int thing2;
    public void changeThing(int addThis) {
        thing2 = addThis + thing;
    }
}
هل كانت مفيدة؟

المحلول

من الصعب جدًا الحصول على Microbenchmarks بشكل صحيح على منصة مثل Java.يجب عليك بالتأكيد استخراج التعليمات البرمجية ليتم قياسها في طرق منفصلة، ​​وتشغيلها بضعة آلاف من المرات كإحماء ثم قياسها.لقد فعلت ذلك (الرمز أدناه) والنتيجة هي أن الوصول المباشر من خلال المراجع يكون أسرع بثلاث مرات من خلال المصفوفة، ولكن المجموعة لا تزال أبطأ بعامل 2.

تعتمد هذه الأرقام على خيارات JVM -server -XX:+DoEscapeAnalysis.بدون -server, ، وذلك باستخدام المجموعة بشكل جذري أبطأ (ولكن الغريب أن الوصول المباشر والمصفوفة أسرع قليلاً، مما يشير إلى أن هناك شيئًا غريبًا يحدث). -XX:+DoEscapeAnalysis يؤدي إلى تسريع المجموعة بنسبة 30% أخرى، ولكن من المشكوك فيه جدًا ما إذا كانت ستعمل أيضًا مع رمز الإنتاج الفعلي الخاص بك.

عموما استنتاجي سيكون:انسَ أمر المعايير القياسية الدقيقة، فمن الممكن أن تكون مضللة بسهولة.قم بالقياس بالقرب من رمز الإنتاج قدر الإمكان دون الحاجة إلى إعادة كتابة التطبيق بالكامل.

import java.util.ArrayList;

public class ArrayTest {

    static int RUNS_INNER = 1000;
    static int RUNS_WARMUP = 10000;
    static int RUNS_OUTER = 100000;

    public static void main(String[] args) {
        long t1;
        long t2;

        Test test1 = new Test();
        test1.thing = (int)Math.round(100*Math.random());
        Test test2 = new Test();
        test2.thing = (int)Math.round(100*Math.random());

        for(int i=0; i<RUNS_WARMUP; i++)
        {
            testRefs(test1, test2);            
        }
        t1 = System.nanoTime();
        for(int i=0; i<RUNS_OUTER; i++)
        {
            testRefs(test1, test2);            
        }

        t2 = System.nanoTime();
        System.out.println((t2-t1)/1000000.0 + " How long NO collection");

        ArrayList<Test> list = new ArrayList<Test>(1);
        list.add(test1);
        list.add(test2);
        // tried this too: helps a tiny tiny bit 
        list.trimToSize();

        for(int i=0; i<RUNS_WARMUP; i++)
        {
            testColl(list);
        }
        t1= System.nanoTime();

        for(int i=0; i<RUNS_OUTER; i++)
        {
            testColl(list);
        }

        t2 = System.nanoTime();
        System.out.println((t2-t1)/1000000.0 + " How long collection");


        Test[] array = new Test[2];
        list.toArray(array);

        for(int i=0; i<RUNS_WARMUP; i++)
        {
            testArr(array);            
        }
        t1= System.nanoTime();

        for(int i=0; i<RUNS_OUTER; i++)
        {
            testArr(array);
        }

        t2 = System.nanoTime();
        System.out.println((t2-t1)/1000000.0 + " How long array ");

    }

    private static void testArr(Test[] array)
    {
        for (int i=0; i<RUNS_INNER; i++) {
            for (Test test : array) {
                test.changeThing(i);
            }
        }
    }

    private static void testColl(ArrayList<Test> list)
    {
        for (int i=0; i<RUNS_INNER; i++) {
            for (Test eachTest : list) {
                eachTest.changeThing(i);
            }
        }
    }

    private static void testRefs(Test test1, Test test2)
    {
        for (int i=0; i<RUNS_INNER; i++) {
            test1.changeThing(i);
            test2.changeThing(i);
        }
    }
}

class Test {
    int thing;
    int thing2;
    public void changeThing(int addThis) {
        thing2 = addThis + thing;
    }
}

نصائح أخرى

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

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

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

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