ประสิทธิภาพการทำงานจะแตกต่างกันอย่างไรระหว่างสองลูปต่อไปนี้
for (Object o: objectArrayList) {
o.DoSomething();
}
และ
for (int i=0; i<objectArrayList.size(); i++) {
objectArrayList.get(i).DoSomething();
}
ประสิทธิภาพการทำงานจะแตกต่างกันอย่างไรระหว่างสองลูปต่อไปนี้
for (Object o: objectArrayList) {
o.DoSomething();
}
และ
for (int i=0; i<objectArrayList.size(); i++) {
objectArrayList.get(i).DoSomething();
}
คำตอบ:
จากรายการ 46 ในจาวาที่มีประสิทธิภาพโดย Joshua Bloch:
สำหรับแต่ละลูปที่ถูกนำมาใช้ในรีลีส 1.5 กำจัดความยุ่งเหยิงและโอกาสในการเกิดข้อผิดพลาดโดยการซ่อนตัววนซ้ำหรือตัวแปรดัชนีอย่างสมบูรณ์ สำนวนที่เป็นผลจะใช้กับคอลเลกชันและอาร์เรย์อย่างเท่าเทียมกัน:
// The preferred idiom for iterating over collections and arrays for (Element e : elements) { doSomething(e); }
เมื่อคุณเห็นเครื่องหมายจุดคู่ (:) ให้อ่านว่าเป็น "นิ้ว" ดังนั้นลูปด้านบนจะอ่านว่า“ สำหรับแต่ละองค์ประกอบอีในองค์ประกอบ” โปรดทราบว่าไม่มีการปรับประสิทธิภาพสำหรับการใช้ลูป for-each แม้กระทั่งสำหรับอาร์เรย์ ในความเป็นจริงมันอาจมีข้อได้เปรียบด้านประสิทธิภาพเล็กน้อยกว่าปกติสำหรับลูปในบางสถานการณ์เนื่องจากคำนวณขีด จำกัด ของดัชนีอาร์เรย์เพียงครั้งเดียว ในขณะที่คุณสามารถทำได้ด้วยมือ (รายการ 45) โปรแกรมเมอร์ไม่ได้ทำเช่นนั้นเสมอไป
ลูปทั้งหมดนี้ทำอย่างเดียวกันฉันแค่ต้องการแสดงสิ่งเหล่านี้ก่อนที่จะโยนลงไปในสองเซ็นต์ของฉัน
ก่อนวิธีที่คลาสสิกของการวนซ้ำผ่านรายการ:
for (int i=0; i < strings.size(); i++) { /* do something using strings.get(i) */ }
ประการที่สองวิธีที่แนะนำเนื่องจากมีข้อผิดพลาดน้อยกว่า (คุณทำ "โอ๊ะโอผสมตัวแปร i และ j ในลูปเหล่านี้ในลูป" กี่ครั้ง?)
for (String s : strings) { /* do something using s */ }
ประการที่สามการปรับขนาดเล็กสำหรับลูป:
int size = strings.size();
for (int i = -1; ++i < size;) { /* do something using strings.get(i) */ }
ทีนี้สองเซนต์จริง ๆ : อย่างน้อยตอนที่ผมทดสอบพวกนี้อันที่สามนั้นเร็วที่สุดเมื่อนับมิลลิวินาทีว่ามันต้องใช้เวลานานเท่าใดสำหรับลูปแต่ละประเภทด้วยการใช้งานที่เรียบง่ายซ้ำสองสามล้านครั้ง - นี่ใช้ Java 5 กับ jre1.6u10 บน Windows ในกรณีที่ทุกคนสนใจ
ในขณะที่อย่างน้อยก็ดูเหมือนว่าจะเป็นอย่างที่สามคือเร็วที่สุดคุณควรถามตัวเองว่าคุณต้องการรับความเสี่ยงจากการใช้การเพิ่มประสิทธิภาพตาแมวนี้ทุกที่ในรหัสวนลูปของคุณตั้งแต่สิ่งที่ฉันเห็น โดยปกติแล้วจะใช้เวลาส่วนใหญ่ที่สุดในโปรแกรมจริง ๆ (หรือบางทีฉันก็แค่ทำงานผิดที่ใครจะรู้) และเช่นเดียวกับที่ฉันกล่าวถึงในข้ออ้างสำหรับ Java for-each loop (บางคนอ้างถึงมันเป็นIterator loopและอื่น ๆ ในฐานะfor-in loop ) คุณมีโอกาสน้อยที่จะพบข้อผิดพลาดโง่ ๆ เมื่อใช้มัน และก่อนที่จะถกเถียงกันว่าสิ่งนี้จะยิ่งเร็วกว่าตัวอื่น ๆ ได้อย่างไรโปรดจำไว้ว่า javac นั้นไม่ได้ปรับปรุงประสิทธิภาพ bytecode เลย (ดีเกือบทุกอย่างเลย) มันแค่รวบรวมมัน
หากคุณใช้การเพิ่มประสิทธิภาพแบบไมโครและ / หรือซอฟต์แวร์ของคุณใช้ลูปแบบวนซ้ำและเช่นนั้นคุณอาจสนใจประเภทลูปที่สาม เพียงจำไว้ว่าให้ทำการวัดค่าซอฟต์แวร์ของคุณทั้งก่อนและหลังการเปลี่ยนลูป
get(int)
, Iterator
การใช้งานอื่น พิจารณาLinkedList
ว่าประสิทธิภาพการทำงานของfor(int i=0;i<strings.size();i++) { /* do something using strings.get(i) */ }
แย่ลงมากเพราะทำget(int)
n ครั้ง
โดยทั่วไปแล้วควรจะเลือกลูปสำหรับแต่ละลูป วิธี "รับ" อาจช้าลงหากการใช้งานรายการที่คุณใช้ไม่รองรับการเข้าถึงแบบสุ่ม ตัวอย่างเช่นหากใช้ LinkedList คุณจะต้องเสียค่าใช้จ่ายในการสำรวจเส้นทางในขณะที่วิธีการสำหรับแต่ละรายการจะใช้ตัววนซ้ำที่คอยติดตามตำแหน่งในรายการ ข้อมูลเพิ่มเติมเกี่ยวกับความแตกต่างของแต่ละคนสำหรับวง
ฉันคิดว่าบทความนี้อยู่ที่นี่แล้ว: ตำแหน่งใหม่
ลิงก์ที่แสดงที่นี่เสียชีวิต
ผลกระทบด้านประสิทธิภาพนั้นแทบไม่มีนัยสำคัญ แต่ก็ไม่เป็นศูนย์ หากคุณดูที่ JavaDoc ของRandomAccess
อินเตอร์เฟส:
ตามกฎง่ายๆการใช้งานรายการควรใช้อินเทอร์เฟซนี้ถ้าสำหรับอินสแตนซ์ทั่วไปของคลาสลูปนี้:
for (int i=0, n=list.size(); i < n; i++) list.get(i);
วิ่งเร็วกว่าลูปนี้:
for (Iterator i=list.iterator(); i.hasNext();) i.next();
และสำหรับแต่ละลูปจะใช้เวอร์ชันพร้อมตัววนซ้ำดังนั้นArrayList
ตัวอย่างเช่นสำหรับลูปแต่ละวงจะไม่เร็วที่สุด
Iterable
s แต่ไม่ใช่อาร์เรย์ สำหรับอาร์เรย์จะใช้ for-loop พร้อมกับตัวแปรดัชนี มีข้อมูลเกี่ยวกับเรื่องนี้ในเป็นJLS
น่าเสียดายที่มีความแตกต่าง
หากคุณดูที่โค้ดไบต์ที่สร้างขึ้นสำหรับลูปทั้งสองชนิดมันจะแตกต่างกัน
นี่คือตัวอย่างจากซอร์สโค้ด 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
เกิดอะไรขึ้นกับ Oracle นั่น?
ฉันได้ลองกับ Java 7 และ 8 บน Windows 7
ควรใช้ตัววนซ้ำแทนการทำดัชนี นี่เป็นเพราะตัววนซ้ำน่าจะเหมาะสมที่สุดสำหรับการใช้งานรายการในขณะที่การทำดัชนี (การเรียกรับ) อาจไม่ใช่ ตัวอย่างเช่น LinkedList เป็นรายการ แต่การทำดัชนีผ่านองค์ประกอบจะช้ากว่าการวนซ้ำโดยใช้ตัววนซ้ำ
foreach ทำให้เจตนาของโค้ดของคุณชัดเจนขึ้นและเป็นที่ต้องการมากกว่าการปรับปรุงความเร็วเล็กน้อย - ถ้ามี
เมื่อใดก็ตามที่ฉันเห็นลูปที่มีการจัดทำดัชนีฉันต้องแยกมันออกไปอีกหน่อยเพื่อให้แน่ใจว่ามันทำในสิ่งที่ฉันคิดว่ามันเป็นเช่นมันเริ่มจากศูนย์มันรวมหรือแยกจุดสิ้นสุดเป็นต้นหรือไม่?
เวลาส่วนใหญ่ของฉันดูเหมือนว่าจะใช้รหัสการอ่าน (ที่ฉันเขียนหรือคนอื่นเขียน) และความคมชัดมักจะสำคัญกว่าประสิทธิภาพ มันง่ายที่จะยกเลิกประสิทธิภาพการทำงานในวันนี้เพราะ Hotspot ทำหน้าที่ได้อย่างยอดเยี่ยม
รหัสต่อไปนี้:
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
ฉันใช้ Ubuntu 12.10 alpha กับ OracleJDK 1.7 update 6
โดยทั่วไปแล้ว HotSpot จะเพิ่มประสิทธิภาพของการอ้อมค้อมและการดำเนินการ reduntant ง่าย ๆ ดังนั้นโดยทั่วไปคุณไม่ควรกังวลเกี่ยวกับสิ่งเหล่านี้เว้นแต่จะมีพวกเขาจำนวนมากในสถานการณ์ที่ไม่เหมาะสมหรือพวกมันซ้อนกันอย่างหนัก
ในทางกลับกันการทำดัชนี get on LinkedList นั้นช้ากว่าการเรียกตัวถัดไปบนตัววนซ้ำสำหรับ LinkedList ดังนั้นคุณสามารถหลีกเลี่ยงการเข้าชมที่เกิดขึ้นในขณะที่ยังคงสามารถอ่านได้เมื่อคุณใช้ตัววนซ้ำ (โดยชัดแจ้งหรือโดยนัย
แม้ว่าจะมีบางอย่างเช่น ArrayList หรือ Vector ซึ่ง "get" เป็นการค้นหาอาร์เรย์อย่างง่าย แต่การวนซ้ำครั้งที่สองยังคงมีโอเวอร์เฮดเพิ่มเติมซึ่งครั้งแรกไม่มี ฉันคาดว่ามันช้ากว่าครั้งแรกเล็กน้อย
นี่คือการวิเคราะห์สั้น ๆ เกี่ยวกับความแตกต่างของทีมพัฒนา Android:
https://www.youtube.com/watch?v=MZOf3pOAM6A
ผลที่ตามมาก็คือว่ามีคือความแตกต่างและอยู่ในสภาพแวดล้อมที่ จำกัด มากกับรายการที่มีขนาดใหญ่มากมันอาจจะเป็นความแตกต่างที่เห็นได้ชัด ในการทดสอบของพวกเขาแต่ละวงใช้เวลานานเป็นสองเท่า อย่างไรก็ตามการทดสอบของพวกเขาอยู่ในรายการอาร์เรย์ของจำนวนเต็ม 400,000 ความแตกต่างที่เกิดขึ้นจริงต่อองค์ประกอบในอาร์เรย์ 6 microseconds ฉันยังไม่ได้ทดสอบและพวกเขาไม่ได้พูด แต่ฉันคาดหวังว่าความแตกต่างจะใหญ่กว่าการใช้วัตถุแทนที่จะเป็นแบบดั้งเดิม แต่ก็ยังเว้นแต่ว่าคุณกำลังสร้างรหัสห้องสมุดที่คุณไม่ทราบขนาดของสิ่งที่คุณจะถาม เพื่อย้ำมากกว่าฉันคิดว่าความแตกต่างนั้นไม่คุ้มกับการเครียด
โดยใช้ชื่อตัวแปรobjectArrayList
, java.util.ArrayList
ฉันคิดว่าเป็นตัวอย่างของ ในกรณีนั้นความแตกต่างด้านประสิทธิภาพจะไม่สามารถสังเกตเห็นได้
ในทางกลับกันหากเป็นตัวอย่างของjava.util.LinkedList
วิธีการที่สองจะช้ากว่ามากเนื่องจากเป็นการดำเนินList#get(int)
การ O (n)
ดังนั้นวิธีแรกจึงเป็นที่ต้องการเสมอยกเว้นว่าต้องการดัชนีโดยตรรกะในการวนซ้ำ
1. for(Object o: objectArrayList){
o.DoSomthing();
}
and
2. for(int i=0; i<objectArrayList.size(); i++){
objectArrayList.get(i).DoSomthing();
}
ทั้งสองทำสิ่งเดียวกัน แต่เพื่อความสะดวกและปลอดภัยในการเขียนโปรแกรมใช้สำหรับแต่ละคนมีความเป็นไปได้ที่จะเกิดข้อผิดพลาดในวิธีที่ 2
มันแปลกที่ไม่มีใครพูดถึงความชัดเจน - foreach จัดสรรหน่วยความจำ (ในรูปแบบของตัววนซ้ำ) ในขณะที่ปกติสำหรับลูปไม่ได้จัดสรรหน่วยความจำใด ๆ สำหรับเกมบน Android นี่เป็นปัญหาเพราะหมายความว่าตัวรวบรวมขยะจะทำงานเป็นระยะ ในเกมคุณไม่ต้องการให้ตัวรวบรวมขยะทำงาน ... เคย ดังนั้นอย่าใช้วิธีลูป foreach ในวิธีการวาด (หรือเรนเดอร์) ของคุณ
คำตอบที่ได้รับการยอมรับตอบคำถามนอกเหนือจากกรณีพิเศษของ ArrayList ...
เนื่องจากนักพัฒนาส่วนใหญ่พึ่งพา ArrayList (อย่างน้อยฉันก็เชื่อเช่นนั้น)
ดังนั้นฉันจำเป็นต้องเพิ่มคำตอบที่ถูกต้องที่นี่
ส่งตรงจากเอกสารของนักพัฒนาซอฟต์แวร์: -
Enhanced for loop (บางครั้งเรียกว่า "for-each" loop) สามารถใช้สำหรับคอลเลกชันที่ใช้ Iterable Interface และ Array ด้วยคอลเล็กชันตัววนซ้ำจะถูกจัดสรรเพื่อทำการเรียกใช้อินเตอร์เฟสไปยัง hasNext () และถัดไป () ด้วย ArrayList การวนซ้ำที่เขียนด้วยมือจะเร็วกว่าประมาณ 3 เท่า (มีหรือไม่มี 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 มันใช้ Enhanced สำหรับไวยากรณ์วนลูปที่แนะนำในรุ่น 1.5 ของภาษาการเขียนโปรแกรม Java
ดังนั้นคุณควรใช้ขั้นสูงสำหรับลูปเป็นค่าเริ่มต้น แต่ให้พิจารณาลูปนับที่เขียนด้วยมือสำหรับการทำซ้ำ ArrayList ที่สำคัญต่อประสิทธิภาพ
ใช่ตัวแปรจะเร็วกว่ากว่าปกติ for-each
index-based-for-loop
for-each
iterator
การใช้งานที่แตกต่างกัน ดังนั้นการfor
เคลื่อนที่เร็วกว่าลูปปกติซึ่งเป็นดัชนี
นี้เป็นเพราะiterator
จะเหมาะสำหรับ traversing เพราะมันจะชี้ไปก่อนองค์ประกอบถัดไปและหลังองค์ประกอบก่อนหน้านี้ หนึ่งในเหตุผลที่เป็นindex-based-for-loop
ไปได้ช้าคือมันต้องคำนวณและย้ายไปยังตำแหน่งองค์ประกอบในแต่ละครั้งiterator
ที่ไม่ได้อยู่กับ
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+" ");
}
}
}