Sự khác nhau giữa khai báo biến trước hoặc trong vòng lặp?


312

Tôi đã luôn tự hỏi rằng, nói chung, việc khai báo một biến ném trước một vòng lặp, trái ngược với lặp đi lặp lại bên trong vòng lặp, có làm cho bất kỳ (hiệu suất) khác biệt? Một ví dụ (khá vô nghĩa) trong Java:

a) khai báo trước vòng lặp:

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

b) khai báo (lặp đi lặp lại) bên trong vòng lặp:

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

Cái nào tốt hơn, a hay b ?

Tôi nghi ngờ rằng việc khai báo biến lặp lại (ví dụ b ) tạo ra nhiều chi phí hơn trong lý thuyết , nhưng trình biên dịch đó đủ thông minh để nó không thành vấn đề. Ví dụ b có ưu điểm là nhỏ gọn hơn và giới hạn phạm vi của biến đến nơi nó được sử dụng. Tuy nhiên, tôi có xu hướng mã theo ví dụ a .

Chỉnh sửa: Tôi đặc biệt quan tâm đến trường hợp Java.


Điều này quan trọng khi viết mã Java cho nền tảng Android. Google đề nghị rằng đối với mã thời gian quan trọng để khai báo các biến tăng dần bên ngoài vòng lặp for, như thể bên trong vòng lặp for, nó sẽ khai báo lại mỗi lần trong môi trường đó. Sự khác biệt hiệu suất là rất đáng chú ý đối với các thuật toán đắt tiền.
AaronCarson

1
@AaronCarson bạn có thể vui lòng cung cấp liên kết đến đề xuất này của Google
Vitaly Zinchenko

Câu trả lời:


256

Cái nào tốt hơn, a hoặc b ?

Từ góc độ hiệu suất, bạn phải đo nó. (Và theo tôi, nếu bạn có thể đo lường sự khác biệt, trình biên dịch không tốt lắm).

Từ góc độ bảo trì, b là tốt hơn. Khai báo và khởi tạo các biến ở cùng một vị trí, trong phạm vi hẹp nhất có thể. Đừng để lỗ hổng giữa khai báo và khởi tạo và không gây ô nhiễm không gian tên mà bạn không cần.


5
Thay vì Double, nếu nó liên quan đến String, vẫn còn trường hợp "b" tốt hơn?
Antoops

3
@Antoops - có, b tốt hơn vì những lý do không liên quan đến kiểu dữ liệu của biến được khai báo. Tại sao nó lại khác với String?
Daniel Earwicker

215

Chà, tôi đã chạy các ví dụ A và B của bạn 20 lần mỗi lần, lặp 100 triệu lần. (JVM - 1.5.0)

A: thời gian thực hiện trung bình: .074 giây

B: thời gian thực hiện trung bình: .067 giây

Tôi ngạc nhiên là B nhanh hơn một chút. Nhanh như máy tính bây giờ thật khó để nói nếu bạn có thể đo chính xác điều này. Tôi cũng sẽ viết mã theo cách A nhưng tôi sẽ nói nó không thực sự quan trọng.


12
Bạn đánh bại tôi Tôi vừa mới đăng kết quả của mình để lập hồ sơ, tôi ít nhiều giống nhau và thật ngạc nhiên là B nhanh hơn thực sự sẽ nghĩ A nếu tôi cần đặt cược vào nó.
Mark Davidson

14
Không có nhiều bất ngờ - khi biến là cục bộ của vòng lặp, nó không cần được bảo toàn sau mỗi lần lặp, vì vậy nó có thể ở trong một thanh ghi.

142
+1 để thực sự thử nghiệm nó , không chỉ là một ý kiến ​​/ lý thuyết mà OP có thể tự tạo ra.
MGOwen

3
@ GoodPerson thành thật mà nói, tôi muốn điều đó được thực hiện. Tôi đã chạy thử nghiệm này khoảng 10 lần trên máy của mình với số lần lặp 50.000.000 - 100.000.000 với gần như một đoạn mã giống hệt nhau (mà tôi rất muốn chia sẻ với bất kỳ ai muốn chạy số liệu thống kê). Các câu trả lời được phân chia gần như bằng nhau theo cách thông thường với biên độ 900ms (hơn 50 triệu lần lặp) không thực sự nhiều. Mặc dù suy nghĩ đầu tiên của tôi là nó sẽ là "tiếng ồn", nhưng nó có thể nghiêng một chút. Tuy nhiên, nỗ lực này có vẻ hoàn toàn mang tính học thuật đối với tôi (đối với hầu hết các ứng dụng thực tế) .. Tôi rất muốn thấy kết quả nào;) Có ai đồng ý không?
javatarz

3
Hiển thị kết quả kiểm tra mà không ghi lại các thiết lập, là vô giá trị. Điều đó đặc biệt đúng trong trường hợp này, trong đó cả hai đoạn mã đều tạo ra mã byte giống hệt nhau, do đó, bất kỳ sự khác biệt nào được đo chỉ là dấu hiệu của điều kiện kiểm tra không đủ.
Holger

66

Nó phụ thuộc vào ngôn ngữ và cách sử dụng chính xác. Ví dụ, trong C # 1, nó không có sự khác biệt. Trong C # 2, nếu biến cục bộ bị bắt bởi một phương thức ẩn danh (hoặc biểu thức lambda trong C # 3), nó có thể tạo ra sự khác biệt rất lớn.

Thí dụ:

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();
        }
    }
}

Đầu ra:

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

Sự khác biệt là tất cả các hành động nắm bắt cùng một outerbiến, nhưng mỗi hành động có một biến riêng inner.


3
trong ví dụ B (câu hỏi ban đầu), nó có thực sự tạo ra một biến mới mỗi lần không? Điều gì xảy ra trong mắt của ngăn xếp?
Royi Namir

@Jon, đó có phải là một lỗi trong C # 1.0 không? Không nên lý tưởng Outerlà 9?
nawfal

@nawfal: Tôi không hiểu ý bạn. Biểu thức Lambda không có trong 1.0 ... và Outer 9. Bạn có ý gì?
Jon Skeet

@nawfal: Quan điểm của tôi là không có bất kỳ tính năng ngôn ngữ nào trong C # 1.0 nơi bạn có thể cho biết sự khác biệt giữa việc khai báo một biến trong một vòng lặp và khai báo bên ngoài (giả sử rằng cả hai được biên dịch). Điều đó đã thay đổi trong C # 2.0. Không có lỗi.
Jon Skeet

@JonSkeet Ồ vâng, tôi hiểu bạn ngay bây giờ, tôi hoàn toàn bỏ qua thực tế là bạn không thể đóng các biến như thế trong 1.0, thật tệ! :)
nawfal

35

Sau đây là những gì tôi đã viết và biên dịch trong .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);
}

Đây là những gì tôi nhận được từ .NET Reflector khi CIL được kết xuất lại thành mã.

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);
}

Vì vậy, cả hai trông giống hệt nhau sau khi biên dịch. Trong các ngôn ngữ được quản lý, mã được chuyển đổi thành mã CL / byte và tại thời điểm thực hiện, nó được chuyển đổi thành ngôn ngữ máy. Vì vậy, trong ngôn ngữ máy, một đôi thậm chí có thể không được tạo trên ngăn xếp. Nó có thể chỉ là một thanh ghi vì mã phản ánh rằng nó là một biến tạm thời cho WriteLinehàm. Có một bộ quy tắc tối ưu hóa toàn bộ chỉ dành cho các vòng lặp. Vì vậy, những người bình thường không nên lo lắng về điều đó, đặc biệt là trong các ngôn ngữ được quản lý. Có những trường hợp khi bạn có thể tối ưu hóa quản lý mã, ví dụ, nếu bạn phải nối một số lượng lớn các chuỗi chỉ bằng cách sử dụngstring a; a+=anotherstring[i] so với sử dụngStringBuilder. Có sự khác biệt rất lớn về hiệu suất giữa cả hai. Có rất nhiều trường hợp như vậy mà trình biên dịch không thể tối ưu hóa mã của bạn, bởi vì nó không thể tìm ra mục đích trong phạm vi lớn hơn. Nhưng nó có thể tối ưu hóa khá nhiều thứ cơ bản cho bạn.


int j = 0 for (; j <0x3e8; j ++) theo cách này được khai báo một lần cả hai biến và không phải mỗi chu kỳ. 2) nhiệm vụ đó là chất béo hơn tất cả các tùy chọn khác. 3) Vì vậy, quy tắc bestpractice là bất kỳ khai báo nào ngoài vòng lặp cho.
luka

24

Đây là một gotcha trong VB.NET. Kết quả Visual Basic sẽ không khởi tạo lại biến trong ví dụ này:

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

' Output: 0 1 2 3 4...

Điều này sẽ in 0 lần đầu tiên (các biến Visual Basic có giá trị mặc định khi được khai báo!) Nhưng imỗi lần sau đó.

= 0Tuy nhiên, nếu bạn thêm một , bạn sẽ có được những gì bạn có thể mong đợi:

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
Tôi đã sử dụng VB.NET trong nhiều năm và đã không gặp phải điều này !!
ChrisA

12
Vâng, thật khó chịu khi tìm hiểu điều này trong thực tế.
Michael Haren

Đây là một tài liệu tham khảo về điều này từ Paul Vick: panopticoncentral.net/archive/2006/03/11/11552.aspx
ferventcoder

1
@eschneider @ferventcoder Thật không may @PaulV đã quyết định bỏ các bài đăng trên blog cũ của mình , vì vậy đây là một liên kết chết.
Đánh dấu nhanh

vâng, vừa mới chạy qua đây; đang tìm kiếm một số tài liệu chính thức về điều này ...
Eric Schneider

15

Tôi đã làm một bài kiểm tra đơn giản:

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

đấu với

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

Tôi đã biên dịch các mã này với gcc - 5.2.0. Và sau đó tôi đã phân tách chính () của hai mã này và đó là kết quả:

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

đấu với

   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 

Đó là exaclty cùng kết quả asm. không phải là một bằng chứng cho thấy hai mã sản xuất cùng một thứ?


3
vâng, và thật tuyệt khi bạn đã làm điều này, nhưng điều này trở lại với những gì mọi người đang nói về sự phụ thuộc ngôn ngữ / trình biên dịch. Tôi tự hỏi làm thế nào JIT hoặc giải thích hiệu suất ngôn ngữ sẽ bị ảnh hưởng.
user137717

12

Nó phụ thuộc vào ngôn ngữ - IIRC C # tối ưu hóa điều này, do đó không có sự khác biệt nào, nhưng JavaScript (ví dụ) sẽ thực hiện toàn bộ phân bổ bộ nhớ shebang mỗi lần.


Phải, nhưng số tiền đó không nhiều. Tôi đã thực hiện một thử nghiệm đơn giản với một vòng lặp for thực hiện 100 triệu lần và tôi thấy rằng sự khác biệt lớn nhất trong việc tuyên bố bên ngoài vòng lặp là 8 ms. Nó thường giống như 3-4 và đôi khi khai báo bên ngoài vòng lặp thực hiện WORSE (tối đa 4 ms), nhưng đó không phải là điển hình.
user137717

11

Tôi sẽ luôn sử dụng A (thay vì dựa vào trình biên dịch) và cũng có thể viết lại thành:

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

Điều này vẫn giới hạn intermediateResultphạm vi của vòng lặp, nhưng không được phân phối lại trong mỗi lần lặp.


12
Bạn có khái niệm muốn biến này tồn tại trong suốt thời gian của vòng lặp thay vì riêng biệt cho mỗi lần lặp không? Tôi hiếm khi làm. Viết mã cho thấy ý định của bạn rõ ràng nhất có thể, trừ khi bạn có một lý do rất, rất tốt để làm khác.
Jon Skeet

4
Ah, thỏa hiệp tốt đẹp, tôi không bao giờ nghĩ về điều này! IMO, mã mặc dù trở nên ít rõ ràng hơn 'rõ ràng')
Rabarberski

2
@Jon - Tôi không biết OP thực sự đang làm gì với giá trị trung gian. Chỉ cần nghĩ rằng đó là một lựa chọn đáng để xem xét.
Triptych

6

Theo tôi, b là cấu trúc tốt hơn. Trong một, giá trị cuối cùng của trung gianResult sẽ xuất hiện sau khi vòng lặp của bạn kết thúc.

Chỉnh sửa: Điều này không tạo ra nhiều sự khác biệt với các loại giá trị, nhưng các loại tham chiếu có thể hơi nặng nề. Cá nhân, tôi muốn các biến được hủy đăng ký càng sớm càng tốt để dọn dẹp và b làm điều đó cho bạn,


sticks around after your loop is finished- mặc dù điều này không quan trọng trong một ngôn ngữ như Python, nơi các tên bị ràng buộc bám quanh cho đến khi chức năng kết thúc.
new123456

@ new123456: OP đã yêu cầu các chi tiết cụ thể về Java, ngay cả khi câu hỏi được hỏi một cách khái quát. Nhiều ngôn ngữ có nguồn gốc C có phạm vi cấp khối: C, C ++, Perl (với mytừ khóa), C # và Java để đặt tên 5 tôi đã sử dụng.
Powerlord

Tôi biết - đó là một quan sát, không phải là một lời chỉ trích.
new123456

5

Tôi nghi ngờ một vài trình biên dịch có thể tối ưu hóa cả hai cùng một mã, nhưng chắc chắn không phải tất cả. Vì vậy, tôi muốn nói rằng bạn tốt hơn với trước đây. Lý do duy nhất cho cái sau là nếu bạn muốn đảm bảo rằng biến khai báo chỉ được sử dụng trong vòng lặp của bạn.


5

Theo nguyên tắc chung, tôi khai báo các biến của mình trong phạm vi bên trong nhất có thể. Vì vậy, nếu bạn không sử dụng trung gianResult bên ngoài vòng lặp, thì tôi sẽ đi với B.


5

Một đồng nghiệp thích mẫu đầu tiên, nói rằng đó là một tối ưu hóa, thích sử dụng lại một khai báo.

Tôi thích cái thứ hai (và cố gắng thuyết phục đồng nghiệp của mình! ;-)), khi đọc rằng:

  • Nó làm giảm phạm vi của các biến đến nơi cần thiết, đó là một điều tốt.
  • Java tối ưu hóa đủ để không có sự khác biệt đáng kể về hiệu suất. IIRC, có lẽ hình thức thứ hai thậm chí còn nhanh hơn.

Dù sao, nó nằm trong danh mục tối ưu hóa sớm dựa vào chất lượng của trình biên dịch và / hoặc JVM.


5

Có một sự khác biệt trong C # nếu bạn đang sử dụng biến trong lambda, v.v. Nhưng nói chung, trình biên dịch về cơ bản sẽ làm điều tương tự, giả sử biến chỉ được sử dụng trong vòng lặp.

Cho rằng về cơ bản chúng giống nhau: Lưu ý rằng phiên bản b làm cho người đọc thấy rõ hơn rằng biến không phải và không thể được sử dụng sau vòng lặp. Ngoài ra, phiên bản b dễ dàng được tái cấu trúc hơn nhiều. Việc trích xuất phần thân vòng lặp thành phương thức riêng của nó trong phiên bản a khó hơn.Hơn nữa, phiên bản b đảm bảo với bạn rằng không có tác dụng phụ đối với việc tái cấu trúc như vậy.

Do đó, phiên bản làm tôi khó chịu vô cùng, bởi vì nó không có lợi cho nó và nó khiến cho việc lập luận về mã trở nên khó khăn hơn nhiều ...


5

Chà, bạn luôn có thể tạo một phạm vi cho điều đó:

{ //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);
    }
}

Bằng cách này, bạn chỉ khai báo biến một lần và nó sẽ chết khi bạn rời khỏi vòng lặp.


4

Tôi đã luôn nghĩ rằng nếu bạn khai báo các biến trong vòng lặp thì bạn sẽ lãng phí bộ nhớ. Nếu bạn có một cái gì đó như thế này:

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

Sau đó, không chỉ đối tượng cần được tạo cho mỗi lần lặp, mà còn cần phải có một tham chiếu mới được phân bổ cho từng đối tượng. Có vẻ như nếu trình thu gom rác chậm thì bạn sẽ có một loạt các tài liệu tham khảo lơ lửng cần được dọn sạch.

Tuy nhiên, nếu bạn có điều này:

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

Sau đó, bạn chỉ tạo một tham chiếu duy nhất và gán một đối tượng mới cho nó mỗi lần. Chắc chắn, nó có thể mất một chút thời gian để đi ra khỏi phạm vi, nhưng sau đó chỉ có một tài liệu tham khảo lơ lửng để đối phó.


3
Tham chiếu mới không được phân bổ cho từng đối tượng, ngay cả khi tham chiếu được khai báo trong vòng lặp 'for'. Trong cả hai trường hợp: 1) 'o' là một biến cục bộ và không gian ngăn xếp được phân bổ một lần cho nó khi bắt đầu hàm. 2) Có một Object mới được tạo trong mỗi lần lặp. Vì vậy, không có sự khác biệt trong hiệu suất. Đối với tổ chức mã, khả năng đọc và bảo trì, khai báo tham chiếu trong vòng lặp là tốt hơn.
Ajoy Bhatia

1
Mặc dù tôi không thể nói cho Java, nhưng trong .NET, tham chiếu không được 'phân bổ' cho từng đối tượng trong ví dụ đầu tiên. Có một mục duy nhất trên ngăn xếp cho biến cục bộ (theo phương thức) đó. Ví dụ của bạn, IL được tạo là giống hệt nhau.
Jesse C. Choper

3

Tôi nghĩ rằng nó phụ thuộc vào trình biên dịch và khó có thể đưa ra một câu trả lời chung chung.


3

Thực hành của tôi là như sau:

  • nếu loại biến đơn giản (int, double, ...) Tôi thích biến thể b (bên trong).
    Lý do: giảm phạm vi của biến.

  • nếu loại biến không đơn giản (một số loại classhoặc struct) tôi thích biến thể a (bên ngoài).
    Lý do: giảm số lượng cuộc gọi ctor-dtor.


1

Từ góc độ hiệu suất, bên ngoài là (nhiều) tốt hơn.

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;
    }
}

Tôi đã thực hiện cả hai chức năng 1 tỷ lần mỗi. bên ngoài () mất 65 mili giây. bên trong () mất 1,5 giây.


2
Chắc là đã được Debug biên dịch không tối ưu rồi hả?
Tomasz Przychodzki

int j = 0 for (; j <0x3e8; j ++) theo cách này được khai báo một lần cả hai biến và không phải mỗi chu kỳ. 2) nhiệm vụ đó là chất béo hơn tất cả các tùy chọn khác. 3) Vì vậy, quy tắc bestpractice là bất kỳ khai báo nào ngoài vòng lặp cho.
luka

1

Tôi đã thử nghiệm JS với Node 4.0.0 nếu có ai quan tâm. Khai báo bên ngoài vòng lặp dẫn đến cải thiện hiệu suất ~ 0,55 ms trung bình trên 1000 thử nghiệm với 100 triệu lần lặp mỗi lần thử. Vì vậy, tôi sẽ nói rằng hãy tiếp tục và viết nó theo cách dễ đọc / dễ bảo trì nhất là B, imo. Tôi sẽ đặt mã của mình vào một câu đố, nhưng tôi đã sử dụng mô đun Node hiệu năng - bây giờ. Đây là mã:

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) là cược an toàn hơn B) ......... Hãy tưởng tượng nếu bạn đang khởi tạo cấu trúc trong vòng lặp chứ không phải 'int' hoặc 'float' thì sao?

giống

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?
}

Bạn chắc chắn sẽ phải đối mặt với các vấn đề với rò rỉ bộ nhớ!. Do đó tôi tin rằng 'A' là đặt cược an toàn hơn trong khi 'B' dễ bị tích lũy bộ nhớ, đặc biệt là làm việc với các thư viện nguồn gần. Bạn có thể kiểm tra usinng 'Valgrind' Tool trên Linux cụ thể là 'Helgrind'.


0

Đó là một câu hỏi thú vị. Từ kinh nghiệm của tôi, có một câu hỏi cuối cùng cần xem xét khi bạn tranh luận vấn đề này về một mã:

Có bất kỳ lý do tại sao các biến sẽ cần phải là toàn cầu?

Thật hợp lý khi chỉ khai báo biến một lần, trên toàn cầu, trái ngược với nhiều lần cục bộ, bởi vì nó tốt hơn cho việc tổ chức mã và yêu cầu ít dòng mã hơn. Tuy nhiên, nếu nó chỉ cần được khai báo cục bộ trong một phương thức, tôi sẽ khởi tạo nó trong phương thức đó để rõ ràng rằng biến đó chỉ liên quan đến phương thức đó. Cẩn thận không gọi biến này bên ngoài phương thức được khởi tạo nếu bạn chọn tùy chọn thứ hai - mã của bạn sẽ không biết bạn đang nói về điều gì và sẽ báo cáo lỗi.

Ngoài ra, như một lưu ý phụ, không trùng lặp tên biến cục bộ giữa các phương thức khác nhau ngay cả khi mục đích của chúng gần giống nhau; nó chỉ gây nhầm lẫn


1
lol Tôi không đồng ý vì rất nhiều lý do ... Tuy nhiên, không bỏ phiếu ... Tôi tôn trọng quyền lựa chọn của bạn
Grantly

0

đây là hình thức tốt hơn

double intermediateResult;
int i = byte.MinValue;

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

1) theo cách này được khai báo một lần cả hai biến, và không phải mỗi lần cho chu kỳ. 2) nhiệm vụ đó là chất béo hơn tất cả các tùy chọn khác. 3) Vì vậy, quy tắc bestpractice là bất kỳ khai báo nào ngoài vòng lặp cho.


0

Đã thử điều tương tự trong Go và so sánh đầu ra của trình biên dịch bằng cách sử dụng go tool compile -S với go 1.9.4

Không có sự khác biệt, theo đầu ra của trình biên dịch chương trình.


0

Tôi đã có câu hỏi rất giống nhau trong một thời gian dài. Vì vậy, tôi đã thử nghiệm một đoạn mã thậm chí đơn giản hơn.

Kết luận: Đối với những trường hợp như vậy , KHÔNG có sự khác biệt về hiệu suất.

Trường hợp vòng lặp bên ngoài

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

Trường hợp vòng lặp bên trong

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

Tôi đã kiểm tra tệp đã biên dịch trên trình dịch ngược của IntelliJ và trong cả hai trường hợp, tôi đều nhận được cùng một Test.class

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

Tôi cũng đã phân tách mã cho cả hai trường hợp bằng cách sử dụng phương thức được đưa ra trong câu trả lời này . Tôi sẽ chỉ hiển thị những phần có liên quan đến câu trả lời

Trường hợp vòng lặp bên ngoài

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;

Trường hợp vòng lặp bên trong

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;

Nếu bạn chú ý kỹ, chỉ có phần Slotđược gán iintermediateResultin LocalVariableTableđược hoán đổi như một sản phẩm theo thứ tự xuất hiện của chúng. Sự khác biệt tương tự về vị trí được phản ánh trong các dòng mã khác.

  • Không có hoạt động bổ sung đang được thực hiện
  • intermediateResult vẫn là một biến cục bộ trong cả hai trường hợp, vì vậy không có thời gian truy cập khác nhau.

TẶNG KEM

Trình biên dịch thực hiện rất nhiều tối ưu hóa, hãy xem những gì xảy ra trong trường hợp này.

Không có trường hợp làm việc

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

Không có công việc dịch ngược

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

-1

Ngay cả khi tôi biết trình biên dịch của mình đủ thông minh, tôi sẽ không muốn dựa vào nó và sẽ sử dụng biến thể a).

Biến thể b) chỉ có ý nghĩa với tôi nếu bạn rất cần tạo trung gianResult không có sẵn sau thân vòng lặp. Nhưng dù sao tôi cũng không thể tưởng tượng được tình huống tuyệt vọng như vậy ....

EDIT: Jon Skeet đã đưa ra một quan điểm rất tốt, cho thấy rằng khai báo biến trong một vòng lặp có thể tạo ra sự khác biệt về ngữ nghĩa thực tế.

Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.