2012-10-16 24 views
7

Chương trình kèm theo (xem ở cuối), khi thực hiện, mang lại kết quả như sau:Tại sao ngủ giữa lặp gây ra hoạt động trong vòng một để mất nhiều thời gian hơn so với trường hợp nó không ngủ

.......... 
with sleep time of 0ms 
    times= [1, 1, 1, 0, 1, 1, 0, 1, 1, 0] 
    average= 0.7 
.......... 
with sleep time of 2000ms 
    times= [2, 2, 2, 2, 2, 1, 2, 2, 2, 2] 
    average= 1.9 

Trong cả hai trường hợp chính xác cùng một mã được thực hiện mà là để liên tục có được giá trị tiếp theo từ một đối tượng ngẫu nhiên được khởi tạo ở đầu chương trình. Phương pháp khởi động được thực hiện trước tiên được cho là kích hoạt bất kỳ loại phân loại JIT nào trước khi thử nghiệm thực tế bắt đầu.

Có ai giải thích lý do cho sự khác biệt này không? Tôi đã có thể lặp lại kết quả này trong máy tính của tôi mỗi lần cho đến nay, và điều này đã được thực hiện trên một hệ thống Windows đa lõi với java 7.

Một điều thú vị là nếu thứ tự các thử nghiệm được thực hiện là đảo ngược, có nghĩa là, nếu chúng ta chạy vòng lặp với độ trễ trước khi vòng lặp mà không có sự chậm trễ, thì timings tương tự hơn (với không có vòng lặp chậm trễ thực sự mất nhiều thời gian):

.......... 
with sleep time of 2000ms 
    times= [2, 2, 2, 2, 2, 2, 2, 2, 2, 2] 
    average= 2.0 
.......... 
with sleep time of 0ms 
    times= [2, 3, 3, 2, 3, 3, 2, 3, 2, 3] 
    average= 2.6 

như nhiều như tôi có thể nói , không có đối tượng nào đang được tạo bên trong phương thức hoạt động, và khi chạy điều này thông qua một trình lược tả thì dường như không có bộ sưu tập rác nào được kích hoạt. Một phỏng đoán hoang dã là một số giá trị được lưu trữ trong bộ nhớ cache cục bộ bộ xử lý bị xóa khi luồng được đặt vào chế độ ngủ và sau đó khi luồng đánh thức nó cần lấy giá trị từ bộ nhớ chính, nhưng điều đó không quá nhanh. Tuy nhiên, điều đó không giải thích tại sao đảo ngược thứ tự tạo ra sự khác biệt ...

Tình huống thực tế khi tôi bắt đầu quan sát hành vi này (nhắc tôi viết lớp thử nghiệm mẫu này) là XML unmarshalling, nơi tôi nhận thấy rằng unmarshalling cùng một tài liệu lặp đi lặp lại lần sau cái kia nhanh chóng mang lại thời gian tốt hơn so với thực hiện cùng một điều nhưng với một sự chậm trễ giữa các cuộc gọi đến unmarshal (chậm trễ tạo ra thông qua giấc ngủ hoặc bằng tay).

Đây là mã:

import java.util.ArrayList; 
import java.util.List; 
import java.util.Random; 

public class Tester 
{ 
    public static void main(String[] args) throws InterruptedException 
    { 
     warmUp(10000); 

     int numRepetitions = 10; 
     runOperationInALoop(numRepetitions, 0); 
     runOperationInALoop(numRepetitions, 2000); 
    } 

    private static void runOperationInALoop(int numRepetitions, int sleepTime) throws InterruptedException 
    { 
     List<Long> times = new ArrayList<Long>(numRepetitions); 
     long totalDuration = 0; 

     for(int i=0; i<numRepetitions; i++) 
     { 
      Thread.sleep(sleepTime); 

      long before = System.currentTimeMillis(); 
      someOperation(); 
      long duration = System.currentTimeMillis() - before; 

      times.add(duration); 
      totalDuration = totalDuration + duration; 

      System.out.print("."); 
     } 
     System.out.println(); 

     double averageTimePerOperation = totalDuration/(double)numRepetitions; 

     System.out.println("with sleep time of " + sleepTime + "ms"); 
     System.out.println(" times= " + times); 
     System.out.println(" average= " + averageTimePerOperation); 
    } 

    private static void warmUp(int warmUpRepetitions) 
    { 
     for(int i=0; i<warmUpRepetitions; i++)    
     { 
      someOperation(); 
     } 
    } 

    public static int someInt; 
    public static Random random = new Random(123456789L); 

    private static void someOperation() 
    { 
     for(int j=0; j<50000; j++) 
     { 
      someInt = ((int)random.nextInt()*10) + 1; 
     } 
    } 
} 

Trả lời

9

Khi bạn ngủ ngay cả một khoảng thời gian ngắn (bạn có thể thấy rằng 10 ms là đủ lâu), bạn bỏ CPU và dữ liệu, hướng dẫn và dự đoán rẽ nhánh bộ đệm bị quấy rầy hoặc thậm chí bị xóa. Thậm chí thực hiện một cuộc gọi hệ thống như System.currentTimeMillis() hoặc System.nanoTime() chính xác hơn nhiều() có thể thực hiện điều này ở một mức độ nhỏ.

AFAIK, Cách duy nhất để tránh từ bỏ lõi là bận rộn chờ đợi và sử dụng ái lực luồng để khóa luồng của bạn thành lõi. Điều này ngăn chặn giảm thiểu một sự xáo trộn như vậy và có nghĩa là chương trình của bạn có thể chạy nhanh hơn 2-5x trong các tình huống trễ thấp, tức là khi các nhiệm vụ phụ phần nghìn giây quan trọng.

Đối với sự quan tâm của bạn

http://vanillajava.blogspot.co.uk/2012/01/java-thread-affinity-support-for-hyper.html

http://vanillajava.blogspot.co.uk/2012/02/how-much-difference-can-thread-affinity.html

+0

Điều đó có ý nghĩa và tôi cho rằng phỏng đoán hoang dã của tôi không phải là quá tuyệt đối. Tuy nhiên, vẫn chưa rõ lý do tại sao phiên bản "không ngủ" của vòng lặp cho thấy hiệu suất tương tự (tồi tệ hơn) khi nó được thực hiện sau phiên bản "có ngủ". Cảm ơn các liên kết! – jsiqueira

+0

"Cách duy nhất để tránh bỏ lõi là chờ đợi bận rộn" - một chủ đề đang chờ đợi bận rộn vẫn có thể được sắp xếp trước theo lịch trình, phải không? Điều đó vẫn sẽ gây ra bối cảnh chuyển đổi và bộ nhớ cache thay đổi bộ xử lý, như tôi hiểu nó. Cần điều tra thêm về cách hoạt động của tính năng gắn kết chuỗi chủ đề. – jsiqueira

+1

Trên Linux, bạn có thể báo cho bộ lập lịch để tách riêng các CPU cụ thể. Nếu bạn sử dụng ái lực luồng để chỉ định một luồng cho một CPU bị cô lập, nó sẽ không bị làm trống bởi một luồng khác. –

2

Khi bạn đề đi ngủ bạn đang chủ yếu nói với JVM: chủ đề này được không làm gì cho mili giây X bên cạnh. Các JVM có khả năng vào thời điểm đó để đánh thức các chủ đề nền khác nhau để làm điều của họ (GC, ví dụ), mà cũng có thể gây ra các bản cập nhật cho dữ liệu được lưu trữ trong bộ nhớ cache của bộ xử lý. Khi bạn đang reawakes thread, một số dữ liệu của nó có thể không còn trong bộ nhớ cache (nhanh), nhưng cũng có thể được chuyển sang bộ nhớ chính (chậm).

Hãy xem http://mechanical-sympathy.blogspot.co.uk/ để biết thêm thảo luận về các hiệu ứng bộ nhớ đệm ở mức độ thấp.

+0

Cảm ơn bạn đã liên kết! – jsiqueira

0
  1. Không đảm bảo rằng sleep() ngủ chính xác khoảng thời gian bạn chỉ định. Có một tuyên bố cụ thể trong Javadoc về hiệu ứng đó.

  2. System.currentTimeMillis() có mức chi tiết hệ thống phụ thuộc mà bạn đang hiển thị bằng cách chạy các lần lặp tương đối ít như năm 2000. Bạn nên nhân với ít nhất 10 để thoát ra khỏi vùng chi tiết. Trên Windows, tôi tin rằng nó cao tới 16ms.

+0

1. Lượng thời gian mà chủ đề ngủ không đặc biệt quan trọng. Từ các câu trả lời trước đó, có vẻ như nó chỉ cần đủ dài để làm cho luồng từ bỏ lõi mà nó đang chạy. – jsiqueira

+0

2. Từ những con số tôi nhận được (và tính nhất quán của họ) có vẻ như tôi ở trên mức độ chi tiết. Ngay cả khi chuyển sang System.nanoTime() và thậm chí có độ chính xác tốt hơn, vì vậy tôi nghiêng rằng điều này không ảnh hưởng xấu đến các thử nghiệm, trong trường hợp này. – jsiqueira

+0

@jsiqueira Eh? 'Lượng thời gian mà chủ đề ngủ' là điều duy nhất được đề cập trong câu hỏi này. Nếu nó ngủ lâu hơn thời gian bạn chỉ định, bạn sẽ nhận được hành vi mà bạn đã quan sát. – EJP

Các vấn đề liên quan