2012-09-14 51 views
9

Đối với một dự án sắp tới với node.js tôi cần thực hiện các nhiệm vụ vệ sinh khác nhau vào các thời điểm định kỳ. Cụ thể một số nhiệm vụ mỗi mili giây, những người khác cứ 20 ms một lần (50 lần mỗi giây) và những người khác mỗi giây một lần. Vì vậy, tôi đã nghĩ đến việc sử dụng setInterval(), với kết quả buồn cười: nhiều cuộc gọi hàm bị bỏ qua.node.js: setInterval() bỏ qua các cuộc gọi

Các benchmark Tôi đã từng là như sau:

var counter = 0; 
var seconds = 0; 
var short = 1; 
setInterval(function() { 
     counter ++; 
    }, short); 
setInterval(function() { 
     seconds ++; 
     log('Seconds: ' + seconds + ', counter: ' + 
      counter + ', missed ' + 
      (seconds * 1000/short - counter)); 
    }, 1000); 

Có một bộ đếm thời gian dài của một giây và một ngắn có thể được điều chỉnh bằng cách sử dụng biến short, trong trường hợp này 1 ms. Mỗi giây chúng tôi in sự khác biệt giữa số lượng bọ mong đợi trong chu kỳ ngắn và số lần thực tế mà bộ đếm ngắn được cập nhật.

Dưới đây là cách ứng xử khi bộ đếm thời gian ngắn là 1 ms:

2012-09-14T23:03:32.780Z Seconds: 1, counter: 869, missed 131 
2012-09-14T23:03:33.780Z Seconds: 2, counter: 1803, missed 197 
2012-09-14T23:03:34.781Z Seconds: 3, counter: 2736, missed 264 
... 
2012-09-14T23:03:41.783Z Seconds: 10, counter: 9267, missed 733 

Nhiều cuộc gọi chức năng đang bị bỏ qua. Dưới đây là 10 ms:

2012-09-14T23:01:56.363Z Seconds: 1, counter: 93, missed 7 
2012-09-14T23:01:57.363Z Seconds: 2, counter: 192, missed 8 
2012-09-14T23:01:58.364Z Seconds: 3, counter: 291, missed 9 
... 
2012-09-14T23:02:05.364Z Seconds: 10, counter: 986, missed 14 

Tốt hơn, nhưng một lần gọi hàm bị bỏ qua mỗi giây. Và đối với 20 ms:

2012-09-14T23:07:18.713Z Seconds: 1, counter: 46, missed 4 
2012-09-14T23:07:19.713Z Seconds: 2, counter: 96, missed 4 
2012-09-14T23:07:20.712Z Seconds: 3, counter: 146, missed 4 
... 
2012-09-14T23:07:27.714Z Seconds: 10, counter: 495, missed 5 

Cuối cùng cho 100 ms:

2012-09-14T23:04:25.804Z Seconds: 1, counter: 9, missed 1 
2012-09-14T23:04:26.803Z Seconds: 2, counter: 19, missed 1 
2012-09-14T23:04:27.804Z Seconds: 3, counter: 29, missed 1 
... 
2012-09-14T23:04:34.805Z Seconds: 10, counter: 99, missed 1 

Trong trường hợp này nó bỏ qua rất ít các cuộc gọi (khoảng cách tăng lên 2 sau 33 giây và 3 sau 108 giây

.

Các số khác nhau, nhưng đáng ngạc nhiên nhất quán giữa các lần chạy: Chạy chuẩn 1 ms đầu tiên ba lần mang lại độ trễ sau 10 giây 9267, 9259 và 9253.

Tôi không tìm thấy tài liệu tham khảo cho vấn đề cụ thể này. Có much cited Ressig post và rất nhiều câu hỏi JavaScript có liên quan, nhưng hầu hết giả định rằng mã chạy trong trình duyệt chứ không phải trong node.js.

Bây giờ cho câu hỏi đáng sợ: những gì đang xảy ra ở đây? Chỉ đùa thôi; các cuộc gọi chức năng rõ ràng đang bị bỏ qua. Nhưng tôi không thấy mô hình. Tôi nghĩ rằng các chu kỳ dài có thể ngăn chặn các chu kỳ ngắn, nhưng nó không có ý nghĩa gì trong trường hợp 1 ms. Các cuộc gọi hàm chu kỳ ngắn không chồng chéo vì chúng chỉ cập nhật một biến, và quá trình node.js gần CPU 5% ngay cả với chu kỳ ngắn 1 ms. Tuy nhiên, tải trung bình là cao, vào khoảng 0,50. Tôi không biết tại sao một ngàn cuộc gọi đang nhấn mạnh hệ thống của tôi rất nhiều, mặc dù, vì node.js xử lý many more clients perfectly; nó phải đúng là setInterval() is CPU intensive (hoặc tôi đang làm điều gì sai).

Một giải pháp rõ ràng là nhóm các cuộc gọi hàm sử dụng bộ hẹn giờ dài hơn và sau đó chạy các cuộc gọi hàm chu kỳ ngắn nhiều lần để mô phỏng bộ hẹn giờ ngắn hơn. Sau đó, sử dụng chu kỳ dài như một "toa xe chổi" mà làm cho bất kỳ cuộc gọi nhỡ trong khoảng thời gian thấp hơn. Một ví dụ: thiết lập 20 ms và 1000 ms setInterval() cuộc gọi. Đối với cuộc gọi 1 ms: gọi cho họ 20 lần trong cuộc gọi lại 20 ms. Đối với cuộc gọi 1000 ms: kiểm tra số lần chức năng 20ms đã được gọi (ví dụ: 47), thực hiện mọi cuộc gọi còn lại (ví dụ: 3). Nhưng sơ đồ này sẽ phức tạp hơn một chút, vì các cuộc gọi có thể chồng lên nhau theo những cách thú vị; nó cũng sẽ không được thường xuyên mặc dù nó có thể trông giống như nó.

Câu hỏi thực sự là: nó có thể được thực hiện tốt hơn, hoặc với setInterval() hoặc các bộ hẹn giờ khác trong node.js? Cảm ơn trước.

Trả lời

10

Hàm SetInterval trong javascript không chính xác. Bạn nên cố gắng sử dụng bộ hẹn giờ có độ phân giải cao. Building accurate Timers in javascript

+0

Làm thế nào? Những giờ giải quyết nào, thư viện? – alexfernandez

+0

Có nhiều tập lệnh hẹn giờ có độ phân giải cao trong google.http: //www.sitepoint.com/tạo-chính xác-giờ-trong-javascript/ – zer02

+0

Nó sẽ không giải quyết được vấn đề hiện tại. –

8

Nhìn vào doc này: http://nodejs.org/api/timers.html#timers_settimeout_callback_delay_arg

Điều quan trọng cần lưu ý là gọi lại của bạn có thể sẽ không được gọi chính xác trì hoãn mili giây - Node.js không đảm bảo về thời gian chính xác khi gọi lại sẽ cháy và cũng không phải thứ tự sắp xếp sẽ kích hoạt. Cuộc gọi lại sẽ được gọi càng gần thời gian được chỉ định.

Điều này xảy ra vì mã ứng dụng chặn vòng lặp sự kiện. Tất cả các sự kiện giờ và I/O chỉ có thể được xử lý trên nextTick.

Bạn có thể thấy hành vi này với mã này:

setInterval(function() { 
    console.log(Date.now()); 
    for (var i = 0; i < 100000000; i++) { 
    } 
}, 1); 

Cố gắng thay đổi lặp đếm và xem kết quả.

Lý tưởng nhất, bộ hẹn giờ sẽ được kích hoạt chính xác nếu các ứng dụng đánh dấu sẽ kéo dài ít hơn một ms. Nhưng điều này là không thực tế trong một ứng dụng thực tế.

+0

Tôi đã đọc tài liệu tham khảo đó, nhưng nó không giải thích tại sao. Trong tiêu chuẩn của tôi, tôi không có mã ứng dụng nào chặn vòng lặp sự kiện. Các tham chiếu đến nextTick là thú vị, cảm ơn. Tuy nhiên nó chỉ đẩy vấn đề lên một cấp độ: tần suất process.nextTick() cháy? Tại sao, và nó có thể được thay đổi? – alexfernandez

+0

Không, tôi không có nghĩa là 'process.nextTick()' như một giải pháp. Tôi muốn nói rằng không có cách nào để xử lý các bộ đếm thời gian và các sự kiện I/O thường xuyên hơn thời gian thực hiện một sự lặp vòng lặp sự kiện. –

+0

Đã hiểu. Nhưng một vòng lặp sự kiện lặp lại bao lâu, trong trường hợp không có tải? – alexfernandez

2

Câu trả lời xảy ra là sự kết hợp của những người được cung cấp bởi Vadim và zer02, vì vậy tôi để lại một bài viết ở đây. Như Vadim đã nói, hệ thống không thể đối phó với các cập nhật quá thường xuyên, và thêm một số tải vào hệ thống sẽ không giúp được gì. Hay đúng hơn là thời gian chạy không thể đối phó; hệ thống phải nhiều hơn khả năng kích hoạt gọi lại mỗi mili giây nếu cần thiết, nhưng đối với một số lý do không giải thích được thường thì nó không muốn.

Giải pháp là sử dụng accurate timers, như zer02 đã nhận xét. Đừng bị lừa bởi tên; cơ chế được sử dụng là cùng một setTimeout(), nhưng độ trễ được điều chỉnh tùy thuộc vào thời gian còn lại cho đến khi bộ hẹn giờ sẽ kích hoạt. Vì vậy, nếu thời gian kết thúc thì "timer chính xác" sẽ gọi setTimeout (gọi lại, 0) được chạy ngay lập tức. Hệ thống tải, đáng ngạc nhiên, ít hơn với setInterval(): khoảng 2% của CPU thay vì 5%, trong mẫu rất không khoa học của tôi.

chức năng đơn giản này có thể có ích:

/** 
* A high resolution timer. 
*/ 
function timer(delay, callback) 
{ 
    // self-reference 
    var self = this; 

    // attributes 
    var counter = 0; 
    var start = new Date().getTime(); 

    /** 
    * Delayed running of the callback. 
    */ 
    function delayed() 
    { 
     callback(delay); 
     counter ++; 
     var diff = (new Date().getTime() - start) - counter * delay; 
     setTimeout(delayed, delay - diff); 
    } 

    // start timer 
    delayed(); 
    setTimeout(delayed, delay); 
} 

Để sử dụng, chỉ cần gọi new timer(delay, callback);. (Có, tôi đã đảo ngược thứ tự của các thông số vì việc gọi lại trước hết là rất khó chịu.)

Lưu ý cuối cùng: setTimeout (gọi lại, trì hoãn) không sử dụng đệ quy như tôi lo ngại (như: chờ một thời gian, sau đó gọi lại gọi lại), nó chỉ đặt các cuộc gọi lại trong một hàng đợi sẽ được gọi bởi thời gian chạy khi đến lượt nó, trong bối cảnh toàn cục.

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