هل هناك فرق في الأداء بين لحلقة ومقابل كل حلقة؟

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

  •  05-07-2019
  •  | 
  •  

سؤال

وماذا، إن وجدت، هو الفرق في الأداء بين الحلقات التاليين؟

for (Object o: objectArrayList) {
    o.DoSomething();
}

و

for (int i=0; i<objectArrayList.size(); i++) {
    objectArrayList.get(i).DoSomething();
}
هل كانت مفيدة؟

المحلول

ومن البند 46 في جافا الفعالة بقلم جوشوا بلوك:

<اقتباس فقرة>   

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

// The preferred idiom for iterating over collections and arrays
for (Element e : elements) {
    doSomething(e);
}
     

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

نصائح أخرى

وجميع هذه الحلقات أن تفعل الشيء نفسه بالضبط، أريد فقط أن تظهر هذه قبل رمي في بلدي اثنين سنتا.

أولا، والطريقة الكلاسيكية للحلقات من خلال قائمة:

for(int i=0;i<strings.size();i++) { /* do something using strings.get(i) */ }

والثانية، والطريقة المفضلة لأنه أقل عرضة للخطأ (عدد المرات التي YOU القيام به "عفوا، مختلطة المتغيرات i و j في هذه الحلقات ضمن حلقات" شيء؟).

for(String s : strings) { /* do something using s */ }

وثالثا، الدقيقة الأمثل لحلقة:

int size = strings.size();
for(int i=0;++i<=size;) { /* do something using strings.get(i) */ }

والآن اثنين سنتا الفعلية: على الأقل عندما كنت في اختبار هذه، كان ثلث ميلي ثانية أسرع عندما تعول على الوقت الذي استغرقته لكل نوع من حلقة مع عملية بسيطة فيها المتكررة بضعة ملايين من الأوقات - كان هذا باستخدام جافا 5 مع jre1.6u10 على ويندوز في حالة أي شخص مهتم.

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

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

ومقابل كل حلقة عموما يجب أن يكون من المفضل. قد يكون النهج "الحصول على" أبطأ إذا تنفيذ قائمة تستخدمه لا يدعم الوصول العشوائي. على سبيل المثال، إذا تم استخدام قائمة متصلة، هل تحمل تكلفة عبور، في حين أن ل-كل نهج يستخدم مكرر أن يتتبع موقفها في القائمة. مزيد من المعلومات عن لأ href = "http://today.java.net/pub/a/today/2006/11/07/nuances-of-java-5-for-each-loop.html" يختلط <= " نوفولو noreferrer "> الفروق الدقيقة في مقابل كل حلقة .

وأعتقد أن المادة هي الآن هنا: الموقع الجديد

وكان الارتباط هو موضح هنا ميت.

حسنا، تأثير الأداء هو في الغالب تافهة، ولكن ليس صفرا. اذا نظرتم الى جافادوك واجهة RandomAccess:

<اقتباس فقرة>   

وكقاعدة عامة، على قائمة   تنفيذ ينبغي أن تنفذ هذه   واجهة إذا، على الحالات النموذجية لل   الطبقة، هذه الحلقة:

for (int i=0, n=list.size(); i < n; i++)
    list.get(i);
     

ويعمل بشكل أسرع من هذه الحلقة:

for (Iterator i=list.iterator(); i.hasNext();)
      i.next();

ومقابل كل حلقة يتم استخدام الإصدار مع مكرر، وذلك لArrayList على سبيل المثال، مقابل كل حلقة ليست أسرع.

ويبدو أن هناك فرقا للأسف.

إذا نظرتم الى رمز بايت ولدت لكلا النوعين من الحلقات، فهي مختلفة.

وهنا مثال من التعليمات البرمجية المصدر Log4j.

في /log4j-api/src/main/java/org/apache/logging/log4j/MarkerManager.java لدينا فئة داخلية ثابتة تسمى Log4jMarker الذي يحدد:

    /*
     * Called from add while synchronized.
     */
    private static boolean contains(final Marker parent, final Marker... localParents) {
        //noinspection ForLoopReplaceableByForEach
        for (final Marker marker : localParents) {
            if (marker == parent) {
                return true;
            }
        }
        return false;
    }

ومع حلقة القياسية:

  private static boolean contains(org.apache.logging.log4j.Marker, org.apache.logging.log4j.Marker...);
    Code:
       0: iconst_0
       1: istore_2
       2: aload_1
       3: arraylength
       4: istore_3
       5: iload_2
       6: iload_3
       7: if_icmpge     29
      10: aload_1
      11: iload_2
      12: aaload
      13: astore        4
      15: aload         4
      17: aload_0
      18: if_acmpne     23
      21: iconst_1
      22: ireturn
      23: iinc          2, 1
      26: goto          5
      29: iconst_0
      30: ireturn

ومع مقابل كل من:

  private static boolean contains(org.apache.logging.log4j.Marker, org.apache.logging.log4j.Marker...);
    Code:
       0: aload_1
       1: astore_2
       2: aload_2
       3: arraylength
       4: istore_3
       5: iconst_0
       6: istore        4
       8: iload         4
      10: iload_3
      11: if_icmpge     34
      14: aload_2
      15: iload         4
      17: aaload
      18: astore        5
      20: aload         5
      22: aload_0
      23: if_acmpne     28
      26: iconst_1
      27: ireturn
      28: iinc          4, 1
      31: goto          8
      34: iconst_0
      35: ireturn

ما هو مع ذلك أوراكل؟

ولقد حاول هذا مع جافا 7 و 8 على ويندوز 7.

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

وforeach يجعل نية من التعليمات البرمجية الخاصة بك أكثر وضوحا والتي يفضل عادة على تحسن طفيف جدا سرعة - إن وجدت

وكلما رأيت حلقة المفهرسة لدي تحليل لفترة أطول قليلا للتأكد من أنها تفعل ما أنا <م> التفكير هو الحال على سبيل المثال هل نبدأ من الصفر، وأنها لا تضمين أو استثناء نقطة النهاية الخ.؟

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

والتعليمة البرمجية التالية:

import java.lang.reflect.Array;
import java.util.ArrayList;
import java.util.List;

interface Function<T> {
    long perform(T parameter, long x);
}

class MyArray<T> {

    T[] array;
    long x;

    public MyArray(int size, Class<T> type, long x) {
        array = (T[]) Array.newInstance(type, size);
        this.x = x;
    }

    public void forEach(Function<T> function) {
        for (T element : array) {
            x = function.perform(element, x);
        }
    }
}

class Compute {
    int factor;
    final long constant;

    public Compute(int factor, long constant) {
        this.factor = factor;
        this.constant = constant;
    }

    public long compute(long parameter, long x) {
        return x * factor + parameter + constant;
    }
}

public class Main {

    public static void main(String[] args) {
        List<Long> numbers = new ArrayList<Long>(50000000);
        for (int i = 0; i < 50000000; i++) {
            numbers.add(i * i + 5L);
        }

        long x = 234553523525L;

        long time = System.currentTimeMillis();
        for (int i = 0; i < numbers.size(); i++) {
            x += x * 7 + numbers.get(i) + 3;
        }
        System.out.println(System.currentTimeMillis() - time);
        System.out.println(x);
        x = 0;
        time = System.currentTimeMillis();
        for (long i : numbers) {
            x += x * 7 + i + 3;
        }
        System.out.println(System.currentTimeMillis() - time);
        System.out.println(x);
        x = 0;
        numbers = null;
        MyArray<Long> myArray = new MyArray<Long>(50000000, Long.class, 234553523525L);
        for (int i = 0; i < 50000000; i++) {
            myArray.array[i] = i * i + 3L;
        }
        time = System.currentTimeMillis();
        myArray.forEach(new Function<Long>() {

            public long perform(Long parameter, long x) {
                return x * 8 + parameter + 5L;
            }
        });
        System.out.println(System.currentTimeMillis() - time);
        System.out.println(myArray.x);
        myArray = null;
        myArray = new MyArray<Long>(50000000, Long.class, 234553523525L);
        for (int i = 0; i < 50000000; i++) {
            myArray.array[i] = i * i + 3L;
        }
        time = System.currentTimeMillis();
        myArray.forEach(new Function<Long>() {

            public long perform(Long parameter, long x) {
                return new Compute(8, 5).compute(parameter, x);
            }
        });
        System.out.println(System.currentTimeMillis() - time);
        System.out.println(myArray.x);
    }
}

ويعطي التالية الإخراج على نظام بلدي:

224
-699150247503735895
221
-699150247503735895
220
-699150247503735895
219
-699150247503735895

وأنا على التوالي أوبونتو 12.10 ألفا مع OracleJDK 1.7 التحديث 6.

في بؤرة العام يحسن الكثير من المراوغات والعمليات reduntant بسيطة، وذلك في عام يجب أن لا تقلق حول لهم ما لم تكن هناك الكثير منهم في seqence أو أنها متداخلة بشكل كبير.

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

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

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

وهنا هو تحليل موجز للفرق وضعت من قبل فريق تطوير الروبوت:

https://www.youtube.com/watch؟v=MZOf3pOAM6A

والنتيجة هي أن هناك <م> هو فرق، وفي بيئات مقيدة جدا مع قوائم كبيرة جدا يمكن أن يكون هناك فرق ملحوظ. في تجاربها، ولكل حلقة استغرق ضعف المدة. ومع ذلك، كان تجاربها على مدى arraylist من 400،000 أعداد صحيحة. كان الفارق الفعلي لكل عنصر في مجموعة 6 <م> ميكروثانية . أنا لم تختبر ولم يقولوا، ولكن أتوقع الفرق لتكون أكبر قليلا باستخدام كائنات بدلا من الأوليات، ولكن لا يزال حتى إلا إذا كنت بناء كود المكتبة حيث كان لديك أي فكرة عن حجم ما سوف يطلب منك تكرار أكثر، وأعتقد أن الفرق هو لا يستحق مشددا حول.

حسب اسم objectArrayList متغير، وأفترض أنه لا مثيل java.util.ArrayList. في هذه الحالة، فإن الفارق في الأداء يكون غير ملحوظ.

وعلى صعيد آخر، اذا كان مثيل java.util.LinkedList، والنهج الثاني سيكون أبطأ بكثير كما List#get(int) هو O (ن) العملية.

وهكذا هو دائما يفضل النهج الأول ما لم يكن هناك حاجة بتصدر المنطق في الحلقة.

1. for(Object o: objectArrayList){
    o.DoSomthing();
}
and

2. for(int i=0; i<objectArrayList.size(); i++){
    objectArrayList.get(i).DoSomthing();
}

وكل من يفعل نفس الشيء ولكن للاستخدام البرمجة سهلة وآمنة لل-كل، هناك احتمالات عرضة للخطأ في طريقة 2ND استخدام.

وانها غريبة أن أحدا لم يذكر ما هو واضح - foreach يخصص الذاكرة (في شكل مكرر)، في حين أن الطبيعي لحلقة لا تخصيص أي ذاكرة. للألعاب على الروبوت، وهذه مشكلة، لأنه يعني أن جمع القمامة سيتم تشغيل بشكل دوري. في لعبة كنت لا تريد جامع القمامة لتشغيل ... EVER. حتى لا تستخدم حلقات foreach في التعادل (أو تقديم) الطريقة.

والجواب مقبول تجيب على السؤال، بصرف النظر عن حالة استثنائية من ArrayList ...

وبما أن معظم المطورين تعتمد على ArrayList (أتلست أعتقد ذلك)

وحتى وأنا ملزمة لإضافة الإجابة الصحيحة هنا.

ومباشرة من وثائق المطور: -

والمعززة لحلقة (كما تعرف أحيانا باسم "مقابل كل" حلقة) يمكن استخدامها للمجموعات التي تنفذ واجهة Iterable والمصفوفات. مع مجموعات، يتم تخصيص مكرر لإجراء مكالمات واجهة لhasNext () والقادم (). مع ArrayList، حلقة عد مكتوبة بخط اليد حول 3x أسرع (مع أو بدون JIT)، ولكن للمجموعات الأخرى المعززة لتركيب الحلقة سوف يكون معادلا بالضبط لاستخدام مكرر صريح.

وهناك العديد من البدائل لبالتكرار عبر صفيف:

static class Foo {
    int mSplat;
}

Foo[] mArray = ...

public void zero() {
    int sum = 0;
    for (int i = 0; i < mArray.length; ++i) {
        sum += mArray[i].mSplat;
    }
}

public void one() {
    int sum = 0;
    Foo[] localArray = mArray;
    int len = localArray.length;

    for (int i = 0; i < len; ++i) {
        sum += localArray[i].mSplat;
    }
}

public void two() {
    int sum = 0;
    for (Foo a : mArray) {
        sum += a.mSplat;
    }
}

وصفر () هو أبطأ، لأن JIT لا يمكن بعد تحسين بعيدا تكلفة الحصول على طول مجموعة مرة واحدة كل التكرار خلال الحلقة.

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

واثنين () هو الأسرع للأجهزة دون JIT، وتمييزه عن واحد () لأجهزة مع JIT. ويستخدم المعززة لتركيب الحلقة التي أدخلت في الإصدار 1.5 من لغة البرمجة جافا.

وهكذا، يجب عليك استخدام معزز للحلقة افتراضيا، ولكن النظر حلقة مكتوبة بخط اليد تحسب للتكرار ArrayList الأداء حرجة.

public class FirstJavaProgram {

    public static void main(String[] args) 
    {
        int a[]={1,2,3,45,6,6};

// Method 1: this is simple way to print array 

        for(int i=0;i<a.length;i++) 
        { 
            System.out.print(a[i]+" ");
        }

// Method 2: Enhanced For loop

        for(int i:a)
        {
            System.out.print(i+" ");
        }
    }
}

نعم، for-each البديل هو أسرع من من index-based-for-loop وضعها الطبيعي.

وfor-each البديل يستخدم iterator. لذا العبور أسرع من حلقة for العادية التي يستند المؤشر.
وذلك لأن الأمثل iterator عن العبور، لأنه يشير إلى <م> قبل العنصر التالي وفقط بعد العنصر السابق . أحد الأسباب لكونها index-based-for-loop لتكون بطيئة غير ذلك، فإنه يجب أن <م> حساب والانتقال إلى وضع العنصر في كل مرة التي ليست مع iterator.

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