ความแตกต่างระหว่างการประกาศตัวแปรก่อนหรือในวง?


312

ฉันสงสัยอยู่เสมอว่าโดยทั่วไปแล้วการประกาศตัวแปรแบบโยนทิ้งต่อหน้าวงหนึ่งซึ่งต่างจากการวนซ้ำภายในวงทำให้เกิดความแตกต่าง (ประสิทธิภาพ) ไหม? ตัวอย่าง(ค่อนข้างไม่มีจุดหมาย)ใน Java:

a)การประกาศก่อนลูป:

double intermediateResult;
for(int i=0; i < 1000; i++){
    intermediateResult = i;
    System.out.println(intermediateResult);
}

b)การประกาศ (ซ้ำ) ภายในวง:

for(int i=0; i < 1000; i++){
    double intermediateResult = i;
    System.out.println(intermediateResult);
}

เป็นที่หนึ่งที่ดีกว่าหรือ ?

ฉันสงสัยว่าการประกาศตัวแปรซ้ำ (ตัวอย่างb ) สร้างค่าใช้จ่ายมากขึ้นในทางทฤษฎีแต่คอมไพเลอร์นั้นฉลาดพอที่จะไม่สำคัญ ตัวอย่างbมีข้อได้เปรียบของการกระชับและ จำกัด ขอบเขตของตัวแปรที่จะใช้ แต่ฉันก็ยังมีแนวโน้มที่จะรหัสตามตัวอย่าง

แก้ไข:ฉันสนใจกรณี Java เป็นพิเศษ


สิ่งนี้สำคัญเมื่อเขียนโค้ด Java สำหรับแพลตฟอร์ม Android Google แนะนำว่าสำหรับรหัสที่มีความสำคัญต่อเวลาในการประกาศตัวแปรที่เพิ่มขึ้นนอกเหนือจาก for-loop ราวกับว่าภายใน for-loop มันจะประกาศอีกครั้งในแต่ละครั้งในสภาพแวดล้อมนั้น ความแตกต่างด้านประสิทธิภาพนั้นชัดเจนมากสำหรับอัลกอริทึมที่มีราคาแพง
AaronCarson

1
@AaronCarson กรุณาช่วยลิงค์ไปยังข้อเสนอแนะนี้โดย Google
Vitaly Zinchenko

คำตอบ:


256

ซึ่งจะดีกว่าหรือ ?

จากมุมมองของประสิทธิภาพคุณจะต้องวัดมัน (และในความคิดของฉันถ้าคุณสามารถวัดความแตกต่างคอมไพเลอร์ไม่ดีมาก)

จากมุมมองการบำรุงรักษาbดีกว่า ประกาศและเริ่มต้นตัวแปรในที่เดียวกันในขอบเขตที่แคบที่สุดเท่าที่จะทำได้ อย่าเว้นช่องว่างระหว่างการประกาศและการเริ่มต้นและอย่าทำให้เนมสเปซสกปรกคุณไม่จำเป็นต้องทำ


5
แทนที่จะเป็น Double ถ้าเกี่ยวข้องกับ String ก็ยังคงเป็นตัวพิมพ์ใหญ่ "b" ดีกว่า
Antoops

3
@Antoops - ใช่ b ดีกว่าด้วยเหตุผลที่ไม่มีส่วนเกี่ยวข้องกับชนิดข้อมูลของตัวแปรที่ถูกประกาศ ทำไมถึงแตกต่างกับ Strings
Daniel Earwicker

215

ฉันวิ่งตัวอย่าง A และ B ของคุณไป 20 ครั้งโดยวนลูป 100 ล้านครั้ง (JVM - 1.5.0)

A: เวลาดำเนินการเฉลี่ย: .074 วินาที

B: เวลาดำเนินการเฉลี่ย: .067 วินาที

ฉันประหลาดใจ B เร็วขึ้นเล็กน้อย เร็วเท่าคอมพิวเตอร์ตอนนี้มันยากที่จะพูดว่าถ้าคุณสามารถวัดสิ่งนี้ได้อย่างแม่นยำ ฉันจะเขียนโค้ดด้วยวิธี A แต่ฉันจะบอกว่ามันไม่สำคัญ


12
คุณเอาชนะฉันฉันเพิ่งจะโพสต์ผลลัพธ์ของฉันสำหรับการทำโปรไฟล์ฉันได้มากหรือน้อยเหมือนกันและใช่อย่างน่าประหลาดใจ B เร็วขึ้นจริงๆจะมีความคิดว่าถ้าฉันต้องเดิมพัน
Mark Davidson

14
ไม่แปลกใจมาก - เมื่อตัวแปรเป็นโลคัลกับลูปมันไม่จำเป็นต้องเก็บรักษาไว้หลังจากการวนซ้ำแต่ละครั้งเพื่อให้สามารถอยู่ในรีจิสเตอร์ได้

142
+1 สำหรับการทดสอบจริง ๆ แล้วไม่ใช่แค่ความเห็น / ทฤษฎีที่ OP สามารถสร้างขึ้นเองได้
MGOwen

3
@GoodPerson ซื่อสัตย์ฉันต้องการที่จะทำ ฉันรันการทดสอบนี้ประมาณ 10 ครั้งบนเครื่องของฉันสำหรับการทำซ้ำ 50,000,000-100,000,000 โดยมีรหัสที่เหมือนกันเกือบทุกชิ้น (ซึ่งฉันอยากแบ่งปันกับทุกคนที่ต้องการเรียกใช้สถิติ) คำตอบนั้นถูกแบ่งออกเป็นสองส่วนเท่า ๆ กันโดยปกติแล้วจะมีระยะขอบ 900 มิลลิวินาที (ซ้ำมากกว่า 50 ล้านครั้ง) ซึ่งไม่มากนัก แม้ว่าความคิดแรกของฉันคือมันจะเป็น "เสียงรบกวน" แต่ก็อาจเอนเอียงไปสักหน่อย ความพยายามนี้ดูเหมือนจะเป็นวิชาการสำหรับฉันอย่างแท้จริง (สำหรับการใช้งานในชีวิตจริงส่วนใหญ่) .. ฉันชอบที่จะเห็นผลอยู่ดี;) ใครเห็นด้วย?
javatarz

3
การแสดงผลการทดสอบโดยไม่บันทึกการตั้งค่านั้นไม่มีค่า โดยเฉพาะอย่างยิ่งในกรณีนี้ที่ส่วนของโค้ดทั้งคู่สร้างโค้ดไบต์เหมือนกันดังนั้นความแตกต่างใด ๆ ที่วัดได้เป็นเพียงสัญญาณของเงื่อนไขการทดสอบที่ไม่เพียงพอ
Holger

66

ขึ้นอยู่กับภาษาและการใช้งานที่แน่นอน ตัวอย่างเช่นใน C # 1 มันไม่ได้สร้างความแตกต่าง ใน C # 2 ถ้าตัวแปรโลคอลถูกจับโดยวิธีนิรนาม (หรือการแสดงออกแลมบ์ดาใน C # 3) มันสามารถสร้างความแตกต่างที่สำคัญมาก

ตัวอย่าง:

using System;
using System.Collections.Generic;

class Test
{
    static void Main()
    {
        List<Action> actions = new List<Action>();

        int outer;
        for (int i=0; i < 10; i++)
        {
            outer = i;
            int inner = i;
            actions.Add(() => Console.WriteLine("Inner={0}, Outer={1}", inner, outer));
        }

        foreach (Action action in actions)
        {
            action();
        }
    }
}

เอาท์พุท:

Inner=0, Outer=9
Inner=1, Outer=9
Inner=2, Outer=9
Inner=3, Outer=9
Inner=4, Outer=9
Inner=5, Outer=9
Inner=6, Outer=9
Inner=7, Outer=9
Inner=8, Outer=9
Inner=9, Outer=9

ความแตกต่างคือการกระทำทั้งหมดจับouterตัวแปรเดียวกันแต่แต่ละตัวมีinnerตัวแปรแยกต่างหาก


3
ในตัวอย่าง B (คำถามเดิม) จริง ๆ แล้วมันสร้างตัวแปรใหม่ทุกครั้งหรือไม่ เกิดอะไรขึ้นในสายตาของกองซ้อน?
Royi Namir

@ จอนมันเป็นบั๊กใน C # 1.0 หรือไม่ ไม่ควรนึกถึงOuter9
nawfal

@nawfal: ฉันไม่รู้ว่าคุณหมายถึงอะไร การแสดงออกของแลมบ์ดาไม่ได้อยู่ใน 1.0 ... และด้านนอกคือ 9. ข้อผิดพลาดใดที่คุณหมายถึง
Jon Skeet

@nawfal: ประเด็นของฉันคือไม่มีคุณสมบัติภาษาใด ๆ ใน C # 1.0 ที่คุณสามารถบอกความแตกต่างระหว่างการประกาศตัวแปรภายในลูปและประกาศข้างนอก (สมมติว่าคอมไพล์ทั้งคู่) ที่เปลี่ยนแปลงใน C # 2.0 ไม่มีข้อผิดพลาด
Jon Skeet

@ JonSkeet โอ้ใช่ฉันให้คุณแล้วตอนนี้ฉันมองข้ามความจริงที่ว่าคุณไม่สามารถปิดตัวแปรเช่นนั้นใน 1.0 นั่นคือสิ่งที่ไม่ดีของฉัน! :)
nawfal

35

ต่อไปนี้เป็นสิ่งที่ฉันเขียนและเรียบเรียงใน. NET

double r0;
for (int i = 0; i < 1000; i++) {
    r0 = i*i;
    Console.WriteLine(r0);
}

for (int j = 0; j < 1000; j++) {
    double r1 = j*j;
    Console.WriteLine(r1);
}

นี่คือสิ่งที่ฉันได้รับจาก. NET Reflectorเมื่อCILแสดงผลกลับเป็นรหัส

for (int i = 0; i < 0x3e8; i++)
{
    double r0 = i * i;
    Console.WriteLine(r0);
}
for (int j = 0; j < 0x3e8; j++)
{
    double r1 = j * j;
    Console.WriteLine(r1);
}

ดังนั้นทั้งคู่จึงดูเหมือนกันหลังจากรวบรวม ในรหัสภาษาที่มีการจัดการจะถูกแปลงเป็นรหัส CL / ไบต์และเมื่อมีการดำเนินการมันจะถูกแปลงเป็นภาษาเครื่อง ดังนั้นในภาษาเครื่องอาจไม่สามารถสร้าง double บน stack ได้ มันอาจเป็นเพียงการลงทะเบียนเป็นรหัสสะท้อนให้เห็นว่ามันเป็นตัวแปรชั่วคราวสำหรับWriteLineฟังก์ชั่น มีกฎการปรับให้เหมาะสมทั้งชุดสำหรับลูป ดังนั้นคนทั่วไปไม่ควรกังวลเกี่ยวกับเรื่องนี้โดยเฉพาะในภาษาที่มีการจัดการ มีหลายกรณีที่คุณสามารถปรับการจัดการรหัสให้เหมาะสมตัวอย่างเช่นถ้าคุณต้องเชื่อมสตริงจำนวนมากโดยใช้เพียงแค่string a; a+=anotherstring[i]vsStringBuilder. มีความแตกต่างอย่างมากในประสิทธิภาพการทำงานระหว่างทั้งสอง มีหลายกรณีที่คอมไพเลอร์ไม่สามารถปรับโค้ดของคุณให้เหมาะสมเพราะมันไม่สามารถเข้าใจได้ว่าอะไรคือจุดมุ่งหมายในขอบเขตที่กว้างขึ้น แต่มันสามารถเพิ่มประสิทธิภาพสิ่งพื้นฐานสำหรับคุณได้มากทีเดียว


int j = 0 สำหรับ (; j <0x3e8; j ++) ด้วยวิธีนี้ประกาศครั้งเดียวทั้งสองตัวแปรและไม่ได้สำหรับแต่ละรอบ 2) การมอบหมายมันเป็นตัวเลือกอื่น ๆ ทั้งหมด 3) ดังนั้นกฎการปฏิบัติที่ดีที่สุดคือการประกาศใด ๆ นอกการทำซ้ำสำหรับ
luka

24

นี่คือ gotcha ใน VB.NET ผลลัพธ์ของ Visual Basic จะไม่กำหนดค่าเริ่มต้นให้กับตัวแปรในตัวอย่างนี้:

For i as Integer = 1 to 100
    Dim j as Integer
    Console.WriteLine(j)
    j = i
Next

' Output: 0 1 2 3 4...

นี่จะพิมพ์ 0 ครั้งแรก (ตัวแปร Visual Basic มีค่าเริ่มต้นเมื่อประกาศ!) แต่iทุกครั้งหลังจากนั้น

หากคุณเพิ่ม a = 0คุณจะได้สิ่งที่คาดหวัง:

For i as Integer = 1 to 100
    Dim j as Integer = 0
    Console.WriteLine(j)
    j = i
Next

'Output: 0 0 0 0 0...

1
ฉันใช้ VB.NET มาหลายปีแล้วและไม่เคยเจอสิ่งนี้ !!
ChrisA

12
ใช่มันไม่เป็นที่พอใจที่จะเข้าใจสิ่งนี้ในทางปฏิบัติ
Michael Haren

นี่คือการอ้างอิงเกี่ยวกับเรื่องนี้จาก Paul Vick: panopticoncentral.net/archive/2006/03/28/11552.aspx
ferventcoder

1
@eschneider @ferventcoder โชคไม่ดีที่ @PaulV ได้ตัดสินใจที่จะวางโพสต์บล็อกเก่าของเขาดังนั้นตอนนี้มันจึงเป็นลิงค์ที่ตายแล้ว
Mark Hurd

ใช่เพิ่งวิ่งข้ามนี้ กำลังมองหาบางเอกสารอย่างเป็นทางการเกี่ยวกับเรื่องนี้ ...
เอริคชไนเดอ

15

ฉันทำแบบทดสอบง่ายๆ

int b;
for (int i = 0; i < 10; i++) {
    b = i;
}

VS

for (int i = 0; i < 10; i++) {
    int b = i;
}

ฉันรวบรวมรหัสเหล่านี้ด้วย gcc - 5.2.0 จากนั้นฉันก็แยกส่วน main () ของสองรหัสออกและนั่นคือผลลัพธ์:

1º:

   0x00000000004004b6 <+0>:     push   rbp
   0x00000000004004b7 <+1>:     mov    rbp,rsp
   0x00000000004004ba <+4>:     mov    DWORD PTR [rbp-0x4],0x0
   0x00000000004004c1 <+11>:    jmp    0x4004cd <main+23>
   0x00000000004004c3 <+13>:    mov    eax,DWORD PTR [rbp-0x4]
   0x00000000004004c6 <+16>:    mov    DWORD PTR [rbp-0x8],eax
   0x00000000004004c9 <+19>:    add    DWORD PTR [rbp-0x4],0x1
   0x00000000004004cd <+23>:    cmp    DWORD PTR [rbp-0x4],0x9
   0x00000000004004d1 <+27>:    jle    0x4004c3 <main+13>
   0x00000000004004d3 <+29>:    mov    eax,0x0
   0x00000000004004d8 <+34>:    pop    rbp
   0x00000000004004d9 <+35>:    ret

VS

   0x00000000004004b6 <+0>: push   rbp
   0x00000000004004b7 <+1>: mov    rbp,rsp
   0x00000000004004ba <+4>: mov    DWORD PTR [rbp-0x4],0x0
   0x00000000004004c1 <+11>:    jmp    0x4004cd <main+23>
   0x00000000004004c3 <+13>:    mov    eax,DWORD PTR [rbp-0x4]
   0x00000000004004c6 <+16>:    mov    DWORD PTR [rbp-0x8],eax
   0x00000000004004c9 <+19>:    add    DWORD PTR [rbp-0x4],0x1
   0x00000000004004cd <+23>:    cmp    DWORD PTR [rbp-0x4],0x9
   0x00000000004004d1 <+27>:    jle    0x4004c3 <main+13>
   0x00000000004004d3 <+29>:    mov    eax,0x0
   0x00000000004004d8 <+34>:    pop    rbp
   0x00000000004004d9 <+35>:    ret 

ซึ่ง exaclty ผล asm เดียวกัน ไม่ได้เป็นการพิสูจน์ว่าทั้งสองรหัสผลิตสิ่งเดียวกันหรือไม่


3
ใช่แล้วมันยอดเยี่ยมที่คุณทำสิ่งนี้ แต่สิ่งนี้กลับมาเป็นสิ่งที่ผู้คนพูดถึงการพึ่งพาภาษา / คอมไพเลอร์ ฉันสงสัยว่า JIT หรือการตีความภาษาจะได้รับผลกระทบอย่างไร
user137717

12

มันขึ้นอยู่กับภาษา - IIRC C # ปรับสิ่งนี้ให้เหมาะสมดังนั้นจึงไม่มีความแตกต่าง แต่ JavaScript (ตัวอย่าง) จะทำการจัดสรรหน่วยความจำทั้งหมด shebang ในแต่ละครั้ง


ใช่ แต่นั่นไม่ได้มีจำนวนมาก ฉันทำการทดสอบอย่างง่ายโดยใช้ลูปสำหรับดำเนินการ 100 ล้านครั้งและฉันพบว่าความแตกต่างที่ยิ่งใหญ่ที่สุดในการประกาศนอกลูปคือ 8 ms โดยปกติแล้วมันจะมากกว่า 3-4 และบางครั้งการประกาศนอกวงวนนั้นทำได้ WORSE (มากถึง 4 มิลลิวินาที) แต่นั่นไม่ใช่เรื่องปกติ
user137717

11

ฉันมักจะใช้ A (แทนที่จะพึ่งพาคอมไพเลอร์) และอาจเขียนใหม่เพื่อ:

for(int i=0, double intermediateResult=0; i<1000; i++){
    intermediateResult = i;
    System.out.println(intermediateResult);
}

นี่ยังคง จำกัดintermediateResultขอบเขตของลูป แต่จะไม่ประกาศซ้ำในระหว่างการวนซ้ำแต่ละครั้ง


12
คุณต้องการให้ตัวแปรมีชีวิตอยู่ตลอดช่วงเวลาของการวนรอบแทนที่จะแยกกันต่อการวนซ้ำหรือไม่? ฉันไม่ค่อยทำ เขียนโค้ดที่แสดงความตั้งใจของคุณอย่างชัดเจนที่สุดเว้นแต่คุณจะมีเหตุผลที่ดีมาก ๆ
Jon Skeet

4
อาประนีประนอมดีฉันไม่เคยคิดเรื่องนี้! IMO รหัสไม่เป็นบิตน้อยสายตา 'ชัดเจน' แม้ว่า)
Rabarberski

2
@ จอน - ฉันไม่รู้ว่า OP กำลังทำอะไรกับค่ากลาง แค่คิดว่ามันเป็นตัวเลือกที่คุ้มค่าในการพิจารณา
Triptych

6

ในความคิดของฉัน b เป็นโครงสร้างที่ดีกว่า ใน a ค่าสุดท้ายของสื่อกลางผลลัพธ์จะยึดตามหลังจากวนรอบของคุณเสร็จสิ้น

แก้ไข: สิ่งนี้ไม่ได้สร้างความแตกต่างอย่างมากกับประเภทค่า แต่ประเภทการอ้างอิงอาจมีน้ำหนักค่อนข้าง โดยส่วนตัวแล้วฉันชอบตัวแปรที่จะถูกยกเลิกการลงทะเบียนโดยเร็วที่สุดสำหรับการล้างข้อมูลและ b ก็ทำเพื่อคุณ


sticks around after your loop is finished- แม้ว่าสิ่งนี้จะไม่สำคัญในภาษาเช่น Python ที่ชื่อที่ถูกผูกไว้จะวนไปมาจนกว่าฟังก์ชันจะจบ
new123456

@ new123456: OP ขอข้อมูลเฉพาะของ Java แม้ว่าคำถามจะถูกถามโดยทั่วไป ภาษาที่ได้จาก C หลายภาษามีการกำหนดขอบเขตระดับบล็อก: C, C ++, Perl (พร้อมด้วยmyคำหลัก), C # และ Java ในชื่อ 5 ที่ฉันเคยใช้
Powerlord

ฉันรู้ - มันเป็นการสังเกตไม่ใช่การวิจารณ์
new123456

5

ฉันสงสัยว่าคอมไพเลอร์บางตัวสามารถปรับทั้งสองอย่างให้เป็นโค้ดเดียวกันได้ แต่ก็ไม่ทั้งหมด ดังนั้นฉันจะบอกว่าคุณดีกว่ากับอดีต เหตุผลเดียวสำหรับหลังคือถ้าคุณต้องการให้แน่ใจว่าตัวแปรที่ประกาศจะใช้เฉพาะภายในวงของคุณ


5

ตามกฎทั่วไปฉันประกาศตัวแปรของฉันในขอบเขตที่เป็นไปได้มากที่สุด ดังนั้นหากคุณไม่ได้ใช้สื่อกลางผลการค้นหานอกเหนือจากลูปฉันจะไปกับ B


5

เพื่อนร่วมงานต้องการแบบฟอร์มแรกโดยบอกว่าเป็นการเพิ่มประสิทธิภาพโดยเลือกที่จะใช้การประกาศอีกครั้ง

ฉันชอบคนที่สอง (และพยายามเกลี้ยกล่อมเพื่อนร่วมงานของฉัน! ;-)) โดยอ่านว่า:

  • มันลดขอบเขตของตัวแปรไปยังตำแหน่งที่ต้องการซึ่งเป็นสิ่งที่ดี
  • Java ปรับให้เหมาะสมเพียงพอที่จะไม่ทำให้เกิดความแตกต่างในประสิทธิภาพ IIRC บางทีรูปแบบที่สองนั้นเร็วกว่า

อย่างไรก็ตามมันอยู่ในหมวดหมู่ของการเพิ่มประสิทธิภาพก่อนวัยอันควรซึ่งต้องพึ่งพาคุณภาพของคอมไพเลอร์และ / หรือ JVM


5

มีความแตกต่างใน C # หากคุณใช้ตัวแปรในแลมบ์ดาเป็นต้น แต่โดยทั่วไปคอมไพเลอร์จะทำสิ่งเดียวกันโดยทั่วไปสมมติว่าตัวแปรนั้นใช้ภายในลูปเท่านั้น

ระบุว่าพวกมันเหมือนกัน: โปรดทราบว่าเวอร์ชัน b ทำให้ผู้อ่านเห็นได้ชัดเจนว่าตัวแปรไม่ได้และไม่สามารถใช้หลังจากวนรอบได้ นอกจากนี้เวอร์ชัน b ยังได้รับการปรับโครงสร้างได้ง่ายขึ้น เป็นการยากที่จะแยกส่วนลูปออกเป็นเมธอดของตัวเองในเวอร์ชัน a ยิ่งกว่านั้นเวอร์ชัน b ยังช่วยให้คุณมั่นใจได้ว่าไม่มีผลข้างเคียงจากการปรับโครงสร้างดังกล่าว

ดังนั้นรุ่นทำให้ฉันรำคาญไม่จบเพราะไม่มีประโยชน์กับมันและมันทำให้ยากมากที่จะให้เหตุผลเกี่ยวกับรหัส ...


5

คุณสามารถสร้างขอบเขตสำหรับสิ่งนั้นได้เสมอ:

{ //Or if(true) if the language doesn't support making scopes like this
    double intermediateResult;
    for (int i=0; i<1000; i++) {
        intermediateResult = i;
        System.out.println(intermediateResult);
    }
}

วิธีนี้คุณประกาศตัวแปรเพียงครั้งเดียวและมันจะตายเมื่อคุณออกจากลูป


4

ฉันคิดเสมอว่าถ้าคุณประกาศตัวแปรของคุณในวงของคุณแล้วคุณจะสูญเสียความทรงจำ หากคุณมีสิ่งนี้:

for(;;) {
  Object o = new Object();
}

จากนั้นวัตถุไม่เพียง แต่จะต้องถูกสร้างขึ้นสำหรับการวนซ้ำแต่ละครั้งเท่านั้น แต่จะต้องมีการอ้างอิงใหม่ที่จัดสรรสำหรับแต่ละวัตถุ ดูเหมือนว่าหากตัวรวบรวมขยะช้าคุณจะต้องมีการอ้างอิงที่ห้อยอยู่จำนวนมากซึ่งจำเป็นต้องทำความสะอาด

อย่างไรก็ตามหากคุณมีสิ่งนี้:

Object o;
for(;;) {
  o = new Object();
}

จากนั้นคุณจะสร้างการอ้างอิงเดียวและกำหนดวัตถุใหม่ให้กับมันทุกครั้ง แน่นอนว่าอาจใช้เวลานานกว่าเล็กน้อยกว่าที่จะออกนอกขอบเขต แต่จากนั้นมีการอ้างอิงทางเดียวที่น่าสนใจเพื่อจัดการกับ


3
การอ้างอิงใหม่ไม่ได้ถูกจัดสรรสำหรับแต่ละวัตถุแม้ว่าการอ้างอิงจะถูกประกาศภายใน 'for'-loop ในทั้งสองกรณี: 1) 'o' เป็นตัวแปรโลคัลและพื้นที่สแต็กถูกจัดสรรเพียงครั้งเดียวเมื่อเริ่มต้นฟังก์ชัน 2) มีวัตถุใหม่ที่สร้างขึ้นในการทำซ้ำแต่ละครั้ง ดังนั้นจึงไม่มีความแตกต่างในด้านประสิทธิภาพ สำหรับการจัดระเบียบโค้ดการอ่านและการบำรุงรักษาการประกาศการอ้างอิงภายในลูปจะดีกว่า
Ajoy Bhatia

1
ในขณะที่ฉันไม่สามารถพูดกับ Java ได้ใน. NET การอ้างอิงไม่ได้ถูก 'จัดสรร' สำหรับแต่ละวัตถุในตัวอย่างแรก มีรายการเดียวบนสแต็กสำหรับตัวแปรโลคัล (ไปยังเมธอด) นั้น สำหรับตัวอย่างของคุณ IL ที่สร้างขึ้นนั้นเหมือนกัน
Jesse C. Slicer

3

ฉันคิดว่ามันขึ้นอยู่กับคอมไพเลอร์และยากที่จะให้คำตอบทั่วไป


3

การปฏิบัติของฉันมีดังต่อไปนี้:

  • ถ้าประเภทของตัวแปรนั้นง่าย(int, double, ... )ฉันชอบตัวแปรb (ภายใน)
    เหตุผล:การลดขอบเขตของตัวแปร

  • ถ้าประเภทของตัวแปรนั้นไม่ง่าย(บางชนิดclassหรือstruct)ฉันชอบตัวแปรa (นอก)
    สาเหตุ:ลดจำนวนการโทร ctor-dtor


1

จากมุมมองประสิทธิภาพภายนอกดีกว่ามาก

public static void outside() {
    double intermediateResult;
    for(int i=0; i < Integer.MAX_VALUE; i++){
        intermediateResult = i;
    }
}

public static void inside() {
    for(int i=0; i < Integer.MAX_VALUE; i++){
        double intermediateResult = i;
    }
}

ฉันดำเนินการทั้งสองฟังก์ชั่น 1,000,000 ครั้งต่อครั้ง ข้างนอก () ใช้เวลา 65 มิลลิวินาที ข้างใน () ใช้เวลา 1.5 วินาที


2
ต้องเป็น Debug การรวบรวมที่ไม่มีการเพิ่มประสิทธิภาพแล้วใช่ไหม
Tomasz Przychodzki

int j = 0 สำหรับ (; j <0x3e8; j ++) ด้วยวิธีนี้ประกาศครั้งเดียวทั้งสองตัวแปรและไม่ได้สำหรับแต่ละรอบ 2) การมอบหมายมันเป็นตัวเลือกอื่น ๆ ทั้งหมด 3) ดังนั้นกฎการปฏิบัติที่ดีที่สุดคือการประกาศใด ๆ นอกการทำซ้ำสำหรับ
luka

1

ฉันทดสอบ JS ด้วย Node 4.0.0 ถ้าใครสนใจ การประกาศนอกลูปทำให้การปรับปรุงประสิทธิภาพของ ~ .5 ms โดยเฉลี่ยมากกว่า 1,000 ครั้งพร้อมการวนซ้ำ 100 ล้านครั้งต่อการทดลอง ดังนั้นฉันจะบอกว่าไปข้างหน้าและเขียนในวิธีที่อ่านได้ / บำรุงรักษามากที่สุดซึ่งเป็น B, IMO ฉันจะใส่รหัสของฉันในซอ แต่ฉันใช้โมดูประสิทธิภาพตอนนี้โหนด นี่คือรหัส:

var now = require("../node_modules/performance-now")

// declare vars inside loop
function varInside(){
    for(var i = 0; i < 100000000; i++){
        var temp = i;
        var temp2 = i + 1;
        var temp3 = i + 2;
    }
}

// declare vars outside loop
function varOutside(){
    var temp;
    var temp2;
    var temp3;
    for(var i = 0; i < 100000000; i++){
        temp = i
        temp2 = i + 1
        temp3 = i + 2
    }
}

// for computing average execution times
var insideAvg = 0;
var outsideAvg = 0;

// run varInside a million times and average execution times
for(var i = 0; i < 1000; i++){
    var start = now()
    varInside()
    var end = now()
    insideAvg = (insideAvg + (end-start)) / 2
}

// run varOutside a million times and average execution times
for(var i = 0; i < 1000; i++){
    var start = now()
    varOutside()
    var end = now()
    outsideAvg = (outsideAvg + (end-start)) / 2
}

console.log('declared inside loop', insideAvg)
console.log('declared outside loop', outsideAvg)

0

A) เป็นการเดิมพันที่ปลอดภัยกว่า B) ......... ลองนึกภาพถ้าคุณกำลังเริ่มโครงสร้างในลูปแทนที่จะเป็น 'int' หรือ 'โฟลต' แล้วอะไรล่ะ?

ชอบ

typedef struct loop_example{

JXTZ hi; // where JXTZ could be another type...say closed source lib 
         // you include in Makefile

}loop_example_struct;

//then....

int j = 0; // declare here or face c99 error if in loop - depends on compiler setting

for ( ;j++; )
{
   loop_example loop_object; // guess the result in memory heap?
}

คุณต้องเผชิญกับปัญหาการรั่วไหลของหน่วยความจำอย่างแน่นอน! ดังนั้นฉันเชื่อว่า 'A' เป็นเดิมพันที่ปลอดภัยกว่าในขณะที่ 'B' มีความเสี่ยงต่อการสะสมหน่วยความจำโดยใช้ไลบรารีแหล่งใกล้คุณสามารถตรวจสอบการใช้ 'Valgrind' เครื่องมือบน Linux เครื่องมือย่อยเฉพาะ 'Helgrind'


0

มันเป็นคำถามที่น่าสนใจ จากประสบการณ์ของฉันมีคำถามที่ดีที่สุดที่ควรพิจารณาเมื่อคุณอภิปรายเรื่องนี้สำหรับรหัส:

มีเหตุผลใดที่ตัวแปรจะต้องเป็นระดับโลก?

มันเหมาะสมที่จะประกาศตัวแปรเพียงครั้งเดียวทั่วโลกซึ่งตรงข้ามกับหลาย ๆ ครั้งในพื้นที่เพราะจะเป็นการดีกว่าสำหรับการจัดระเบียบรหัสและต้องการบรรทัดของรหัสที่น้อยลง อย่างไรก็ตามหากจำเป็นต้องประกาศภายในเครื่องเพียงวิธีเดียวฉันจะเริ่มต้นด้วยวิธีดังกล่าวเพื่อให้ชัดเจนว่าตัวแปรนั้นเกี่ยวข้องเฉพาะกับวิธีนั้น ระวังอย่าเรียกตัวแปรนี้นอกเหนือจากวิธีที่มันถูกเริ่มต้นถ้าคุณเลือกตัวเลือกหลัง - รหัสของคุณจะไม่รู้ว่าคุณกำลังพูดถึงอะไรและจะรายงานข้อผิดพลาด

นอกจากนี้ในฐานะที่เป็นบันทึกด้านข้างอย่าทำซ้ำชื่อตัวแปรท้องถิ่นระหว่างวิธีที่ต่างกันแม้ว่าจุดประสงค์ของชื่อนั้นจะเหมือนกัน มันแค่สับสน


1
ฮ่า ๆ ๆ ฉันไม่เห็นด้วยด้วยเหตุผลมากมาย ... อย่างไรก็ตามไม่ลงคะแนน ... ฉันเคารพสิทธิ์ของคุณที่จะเลือก
Grantly

0

นี่คือรูปแบบที่ดีกว่า

double intermediateResult;
int i = byte.MinValue;

for(; i < 1000; i++)
{
intermediateResult = i;
System.out.println(intermediateResult);
}

1) ด้วยวิธีนี้ประกาศครั้งเดียวทั้งสองตัวแปรและไม่ใช่สำหรับวัฏจักร 2) การมอบหมายมันเป็นตัวเลือกอื่น ๆ ทั้งหมด 3) ดังนั้นกฎการปฏิบัติที่ดีที่สุดคือการประกาศใด ๆ นอกการทำซ้ำสำหรับ


0

พยายามทำสิ่งเดียวกันใน Go และเปรียบเทียบคอมไพเลอร์เอาต์พุตโดยใช้go tool compile -Sกับ go 1.9.4

ความแตกต่างเป็นศูนย์ตามเอาต์พุตของแอสเซมเบลอร์


0

ฉันมีคำถามเดียวกันนี้มานานแล้ว ดังนั้นฉันจึงทดสอบโค้ดที่เรียบง่ายกว่าเดิม

สรุป:สำหรับกรณีดังกล่าวมีไม่แตกต่างประสิทธิภาพ

กรณีนอกวง

int intermediateResult;
for(int i=0; i < 1000; i++){
    intermediateResult = i+2;
    System.out.println(intermediateResult);
}

เคสด้านใน

for(int i=0; i < 1000; i++){
    int intermediateResult = i+2;
    System.out.println(intermediateResult);
}

ฉันตรวจสอบไฟล์ที่คอมไพล์บนเครื่อง Decompiler ของ IntelliJ และสำหรับทั้งสองกรณีฉันได้เหมือนกัน Test.class

for(int i = 0; i < 1000; ++i) {
    int intermediateResult = i + 2;
    System.out.println(intermediateResult);
}

ฉันยังถอดชิ้นส่วนรหัสสำหรับทั้งสองกรณีโดยใช้วิธีที่กำหนดไว้ในคำตอบนี้ ฉันจะแสดงเฉพาะส่วนที่เกี่ยวข้องกับคำตอบเท่านั้น

กรณีนอกวง

Code:
  stack=2, locals=3, args_size=1
     0: iconst_0
     1: istore_2
     2: iload_2
     3: sipush        1000
     6: if_icmpge     26
     9: iload_2
    10: iconst_2
    11: iadd
    12: istore_1
    13: getstatic     #2                  // Field java/lang/System.out:Ljava/io/PrintStream;
    16: iload_1
    17: invokevirtual #3                  // Method java/io/PrintStream.println:(I)V
    20: iinc          2, 1
    23: goto          2
    26: return
LocalVariableTable:
        Start  Length  Slot  Name   Signature
           13      13     1 intermediateResult   I
            2      24     2     i   I
            0      27     0  args   [Ljava/lang/String;

เคสด้านใน

Code:
      stack=2, locals=3, args_size=1
         0: iconst_0
         1: istore_1
         2: iload_1
         3: sipush        1000
         6: if_icmpge     26
         9: iload_1
        10: iconst_2
        11: iadd
        12: istore_2
        13: getstatic     #2                  // Field java/lang/System.out:Ljava/io/PrintStream;
        16: iload_2
        17: invokevirtual #3                  // Method java/io/PrintStream.println:(I)V
        20: iinc          1, 1
        23: goto          2
        26: return
      LocalVariableTable:
        Start  Length  Slot  Name   Signature
           13       7     2 intermediateResult   I
            2      24     1     i   I
            0      27     0  args   [Ljava/lang/String;

หากคุณใส่ใจอย่างใกล้ชิดSlotจะมีการสลับเฉพาะสินค้าที่ได้รับมอบหมายiและintermediateResultเข้ามาLocalVariableTableเป็นผลิตภัณฑ์ตามลำดับที่ปรากฏ ความแตกต่างในช่องเดียวกันนั้นสะท้อนให้เห็นในรหัสบรรทัดอื่น ๆ

  • ไม่มีการดำเนินการพิเศษใด ๆ
  • intermediateResult ยังคงเป็นตัวแปรโลคัลในทั้งสองกรณีดังนั้นจึงไม่มีเวลาเข้าถึงที่ต่างกัน

โบนัส

คอมไพเลอร์ทำการปรับแต่งมากมายดูที่สิ่งที่เกิดขึ้นในกรณีนี้

กรณีการทำงานเป็นศูนย์

for(int i=0; i < 1000; i++){
    int intermediateResult = i;
    System.out.println(intermediateResult);
}

ศูนย์งาน decompiled

for(int i = 0; i < 1000; ++i) {
    System.out.println(i);
}

-1

แม้ว่าฉันจะรู้ว่าคอมไพเลอร์ของฉันฉลาดพอฉันก็ไม่ชอบที่จะพึ่งพามันและจะใช้ตัวแปร)

ตัวแปร b) เหมาะสมกับฉันเฉพาะเมื่อคุณจำเป็นต้องทำให้สื่อกลางไม่สามารถใช้งานได้หลังจากร่างกายลูป แต่ฉันไม่สามารถจินตนาการถึงสถานการณ์ที่สิ้นหวังได้ ...

แก้ไข: Jon Skeetสร้างจุดดีมากโดยแสดงให้เห็นว่าการประกาศตัวแปรภายในลูปสามารถสร้างความแตกต่างทางความหมายที่แท้จริงได้

โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.