ประกาศตัวแปรภายในหรือภายนอกของลูป


236

ทำไมงานต่อไปนี้ถึงทำงานได้ดี?

String str;
while (condition) {
    str = calculateStr();
    .....
}

แต่สิ่งนี้ถูกกล่าวว่าเป็นอันตราย / ไม่ถูกต้อง:

while (condition) {
    String str = calculateStr();
    .....
}

จำเป็นต้องประกาศตัวแปรนอกลูปหรือไม่?

คำตอบ:


289

ขอบเขตของตัวแปรโลคัลควรน้อยที่สุดเท่าที่จะเป็นไปได้

ในตัวอย่างของฉันเข้าใจstrจะไม่ใช้นอกของwhileวงมิฉะนั้นคุณจะไม่ได้รับการถามคำถามเพราะประกาศว่าภายในwhileห่วงจะไม่เป็นตัวเลือกเพราะมันจะไม่รวบรวม

ดังนั้นตั้งแต่strจะไม่ใช้นอกวงขอบเขตที่เป็นไปได้น้อยที่สุดstrคือภายในในขณะที่ห่วง

ดังนั้นคำตอบคืออย่างเด่นชัดที่strควรจะประกาศอย่างแน่นอนในขณะที่วง ไม่ว่าจะเป็นไม่ใช่ไม่มีและไม่เป็นไปไม่ได้

กรณีเดียวที่กฎนี้อาจถูกละเมิดคือด้วยเหตุผลบางอย่างที่มีความสำคัญอย่างยิ่งที่ทุกรอบสัญญาณนาฬิกาจะต้องถูกบีบออกจากรหัสซึ่งในกรณีนี้คุณอาจต้องการพิจารณาการสร้างอินสแตนซ์ในขอบเขตด้านนอกและนำมันกลับมาใช้แทน re-instantiating มันในทุกรอบซ้ำของขอบเขตภายใน อย่างไรก็ตามสิ่งนี้ใช้ไม่ได้กับตัวอย่างของคุณเนื่องจากสตริงไม่เปลี่ยนแปลงใน java: อินสแตนซ์ใหม่ของ str จะถูกสร้างขึ้นในตอนต้นของการวนซ้ำของคุณและมันจะต้องถูกโยนทิ้งในตอนท้ายของมันดังนั้นจึงมี ไม่มีความเป็นไปได้ที่จะปรับให้เหมาะสม

แก้ไข: (ฉีดความคิดเห็นของฉันด้านล่างในคำตอบ)

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


2
Query ในย่อหน้าสุดท้าย: ถ้ามันเป็นอีกอัน String ซึ่งไม่เปลี่ยนรูปแล้วจะมีผลหรือไม่
Harry Joy

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

7
@HarryJoy วิธีที่ถูกต้องในการทำสิ่งต่าง ๆ คือการเขียนโค้ดทั้งหมดของคุณอย่างถูกต้องสร้างข้อกำหนดด้านประสิทธิภาพสำหรับผลิตภัณฑ์ของคุณวัดผลิตภัณฑ์ขั้นสุดท้ายของคุณตามข้อกำหนดนี้และหากไม่เป็นไปตามที่กำหนดไว้ และคุณรู้อะไรไหม โดยทั่วไปคุณจะสามารถให้อัลกอริธึมที่ดีและเป็นทางการได้ในสถานที่เพียงไม่กี่แห่งที่จะทำเคล็ดลับแทนที่จะต้องไปทั่วฐานรหัสทั้งหมดของคุณและปรับแต่งและแฮ็กสิ่งต่าง ๆ เพื่อบีบวงจรนาฬิกาที่นี่และที่นั่น
Mike Nakis

2
@ MikeNakis สิ่งที่คุณคิดในขอบเขตที่แคบมาก
Siten

5
คุณจะเห็นซีพียู multi-gigahertz ที่ทันสมัยมัลติคอร์ pipelined และหน่วยความจำแคชหลายระดับทำให้เราสามารถมุ่งเน้นไปที่การปฏิบัติที่ดีที่สุดโดยไม่ต้องกังวลกับวงจรนาฬิกา นอกจากนี้การปรับให้เหมาะสมจะแนะนำก็ต่อเมื่อมันได้รับการพิจารณาแล้วว่ามีความจำเป็นและเมื่อจำเป็นก็จำเป็นต้องมีการปรับแต่งสองภาษาที่มีการแปลเป็นภาษาท้องถิ่นอย่างมากซึ่งจะทำให้ได้ประสิทธิภาพตามที่ต้องการดังนั้นจึงไม่จำเป็นต้องทิ้งรหัสทั้งหมดของเรา มีแฮ็กเล็กน้อยในชื่อของประสิทธิภาพ
Mike Nakis

293

ฉันเปรียบเทียบโค้ดไบต์ของทั้งสองตัวอย่าง (คล้ายกัน):

ลองดูตัวอย่าง 1. :

package inside;

public class Test {
    public static void main(String[] args) {
        while(true){
            String str = String.valueOf(System.currentTimeMillis());
            System.out.println(str);
        }
    }
}

หลังจากjavac Test.javaนั้นjavap -c Testคุณจะได้รับ:

public class inside.Test extends java.lang.Object{
public inside.Test();
  Code:
   0:   aload_0
   1:   invokespecial   #1; //Method java/lang/Object."<init>":()V
   4:   return

public static void main(java.lang.String[]);
  Code:
   0:   invokestatic    #2; //Method java/lang/System.currentTimeMillis:()J
   3:   invokestatic    #3; //Method java/lang/String.valueOf:(J)Ljava/lang/String;
   6:   astore_1
   7:   getstatic       #4; //Field java/lang/System.out:Ljava/io/PrintStream;
   10:  aload_1
   11:  invokevirtual   #5; //Method java/io/PrintStream.println:(Ljava/lang/String;)V
   14:  goto    0

}

ลองดูตัวอย่างที่2 :

package outside;

public class Test {
    public static void main(String[] args) {
        String str;
        while(true){
            str =  String.valueOf(System.currentTimeMillis());
            System.out.println(str);
        }
    }
}

หลังจากjavac Test.javaนั้นjavap -c Testคุณจะได้รับ:

public class outside.Test extends java.lang.Object{
public outside.Test();
  Code:
   0:   aload_0
   1:   invokespecial   #1; //Method java/lang/Object."<init>":()V
   4:   return

public static void main(java.lang.String[]);
  Code:
   0:   invokestatic    #2; //Method java/lang/System.currentTimeMillis:()J
   3:   invokestatic    #3; //Method java/lang/String.valueOf:(J)Ljava/lang/String;
   6:   astore_1
   7:   getstatic       #4; //Field java/lang/System.out:Ljava/io/PrintStream;
   10:  aload_1
   11:  invokevirtual   #5; //Method java/io/PrintStream.println:(Ljava/lang/String;)V
   14:  goto    0

}

การสำรวจแสดงให้เห็นว่าไม่มีความแตกต่างระหว่างสองตัวอย่าง มันเป็นผลมาจากข้อกำหนด JVM ...

แต่ในชื่อของวิธีปฏิบัติที่ดีที่สุดในการเขียนโปรแกรมขอแนะนำให้ประกาศตัวแปรในขอบเขตที่เล็กที่สุดเท่าที่จะเป็นไปได้ (ในตัวอย่างนี้มันอยู่ในลูปเนื่องจากเป็นที่เดียวที่มีการใช้ตัวแปร)


3
มันเป็นผลลัพธ์ของ JVM Soecification ไม่ใช่ 'การปรับให้เหมาะสมของคอมไพเลอร์' สล็อตสแต็กที่ต้องการโดยวิธีการทั้งหมดได้รับการจัดสรรเมื่อเข้าสู่วิธีการ นั่นคือวิธีการระบุ bytecode
มาร์ควิสแห่ง Lorne

2
@Arhimed มีอีกเหตุผลหนึ่งที่ทำให้มันอยู่ในลูป (หรือเพียงแค่ '{}' บล็อก): คอมไพเลอร์จะนำหน่วยความจำที่จัดสรรในเฟรมสแต็กมาใช้ใหม่สำหรับตัวแปรในขอบเขตอื่นหากคุณประกาศในขอบเขตอื่น ๆ .
Serge

1
หากการวนซ้ำผ่านรายการของวัตถุข้อมูลมันจะสร้างความแตกต่างสำหรับข้อมูลจำนวนมากหรือไม่ น่าจะเป็น 40,000
Mithun Khatri

7
สำหรับfinalคนรักของคุณ: การประกาศstrเช่นเดียวกับfinalในinsideกรณีบรรจุภัณฑ์ก็ไม่ได้ทำให้แตกต่างกัน =)
skia.heliou

27

ประกาศวัตถุในขอบเขตที่เล็กที่สุดในการปรับปรุงการอ่าน

ประสิทธิภาพไม่สำคัญสำหรับคอมไพเลอร์ในปัจจุบัน (ในสถานการณ์นี้)
จากมุมมองการบำรุงรักษาตัวเลือกที่2จะดีกว่า
ประกาศและเริ่มต้นตัวแปรในที่เดียวกันในขอบเขตที่แคบที่สุดเท่าที่จะทำได้

ดังที่Donald Ervin Knuthบอกว่า:

"เราควรลืมเกี่ยวกับประสิทธิภาพเล็กน้อยพูดถึง 97% ของเวลา: การปรับให้เหมาะสมก่อนกำหนดเป็นรากของความชั่วร้ายทั้งหมด"

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


1
"ตัวเลือกที่ 2 มีประสิทธิภาพเร็วกว่าเล็กน้อย" => คุณวัดได้หรือไม่ ตามคำตอบข้อใดข้อหนึ่ง bytecode นั้นเหมือนกันดังนั้นฉันไม่เห็นว่าประสิทธิภาพจะแตกต่างกันอย่างไร
assylias

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

ฉันเห็นด้วยกับประเด็นอื่น ๆ ของคุณ - เป็นเพียงแค่ฉันเชื่อว่าไม่มีความแตกต่างด้านประสิทธิภาพ
assylias

11

หากคุณต้องการใช้strนอก looop ด้วย; ประกาศข้างนอก มิฉะนั้นรุ่นที่ 2 ก็ใช้ได้


11

โปรดข้ามไปที่คำตอบที่อัพเดต ...

สำหรับผู้ที่สนใจเกี่ยวกับประสิทธิภาพการทำงานจะออก System.out และ จำกัด การวนรอบเป็น 1 ไบต์ การใช้ double (test 1/2) และใช้ String (3/4) เวลาที่ผ่านไปเป็นมิลลิวินาทีจะได้รับด้านล่างด้วย Windows 7 Professional 64 บิตและ JDK-1.7.0_21 Bytecodes (ที่ให้ไว้ด้านล่างสำหรับ test1 และ test2) นั้นไม่เหมือนกัน ฉันขี้เกียจเกินไปที่จะทดสอบกับวัตถุที่ไม่แน่นอนและค่อนข้างซับซ้อน

สอง

ทดสอบ 1: 2710 msecs

ทดสอบ 2: 2790 มิลลิวินาที

String (เพียงแทนที่ double ด้วย string ในการทดสอบ)

ทดสอบ 3: 1200 msecs

ใช้เวลาทดสอบ 4: 3000 มิลลิวินาที

รวบรวมและรับ bytecode

javac.exe LocalTest1.java

javap.exe -c LocalTest1 > LocalTest1.bc


public class LocalTest1 {

    public static void main(String[] args) throws Exception {
        long start = System.currentTimeMillis();
        double test;
        for (double i = 0; i < 1000000000; i++) {
            test = i;
        }
        long finish = System.currentTimeMillis();
        System.out.println("Test1 Took: " + (finish - start) + " msecs");
    }

}

public class LocalTest2 {

    public static void main(String[] args) throws Exception {
        long start = System.currentTimeMillis();
        for (double i = 0; i < 1000000000; i++) {
            double test = i;
        }
        long finish = System.currentTimeMillis();
        System.out.println("Test1 Took: " + (finish - start) + " msecs");
    }
}


Compiled from "LocalTest1.java"
public class LocalTest1 {
  public LocalTest1();
    Code:
       0: aload_0
       1: invokespecial #1                  // Method java/lang/Object."<init>":()V
       4: return

  public static void main(java.lang.String[]) throws java.lang.Exception;
    Code:
       0: invokestatic  #2                  // Method java/lang/System.currentTimeMillis:()J
       3: lstore_1
       4: dconst_0
       5: dstore        5
       7: dload         5
       9: ldc2_w        #3                  // double 1.0E9d
      12: dcmpg
      13: ifge          28
      16: dload         5
      18: dstore_3
      19: dload         5
      21: dconst_1
      22: dadd
      23: dstore        5
      25: goto          7
      28: invokestatic  #2                  // Method java/lang/System.currentTimeMillis:()J
      31: lstore        5
      33: getstatic     #5                  // Field java/lang/System.out:Ljava/io/PrintStream;
      36: new           #6                  // class java/lang/StringBuilder
      39: dup
      40: invokespecial #7                  // Method java/lang/StringBuilder."<init>":()V
      43: ldc           #8                  // String Test1 Took:
      45: invokevirtual #9                  // Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;
      48: lload         5
      50: lload_1
      51: lsub
      52: invokevirtual #10                 // Method java/lang/StringBuilder.append:(J)Ljava/lang/StringBuilder;
      55: ldc           #11                 // String  msecs
      57: invokevirtual #9                  // Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;
      60: invokevirtual #12                 // Method java/lang/StringBuilder.toString:()Ljava/lang/String;
      63: invokevirtual #13                 // Method java/io/PrintStream.println:(Ljava/lang/String;)V
      66: return
}


Compiled from "LocalTest2.java"
public class LocalTest2 {
  public LocalTest2();
    Code:
       0: aload_0
       1: invokespecial #1                  // Method java/lang/Object."<init>":()V
       4: return

  public static void main(java.lang.String[]) throws java.lang.Exception;
    Code:
       0: invokestatic  #2                  // Method java/lang/System.currentTimeMillis:()J
       3: lstore_1
       4: dconst_0
       5: dstore_3
       6: dload_3
       7: ldc2_w        #3                  // double 1.0E9d
      10: dcmpg
      11: ifge          24
      14: dload_3
      15: dstore        5
      17: dload_3
      18: dconst_1
      19: dadd
      20: dstore_3
      21: goto          6
      24: invokestatic  #2                  // Method java/lang/System.currentTimeMillis:()J
      27: lstore_3
      28: getstatic     #5                  // Field java/lang/System.out:Ljava/io/PrintStream;
      31: new           #6                  // class java/lang/StringBuilder
      34: dup
      35: invokespecial #7                  // Method java/lang/StringBuilder."<init>":()V
      38: ldc           #8                  // String Test1 Took:
      40: invokevirtual #9                  // Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;
      43: lload_3
      44: lload_1
      45: lsub
      46: invokevirtual #10                 // Method java/lang/StringBuilder.append:(J)Ljava/lang/StringBuilder;
      49: ldc           #11                 // String  msecs
      51: invokevirtual #9                  // Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;
      54: invokevirtual #12                 // Method java/lang/StringBuilder.toString:()Ljava/lang/String;
      57: invokevirtual #13                 // Method java/io/PrintStream.println:(Ljava/lang/String;)V
      60: return
}

ปรับปรุงคำตอบ

มันไม่ง่ายเลยที่จะเปรียบเทียบประสิทธิภาพกับการเพิ่มประสิทธิภาพ JVM ทั้งหมด อย่างไรก็ตามมันเป็นไปได้ค่อนข้าง การทดสอบที่ดีขึ้นและผลลัพธ์โดยละเอียดใน Google Caliper

  1. รายละเอียดบางอย่างในบล็อก:คุณควรประกาศตัวแปรภายในลูปหรือก่อนลูปหรือไม่?
  2. พื้นที่เก็บข้อมูล GitHub: https://github.com/gunduru/jvdt
  3. ผลการทดสอบสำหรับกรณีที่สองและ 100M วน (และใช่รายละเอียด JVM ทั้งหมด): https://microbenchmarks.appspot.com/runs/b1cef8d1-0e2c-4120-be61-a99faff625b4

ประกาศก่อนหน้า 1,759.209 ประกาศภายใน 2,242.308

  • ประกาศก่อนหน้านี้ 1,759.209 ns
  • ประกาศภายใน 2,242.308 ns

รหัสการทดสอบบางส่วนสำหรับการประกาศสองครั้ง

สิ่งนี้ไม่เหมือนกับรหัสด้านบน หากคุณเพิ่งเขียนรหัสหุ่นจำลอง JVM ข้ามไปดังนั้นอย่างน้อยคุณต้องกำหนดและส่งคืนบางสิ่งบางอย่าง ขอแนะนำในเอกสารประกอบ Caliper

@Param int size; // Set automatically by framework, provided in the Main
/**
* Variable is declared inside the loop.
*
* @param reps
* @return
*/
public double timeDeclaredInside(int reps) {
    /* Dummy variable needed to workaround smart JVM */
    double dummy = 0;

    /* Test loop */
    for (double i = 0; i <= size; i++) {

        /* Declaration and assignment */
        double test = i;

        /* Dummy assignment to fake JVM */
        if(i == size) {
            dummy = test;
        }
    }
    return dummy;
}

/**
* Variable is declared before the loop.
*
* @param reps
* @return
*/
public double timeDeclaredBefore(int reps) {

    /* Dummy variable needed to workaround smart JVM */
    double dummy = 0;

    /* Actual test variable */
    double test = 0;

    /* Test loop */
    for (double i = 0; i <= size; i++) {

        /* Assignment */
        test = i;

        /* Not actually needed here, but we need consistent performance results */
        if(i == size) {
            dummy = test;
        }
    }
    return dummy;
}

สรุป: ประกาศก่อนหน้านี้จะระบุประสิทธิภาพที่ดีขึ้น - เล็ก ๆ น้อย ๆ - และมันขัดกับหลักการขอบเขตที่เล็กที่สุด JVM ควรทำสิ่งนี้เพื่อคุณ


วิธีการทดสอบไม่ถูกต้องและคุณไม่ได้ให้คำอธิบายใด ๆ เกี่ยวกับผลลัพธ์ของคุณ
มาร์ควิสแห่ง Lorne

1
@EJP นี่ควรจะค่อนข้างชัดเจนสำหรับผู้ที่มีความสนใจในเรื่อง ระเบียบวิธีนั้นมาจากคำตอบของ PrimosK เพื่อให้ข้อมูลที่เป็นประโยชน์มากขึ้น พูดตามตรงฉันไม่มีความคิดในการปรับปรุงคำตอบนี้บางทีคุณสามารถคลิกแก้ไขและแสดงวิธีทำอย่างถูกต้องได้ไหม
Onur Günduru

2
1) Java Bytecode ได้รับการปรับให้เหมาะสม (จัดลำดับใหม่ยุบ ฯลฯ ) ในรันไทม์ดังนั้นอย่าสนใจสิ่งที่เขียนในไฟล์. class มากเกินไป 2) มี 1.000.000.000 การวิ่งเพื่อให้ได้รับประสิทธิภาพการทำงานที่ 2.8s ดังนั้นนั่นประมาณ 2.8ns ต่อการรันกับรูปแบบการเขียนโปรแกรมที่ปลอดภัยและเหมาะสม ผู้ชนะที่ชัดเจนสำหรับฉัน 3) เนื่องจากคุณไม่ได้ให้ข้อมูลเกี่ยวกับการวอร์มอัพการกำหนดเวลาของคุณค่อนข้างไร้ประโยชน์
Hardcoded

@ การทดสอบที่ดีกว่าการขัดจังหวะ / การเปรียบเทียบไมโครด้วยคาลิปเปอร์สำหรับลูปคู่และ 100M เท่านั้น ผลลัพธ์ออนไลน์หากคุณต้องการกรณีอื่น ๆ ให้แก้ไขฟรี
Onur Günduru

ขอขอบคุณกำจัดจุดที่ 1) และ 3) แต่ถึงแม้ว่าเวลาจะเพิ่มขึ้นถึง ~ 5ns ต่อรอบ แต่ก็ยังคงเป็นเวลาที่จะถูกละเว้น ในทางทฤษฎีมีศักยภาพการปรับให้เหมาะสมเพียงเล็กน้อยในความเป็นจริงสิ่งที่คุณทำต่อวงจรมักมีราคาแพงกว่ามาก ดังนั้นอาจเป็นไปได้ที่จะมีค่าสูงสุดไม่กี่วินาทีในเวลาไม่กี่นาทีหรือเป็นชั่วโมง มีตัวเลือกอื่นที่มีศักยภาพสูงกว่า (เช่น Fork / Join, ขนานสตรีม) ที่ฉันจะตรวจสอบก่อนที่จะใช้เวลากับการเพิ่มประสิทธิภาพระดับต่ำแบบนี้
Hardcoded

7

วิธีหนึ่งในการแก้ไขปัญหานี้คือการจัดเตรียมขอบเขตตัวแปรที่ห่อหุ้มห่วงลูปในขณะที่:

{
  // all tmp loop variables here ....
  // ....
  String str;
  while(condition){
      str = calculateStr();
      .....
  }
}

พวกเขาจะถูกยกเลิกการอ้างอิงโดยอัตโนมัติเมื่อขอบเขตด้านนอกสิ้นสุดลง


6

ภายในขอบเขตที่น้อยกว่าที่ตัวแปรมองเห็นได้นั้นดีกว่า


5

หากคุณไม่จำเป็นต้องใช้ในstrขณะที่หลังจากห่วง (ขอบเขตที่เกี่ยวข้อง) แล้วเงื่อนไขที่สองคือ

  while(condition){
        String str = calculateStr();
        .....
    }

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


2
โปรดทราบว่าแม้ในตัวแปรแรกจะไม่มีการสร้างวัตถุใด ๆ หากเงื่อนไขเป็นเท็จ
Philipp Wendler

@ Phillip: ใช่แล้วคุณพูดถูก ความผิดฉันเอง. ฉันคิดเหมือนตอนนี้คุณคิดอะไรอยู่
Cratylus

1
"การนิยามวัตถุบนสแต็ก" เป็นคำที่ค่อนข้างแปลกในโลก Java นอกจากนี้การจัดสรรตัวแปรบนสแต็กมักเป็น noop ตอนรันไทม์ดังนั้นทำไมต้องกังวล? การกำหนดขอบเขตเพื่อช่วยโปรแกรมเมอร์เป็นปัญหาจริง
Philipp Wendler

3

ฉันคิดว่าแหล่งข้อมูลที่ดีที่สุดในการตอบคำถามของคุณคือโพสต์ต่อไปนี้:

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

ตามความเข้าใจของฉันสิ่งนี้จะขึ้นอยู่กับภาษา IIRC Java ทำสิ่งนี้ให้เหมาะสมที่สุดดังนั้นจึงไม่มีความแตกต่าง แต่ JavaScript (ตัวอย่าง) จะทำการจัดสรรหน่วยความจำทั้งหมดในแต่ละครั้งในลูปใน Java โดยเฉพาะอย่างยิ่งฉันคิดว่าตัวที่สองจะทำงานได้เร็วขึ้นเมื่อทำการทำโปรไฟล์


3

ดังที่หลายคนชี้

String str;
while(condition){
    str = calculateStr();
    .....
}

คือไม่ดีกว่านี้:

while(condition){
    String str = calculateStr();
    .....
}

ดังนั้นอย่าประกาศตัวแปรนอกขอบเขตหากคุณไม่ได้ใช้ซ้ำ ...


1
ยกเว้นอาจเป็นในลักษณะนี้: ลิงก์
Dainius Kreivys

2

การประกาศสตริง str นอกของ wile loop อนุญาตให้อ้างอิงใน & นอก while loop การประกาศสตริง str ภายในของ while loop อนุญาตให้อ้างอิงได้เฉพาะใน while loop


1

ควรประกาศตัวแปรที่ใกล้เคียงกับที่ใช้มากที่สุด

มันทำให้ RAII (การได้รับทรัพยากรเป็นการเริ่มต้น)ได้ง่ายขึ้น

มันทำให้ขอบเขตของตัวแปรแน่น สิ่งนี้ช่วยให้เครื่องมือเพิ่มประสิทธิภาพทำงานได้ดีขึ้น



1

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

 String str;
    while(condition){
        str = calculateStr();
        .....
    }

strตัวแปรจะไม่สามารถใช้ได้และหน่วยความจำจะถูกปล่อยออกมาซึ่งได้รับการจัดสรรสำหรับstrตัวแปรด้านล่างรหัส

while(condition){
    String str = calculateStr();
    .....
}

ถ้าเราติดตามอันที่สองแน่นอนว่าสิ่งนี้จะลดหน่วยความจำระบบของเราและเพิ่มประสิทธิภาพ


0

การประกาศภายในลูปจะ จำกัด ขอบเขตของตัวแปรที่เกี่ยวข้อง ทุกอย่างขึ้นอยู่กับความต้องการของโครงการในขอบเขตของตัวแปร


0

แท้จริงคำถามที่กล่าวข้างต้นเป็นปัญหาการเขียนโปรแกรม คุณต้องการโปรแกรมโค้ดของคุณอย่างไร? คุณต้องการเข้าถึง 'STR' ที่ไหน ไม่มีการใช้ประกาศตัวแปรซึ่งใช้ภายในเป็นตัวแปรทั่วโลก พื้นฐานของการเขียนโปรแกรมฉันเชื่อ


-1

ตัวอย่างสองตัวอย่างนี้ส่งผลในสิ่งเดียวกัน อย่างไรก็ตามครั้งแรกให้คุณใช้strตัวแปรนอกห่วงขณะที่; ที่สองไม่ได้


-1

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

public class Test
{
    private final static int STUFF_SIZE = 512;
    private final static long LOOP = 10000000l;

    private static class Foo
    {
        private long[] bigStuff = new long[STUFF_SIZE];

        public Foo(long value)
        {
            setValue(value);
        }

        public void setValue(long value)
        {
            // Putting value in a random place.
            bigStuff[(int) (value % STUFF_SIZE)] = value;
        }

        public long getValue()
        {
            // Retrieving whatever value.
            return bigStuff[STUFF_SIZE / 2];
        }
    }

    public static long test1()
    {
        long total = 0;

        for (long i = 0; i < LOOP; i++)
        {
            Foo foo = new Foo(i);
            total += foo.getValue();
        }

        return total;
    }

    public static long test2()
    {
        long total = 0;

        Foo foo = new Foo(0);
        for (long i = 0; i < LOOP; i++)
        {
            foo.setValue(i);
            total += foo.getValue();
        }

        return total;
    }

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

        start = System.currentTimeMillis();
        test1();
        System.out.println(System.currentTimeMillis() - start);

        start = System.currentTimeMillis();
        test2();
        System.out.println(System.currentTimeMillis() - start);
    }
}

สรุป: ขึ้นอยู่กับขนาดของตัวแปรท้องถิ่นความแตกต่างอาจมีขนาดใหญ่แม้ว่าจะมีตัวแปรที่ไม่ใหญ่มากก็ตาม

แค่พูดอย่างนั้นบางครั้งข้างนอกหรือข้างในก็ไม่สำคัญ


1
แน่นอนว่าวินาทีนั้นเร็วกว่า แต่คุณกำลังทำสิ่งต่าง ๆ : test1 กำลังสร้าง Foo-Objects ที่มีอาร์เรย์ขนาดใหญ่จำนวนมาก test2 ไม่ใช่ test2 กำลังนำวัตถุ Foo เดียวกันซ้ำแล้วซ้ำอีกซึ่งอาจเป็นอันตรายในสภาพแวดล้อมแบบมัลติเธรด
Hardcoded

เป็นอันตรายในสภาพแวดล้อมแบบมัลติเธรด ??? กรุณาอธิบายว่าทำไม เรากำลังพูดถึงตัวแปรท้องถิ่น มันถูกสร้างขึ้นที่การโทรแต่ละวิธี
rt15

หากคุณส่ง Foo-Object ไปยังการดำเนินการที่กำลังประมวลผลข้อมูลแบบอะซิงโครนัสการดำเนินการอาจยังคงทำงานบนอินสแตนซ์ Foo ในขณะที่คุณกำลังเปลี่ยนข้อมูลในนั้น ไม่ต้องมีหลายเธรดเพื่อให้ได้ผลข้างเคียง การใช้อินสแตนซ์ซ้ำนั้นค่อนข้างอันตรายเมื่อคุณไม่รู้ว่าใครกำลังใช้อินสแตนซ์อยู่
Hardcoded

Ps: เมธอด setValue ของคุณควรเป็นbigStuff[(int) (value % STUFF_SIZE)] = value;(ลองใช้ค่า 2147483649L)
ฮาร์ดโค้ด

พูดเกี่ยวกับผลข้างเคียง: คุณเปรียบเทียบผลลัพธ์ของวิธีการของคุณหรือไม่
Hardcoded

-1

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


-2

คุณมีความเสี่ยงNullPointerExceptionหากcalculateStr()วิธีการของคุณคืนเป็นโมฆะและลองโทรหาวิธีการที่ str

มากกว่าปกติหลีกเลี่ยงตัวแปรด้วยnullค่า มันแข็งแกร่งสำหรับแอตทริบิวต์ class โดยวิธีการ


2
นี่ไม่ใช่วิธีที่เกี่ยวข้องกับคำถาม ความน่าจะเป็นของ NullPointerException (การเรียกใช้ฟังก์ชันในอนาคต) จะไม่ขึ้นอยู่กับการประกาศตัวแปร
Desert Ice

1
ฉันไม่คิดอย่างนั้นเพราะคำถามคือ "วิธีที่ดีที่สุดในการทำคืออะไร" IMHO ฉันต้องการรหัสที่ปลอดภัยยิ่งขึ้น
Rémi Doolaeghe

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