Kỹ thuật đảo ngược vòng lặp là gì?


89

Tôi đã xem qua một tài liệu nói về các kỹ thuật tối ưu hóa trình biên dịch đúng lúc (JIT) cho Java. Một trong số đó là "đảo ngược vòng lặp". Và tài liệu nói:

Bạn thay thế một whilevòng lặp thông thường bằng một do-whilevòng lặp. Và do-whilevòng lặp được đặt trong một ifmệnh đề. Sự thay thế này dẫn đến ít lần nhảy hơn.

Đảo ngược vòng lặp hoạt động như thế nào và nó tối ưu hóa đường dẫn mã của chúng ta như thế nào?

NB: Sẽ thật tuyệt nếu ai đó có thể giải thích bằng một ví dụ về mã Java và cách JIT tối ưu hóa nó thành mã gốc và tại sao nó lại tối ưu trong các bộ xử lý hiện đại.


2
Đó không phải là điều bạn sẽ làm với mã nguồn của mình. Nó xảy ra ở cấp mã gốc.
Marko Topolnik

2
@MarkoTopolnik tôi biết. Nhưng tôi muốn biết cách JIT thực hiện điều này ở cấp độ mã gốc. Cảm ơn.
Cố gắng

1
thật tuyệt, có một trang wikipedia về điều này với rất nhiều ví dụ en.wikipedia.org/wiki/Loop_inversion . Ví dụ C cũng hợp lệ trong Java.
Benjamin Gruenbaum

Cách đây một thời gian lấy cảm hứng từ một trong những câu hỏi trên SO Tôi đã thực hiện một nghiên cứu ngắn về vấn đề này, có thể kết quả sẽ hữu ích cho bạn: stackoverflow.com/questions/16205843/java-loop-efficiency/…
Adam Siemion

Đây có phải là điều tương tự như nơi mà điều kiện lặp thường được đặt ở cuối (bất kể sẽ có ít bước nhảy hơn được thực hiện hay không), chỉ để có ít lệnh nhảy hơn (1 so với 2 mỗi lần lặp)?
Extremeaxe5

Câu trả lời:


108
while (condition) { 
  ... 
}

Quy trình làm việc:

  1. Kiểm tra tình trạng;
  2. nếu sai, nhảy ra bên ngoài vòng lặp;
  3. chạy một lần lặp lại;
  4. nhảy lên đầu.

if (condition) do {
  ...
} while (condition);

Quy trình làm việc:

  1. Kiểm tra tình trạng;
  2. nếu sai, nhảy ra ngoài vòng lặp;
  3. chạy một lần lặp lại;
  4. Kiểm tra tình trạng;
  5. nếu đúng, hãy chuyển sang bước 3.

So sánh hai bước này, bạn có thể dễ dàng thấy rằng bước sau có thể không thực hiện bất kỳ bước nhảy nào, miễn là có chính xác một bước trong vòng lặp và nói chung số lần nhảy sẽ ít hơn một lần so với số lần lặp. Đầu tiên sẽ phải quay lại để kiểm tra điều kiện, chỉ nhảy ra khỏi vòng lặp khi điều kiện sai.

Các bước nhảy trên các kiến ​​trúc CPU có đường ống hiện đại có thể khá tốn kém: vì CPU đang hoàn thành việc thực hiện các kiểm tra trước khi bước nhảy, các lệnh vượt quá bước nhảy đó đã ở giữa đường dẫn. Tất cả quá trình xử lý này phải bị loại bỏ nếu dự đoán nhánh không thành công. Quá trình thực thi tiếp tục bị trì hoãn trong khi đường ống đang được lặp lại.

Giải thích cho dự đoán nhánh đã đề cập : đối với mỗi loại bước nhảy có điều kiện, CPU có hai lệnh, mỗi lệnh bao gồm đặt cược vào kết quả. Ví dụ: bạn sẽ đặt một chỉ dẫn nói rằng " nhảy nếu không, đặt cược vào không " ở cuối vòng lặp vì bước nhảy sẽ phải được thực hiện trên tất cả các lần lặp ngoại trừ lần cuối cùng. Bằng cách đó, CPU bắt đầu bơm đường ống của nó với các lệnh theo sau mục tiêu bước nhảy thay vì các lệnh theo chính lệnh nhảy.

Lưu ý quan trọng

Vui lòng không lấy đây làm ví dụ về cách tối ưu hóa ở cấp mã nguồn. Điều đó sẽ hoàn toàn sai lầm vì, như câu hỏi của bạn đã rõ ràng, việc chuyển đổi từ dạng đầu tiên thành dạng thứ hai là điều mà trình biên dịch JIT thực hiện như một vấn đề thường lệ, hoàn toàn tự nó.


51
Ghi chú ở cuối bài thực sự là một điều rất, rất quan trọng.
TJ Crowder

2
@AdamSiemion: Bytecode được tạo cho do-whilemã nguồn đã cho là không liên quan, vì chúng tôi không thực sự viết nó. Chúng tôi viết whilevòng lặp và để trình biên dịch và JIT âm mưu cải thiện nó cho chúng tôi (thông qua đảo ngược vòng lặp) nếu / khi cần thiết.
TJ Crowder

1
@TJCrowder +1 cho điều trên, cùng với lưu ý cho Adam: không bao giờ xem xét bytecode khi nghĩ về các tối ưu hóa của trình biên dịch JIT. Bytecode gần với mã nguồn Java hơn nhiều so với mã được biên dịch JIT thực tế đang được thực thi. Trên thực tế, xu hướng trong các ngôn ngữ hiện đại là hoàn toàn không có mã bytecode như một phần của quá trình phân tách ngôn ngữ.
Marko Topolnik

1
Sẽ có nhiều thông tin hơn Lưu ý quan trọng đã được giải thích thêm một chút. Tại sao nó hoàn toàn sai lầm?
arsaKasra

2
@arsaKasra Đó là một sai lầm vì nói chung khả năng đọc và độ ổn định vượt trội so với tối ưu hóa trong mã nguồn. Đặc biệt với tiết lộ rằng JIT thực hiện điều này cho bạn, bạn không nên tự mình cố gắng tối ưu hóa (rất vi mô).
Radiodef

24

Điều này có thể tối ưu hóa một vòng lặp luôn được thực thi ít nhất một lần.

Sau đó, một whilevòng lặp thông thường sẽ luôn quay lại phần đầu ít nhất một lần và nhảy về phần cuối một lần khi kết thúc. Ví dụ về một vòng lặp đơn giản chạy một lần:

int i = 0;
while (i++ < 1) {
    //do something
}  

Mặt khác, một do-whilevòng lặp sẽ bỏ qua bước nhảy đầu tiên và cuối cùng. Đây là một vòng lặp tương đương với vòng lặp ở trên, sẽ chạy mà không có bước nhảy:

int i = 0;
if (i++ < 1) {
    do {
        //do something
    } while (i++ < 1); 
}

+1 là chính xác và trước tiên, vui lòng xem xét thêm một ví dụ mã. Một cái gì đó giống như boolean b = true; while(b){ b = maybeTrue();}boolean b;do{ b = maybeTrue();}while(b);đủ.
Benjamin Gruenbaum

Đừng lo lắng. Nó làm mất hiệu lực dòng mở đầu của câu trả lời, fwiw. :-)
TJ Crowder

@TJ Chà, nó vẫn sẽ không tối ưu hóa một vòng lặp chưa được nhập, sẽ có một bước nhảy trong cả hai trường hợp.
Keppil

À, vâng. Xin lỗi, tôi đang đọc điều đó có nghĩa là bạn không thể áp dụng nó cho các vòng lặp không lặp lại ít nhất một lần (đúng hơn là nó không giúp ích cho họ). Cùng bạn ngay bây giờ. :-)
TJ Crowder

@Keppil Có lẽ bạn nên nói rõ rằng trong trường hợp chúng ta có một số lượng lớn các lần lặp X, thì chúng ta sẽ chỉ lưu một bước nhảy duy nhất trong số các lần lặp X.
Manuel Selva

3

Hãy xem qua chúng:

Các whilephiên bản:

void foo(int n) {
    while (n < 10) {
       use(n);
       ++n;
    }
    done();
}
  1. Đầu tiên, chúng tôi kiểm tra nvà chuyển sang done();nếu điều kiện không đúng.
  2. Sau đó, chúng tôi sử dụng và tăng dần n.
  3. Bây giờ chúng ta quay trở lại điều kiện.
  4. Rửa sạch, lặp lại.
  5. Khi điều kiện không còn đúng nữa, chúng ta chuyển sang done().

Các do-whilephiên bản:

(Hãy nhớ rằng chúng tôi không thực sự làm điều này trong mã nguồn [sẽ gây ra các vấn đề bảo trì], trình biên dịch / JIT thực hiện điều đó cho chúng tôi.)

void foo(int n) {
    if (n < 10) {
        do {
            use(n);
            ++n;
        }
        while (n < 10);
    }
    done();
}
  1. Đầu tiên, chúng tôi kiểm tra nvà chuyển sang done();nếu điều kiện không đúng.
  2. Sau đó, chúng tôi sử dụng và tăng dần n.
  3. Bây giờ chúng tôi kiểm tra điều kiện và nhảy lại nếu nó đúng.
  4. Rửa sạch, lặp lại.
  5. Khi điều kiện không còn đúng nữa, chúng tôi chuyển (không nhảy) đến done().

Vì vậy, ví dụ, nếu nbắt đầu 9, chúng tôi không bao giờ nhảy ở do-whilephiên bản, trong khi trong whilephiên bản, chúng tôi phải quay lại từ đầu, làm bài kiểm tra và sau đó quay trở lại cuối khi chúng tôi thấy nó không đúng. .


3

Đảo ngược vòng lặp là một kỹ thuật tối ưu hóa hiệu suất giúp cải thiện hiệu suất vì bộ xử lý có thể đạt được cùng một kết quả với ít lệnh hơn. Điều này chủ yếu sẽ cải thiện hiệu suất trong các điều kiện biên.

Liên kết này cung cấp một ví dụ khác về đảo ngược vòng lặp. Trong một số kiến ​​trúc mà việc giảm và so sánh được thực hiện như một tập lệnh duy nhất, việc chuyển đổi vòng lặp for thành một lúc với thao tác giảm và so sánh là rất hợp lý.

Wikipedia có một ví dụ rất hay và tôi đang giải thích lại ở đây.

 int i, a[100];
  i = 0;
  while (i < 100) {
    a[i] = 0;
    i++;
  }

sẽ được trình biên dịch chuyển đổi thành

  int i, a[100];
  i = 0;
  if (i < 100) {
    do {
      a[i] = 0;
      i++;
    } while (i < 100);
  }

Điều này chuyển thành hiệu suất như thế nào? Khi giá trị của i là 99, bộ xử lý không cần thực hiện GOTO (bắt buộc trong trường hợp đầu tiên). Điều này cải thiện hiệu suấ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.