2013-08-21 17 views
74

Tôi có một ứng dụng Javascript khá phức tạp, có vòng lặp chính được gọi là 60 lần mỗi giây. Dường như có rất nhiều bộ sưu tập rác đang diễn ra (dựa trên đầu ra 'răng cưa' từ dòng thời gian Bộ nhớ trong các công cụ dành cho Chrome) - và điều này thường ảnh hưởng đến hiệu suất của ứng dụng.Thực tiễn tốt nhất để giảm hoạt động Thu gom rác trong Javascript

Vì vậy, tôi đang cố gắng nghiên cứu các phương pháp hay nhất để giảm số lượng công việc mà người thu gom rác phải làm. (Hầu hết các thông tin tôi có thể tìm thấy trên web liên quan đến việc tránh rò rỉ bộ nhớ, đó là một câu hỏi hơi khác - bộ nhớ của tôi được giải phóng, nó chỉ là có quá nhiều bộ sưu tập rác đang xảy ra.) Tôi giả sử rằng điều này chủ yếu đi xuống để tái sử dụng các đối tượng càng nhiều càng tốt, nhưng tất nhiên ma quỷ là trong các chi tiết.

Ứng dụng được cấu trúc trong 'các lớp' dọc theo các dòng John Resig's Simple JavaScript Inheritance. Tôi nghĩ một vấn đề là một số chức năng có thể được gọi là hàng ngàn lần mỗi giây (vì chúng được sử dụng hàng trăm lần trong mỗi lần lặp của vòng lặp chính), và có lẽ các biến làm việc cục bộ trong các hàm này (chuỗi, mảng). , vv) có thể là vấn đề.

Tôi biết về đối tượng gộp cho các đối tượng lớn hơn hoặc nặng hơn (và chúng tôi sử dụng nó ở mức độ), nhưng tôi đang tìm kiếm các kỹ thuật có thể áp dụng trên bảng, đặc biệt liên quan đến các hàm được gọi rất nhiều lần trong các vòng chặt chẽ.

Tôi có thể sử dụng những kỹ thuật nào để giảm lượng công việc mà bộ thu gom rác phải làm?

Và, cũng có thể - những kỹ thuật nào có thể được sử dụng để xác định đối tượng nào đang được thu gom rác nhiều nhất? (Đó là một codebase cực lớn, vì vậy việc so sánh các ảnh chụp nhanh của heap không thành công)

+2

Bạn có ví dụ về mã của mình mà bạn có thể hiển thị cho chúng tôi không? Câu hỏi sẽ dễ trả lời hơn (nhưng cũng ít có khả năng chung hơn, vì vậy tôi không chắc chắn ở đây) –

+0

Bạn có thể sử dụng và sử dụng lại các biến trong phạm vi bên ngoài, để bạn không tạo lại chúng mỗi lần. Ví dụ: 'var x =" longstring ";' trong vòng lặp của bạn sẽ gây ra hàng nghìn "longsting" được tạo ra, nhưng trong một outerscope nó sẽ chỉ gây ra một "chuỗi dài". – Paulpro

+2

Làm thế nào về chức năng dừng chạy hàng ngàn lần mỗi giây? Đó có thực sự là cách duy nhất để tiếp cận điều này? Câu hỏi này có vẻ giống như một vấn đề XY. Bạn đang mô tả X nhưng những gì bạn đang thực sự tìm kiếm là một giải pháp cho Y. –

Trả lời

91

Rất nhiều điều bạn cần làm để giảm thiểu sự khuấy động GC chống lại những gì được coi là thành ngữ JS trong hầu hết các trường hợp khác, vì vậy hãy ghi nhớ bối cảnh khi đánh giá lời khuyên tôi đưa ra.

Allocation xảy ra trong dịch viên hiện đại ở một số nơi:

  1. Khi bạn tạo một đối tượng thông qua new hoặc thông qua cú pháp đen [...], hoặc {}.
  2. Khi bạn nối chuỗi.
  3. Khi bạn nhập phạm vi chứa khai báo hàm.
  4. Khi bạn thực hiện một hành động kích hoạt ngoại lệ.
  5. Khi bạn đánh giá biểu thức hàm: (function (...) { ... }).
  6. Khi bạn thực hiện một hoạt động mà cưỡng ép vào Object như Object(myNumber) hoặc Number.prototype.toString.call(42)
  7. Khi bạn gọi một dựng sẵn mà không bất kỳ trong số này dưới mui xe, như Array.prototype.slice.
  8. Khi bạn sử dụng arguments để phản ánh qua danh sách tham số.
  9. Khi bạn chia chuỗi hoặc khớp với cụm từ thông dụng.

Tránh thực hiện những việc đó và chia sẻ và sử dụng lại các đối tượng nếu có thể.

Cụ thể, nhìn ra những cơ hội để:

  1. Kéo chức năng bên trong mà không có hoặc có rất ít phụ thuộc vào đóng trên nhà nước ra vào một phạm vi cao hơn, sống lâu hơn. (Một số bộ mã hóa như Closure compiler có thể nội tuyến chức năng bên trong và có thể cải thiện hiệu suất GC của bạn.)
  2. Tránh sử dụng các chuỗi để biểu diễn dữ liệu có cấu trúc hoặc địa chỉ động. Đặc biệt tránh phân tích cú pháp nhiều lần bằng cách sử dụng split hoặc đối sánh cụm từ thông dụng vì mỗi lần yêu cầu phân bổ nhiều đối tượng. Điều này thường xảy ra với các khóa trong bảng tra cứu và ID nút DOM động. Ví dụ: lookupTable['foo-' + x]document.getElementById('foo-' + x) cả hai đều liên quan đến phân bổ vì có một chuỗi nối. Thông thường, bạn có thể gắn các khóa vào các đối tượng tồn tại lâu dài thay vì ghép lại. Tùy thuộc vào trình duyệt bạn cần hỗ trợ, bạn có thể sử dụng Map để sử dụng các đối tượng dưới dạng khóa trực tiếp.
  3. Tránh bắt ngoại lệ trên các đường dẫn mã thông thường. Thay vì try { op(x) } catch (e) { ... }, hãy làm if (!opCouldFailOn(x)) { op(x); } else { ... }.
  4. Khi bạn không thể tránh tạo chuỗi, ví dụ: để truyền thông điệp tới máy chủ, hãy sử dụng nội trang dựng sẵn như JSON.stringify sử dụng bộ đệm gốc bên trong để tích lũy nội dung thay vì phân bổ nhiều đối tượng.
  5. Tránh sử dụng gọi lại cho các sự kiện tần số cao và nơi bạn có thể, chuyển qua dưới dạng gọi lại hàm dài (xem 1) để tạo lại trạng thái từ nội dung thư.
  6. Tránh sử dụng arguments vì các hàm sử dụng phải tạo đối tượng giống như mảng khi được gọi.

Tôi đã đề xuất sử dụng JSON.stringify để tạo thông điệp mạng đi. Phân tích cú pháp thông điệp đầu vào bằng cách sử dụng JSON.parse rõ ràng là liên quan đến phân bổ và rất nhiều thông báo cho các thư lớn. Nếu bạn có thể đại diện cho các tin nhắn đến của bạn như là mảng của nguyên thủy, sau đó bạn có thể tiết kiệm rất nhiều phân bổ. Chỉ nội trang dựng sẵn khác mà bạn có thể xây dựng một trình phân tích cú pháp không phân bổ là String.prototype.charCodeAt. Trình phân tích cú pháp cho một định dạng phức tạp chỉ sử dụng định dạng đó sẽ là địa ngục để đọc.

+0

Bạn không nghĩ rằng các đối tượng 'JSON.parse'd phân bổ không gian ít hơn (hoặc bằng) so với chuỗi tin nhắn? – Bergi

+0

@Bergi, Điều đó phụ thuộc vào việc tên thuộc tính có yêu cầu phân bổ riêng biệt hay không, nhưng trình phân tích cú pháp tạo sự kiện thay vì cây phân tích cú pháp không có phân bổ không liên quan. –

+0

Câu trả lời tuyệt vời, cảm ơn bạn! Rất nhiều lời xin lỗi vì tiền thưởng đã hết hạn - tôi đã đi du lịch vào thời điểm đó, và vì một lý do nào đó tôi không thể đăng nhập vào SO bằng tài khoản gmail của mình trên điện thoại của mình ....:/ – UpTheCreek

9

Là một nguyên tắc chung bạn muốn lưu vào bộ nhớ cache càng nhiều càng tốt và ít tạo và hủy cho mỗi lần chạy vòng lặp của bạn .

Điều đầu tiên xuất hiện trong đầu của tôi là giảm việc sử dụng các hàm ẩn danh (nếu bạn có) trong vòng lặp chính của bạn. Cũng dễ dàng rơi vào cái bẫy của việc tạo ra và phá hủy các vật thể được truyền vào các chức năng khác. Tôi không phải là một chuyên gia về javascript, nhưng tôi sẽ tưởng tượng rằng đây:

var options = {var1: value1, var2: value2, ChangingVariable: value3}; 
function loopfunc() 
{ 
    //do something 
} 

while(true) 
{ 
    $.each(listofthings, loopfunc); 

    options.ChangingVariable = newvalue; 
    someOtherFunction(options); 
} 

sẽ chạy nhanh hơn rất nhiều so với điều này:

while(true) 
{ 
    $.each(listofthings, function(){ 
     //do something on the list 
    }); 

    someOtherFunction({ 
     var1: value1, 
     var2: value2, 
     ChangingVariable: newvalue 
    }); 
} 

Có bao giờ bất kỳ thời gian chết cho chương trình của bạn? Có thể bạn cần nó chạy trơn tru trong một hoặc hai giây (ví dụ: cho hoạt ảnh) và sau đó có nhiều thời gian hơn để xử lý? Nếu đây là trường hợp tôi có thể thấy việc lấy các đối tượng bình thường sẽ được thu thập rác trong suốt hoạt ảnh và giữ một tham chiếu đến chúng trong một số đối tượng toàn cầu. Sau đó, khi hoạt ảnh kết thúc, bạn có thể xóa tất cả các tham chiếu và để bộ thu gom rác thực hiện công việc.

Xin lỗi nếu điều này hơi tầm thường so với những gì bạn đã thử và nghĩ đến.

+0

Điều này. Ngoài ra còn có các chức năng được đề cập bên trong các chức năng khác (không phải là IIFE) cũng là lạm dụng phổ biến làm bỏng rất nhiều bộ nhớ và rất dễ bỏ sót. – Esailija

+0

Cảm ơn Chris! Tôi không gặp bất kỳ thời gian chết nào:/ – UpTheCreek

5

Tôi muốn tạo một hoặc vài đối tượng trong global scope (nơi tôi chắc chắn bộ thu gom rác không được phép chạm vào chúng), sau đó tôi cố gắng tái cấu trúc giải pháp của mình để sử dụng các đối tượng đó để hoàn thành công việc, thay vì sử dụng các biến cục bộ.

Tất nhiên nó không thể được thực hiện ở khắp mọi nơi trong mã, nhưng nói chung đó là cách của tôi để tránh thu gom rác thải.

P.S. Nó có thể làm cho một phần cụ thể của mã một chút ít bảo trì.

+0

GC sẽ đưa ra các biến phạm vi toàn cầu của tôi một cách nhất quán. – VectorVortec

8

Công cụ dành cho nhà phát triển Chrome có một tính năng rất hay để truy tìm phân bổ bộ nhớ. Nó được gọi là Memory Timeline. This article mô tả một số chi tiết. Tôi cho rằng đây là những gì bạn đang nói về tái "răng cưa"? Đây là hành vi bình thường đối với hầu hết các thời gian hoạt động của GC. Phân bổ tiến hành cho đến khi đạt đến ngưỡng sử dụng kích hoạt một bộ sưu tập. Thông thường có nhiều loại bộ sưu tập khác nhau ở các ngưỡng khác nhau.

Memory Timeline in Chrome

bộ sưu tập rác được đưa vào danh sách sự kiện gắn liền với dấu vết cùng với thời gian của họ. Trên máy tính xách tay cũ của tôi, các bộ sưu tập tạm thời đang xảy ra ở khoảng 4Mb và mất 30ms. Đây là 2 vòng lặp 60Hz của bạn. Nếu đây là một hoạt ảnh, bộ sưu tập 30ms có thể gây ra tình trạng lộn xộn. Bạn nên bắt đầu ở đây để xem điều gì đang xảy ra trong môi trường của bạn: nơi ngưỡng thu thập là bao lâu và bộ sưu tập của bạn đang sử dụng bao lâu. Điều này cung cấp cho bạn một điểm tham chiếu để đánh giá tối ưu hóa. Nhưng bạn có thể sẽ không làm tốt hơn để giảm tần suất của sự nói lắp bằng cách làm chậm tốc độ phân bổ, kéo dài khoảng thời gian giữa các bộ sưu tập.

Bước tiếp theo là sử dụng Cấu hình | Ghi lại tính năng Phân bổ Heap để tạo danh mục phân bổ theo loại bản ghi. Điều này sẽ nhanh chóng hiển thị các loại đối tượng nào đang tiêu thụ nhiều bộ nhớ nhất trong khoảng thời gian theo dõi, tương đương với tốc độ phân bổ. Tập trung vào các thứ tự này theo thứ tự giảm dần.

Các kỹ thuật này không phải là khoa học tên lửa. Tránh các đối tượng đóng hộp khi bạn có thể làm với một đối tượng không có hộp. Sử dụng các biến toàn cục để giữ và sử dụng lại các đối tượng được đóng hộp duy nhất thay vì phân bổ các đối tượng mới trong mỗi lần lặp. Nhóm các loại đối tượng chung trong danh sách miễn phí thay vì từ bỏ chúng. Kết quả chuỗi kết nối bộ nhớ cache có khả năng tái sử dụng trong các lần lặp lại trong tương lai. Tránh phân bổ chỉ để trả về các kết quả hàm bằng cách thiết lập các biến trong một phạm vi kèm theo. Bạn sẽ phải xem xét từng loại đối tượng trong bối cảnh riêng của nó để tìm ra chiến lược tốt nhất. Nếu bạn cần trợ giúp về chi tiết cụ thể, hãy đăng chỉnh sửa mô tả chi tiết về thử thách bạn đang xem.

Tôi khuyên bạn nên tránh biến đổi kiểu mã hóa thông thường của mình trong suốt một ứng dụng trong nỗ lực shotgun để tạo ra ít rác hơn. Điều này là cho cùng một lý do bạn không nên tối ưu hóa cho tốc độ sớm. Hầu hết nỗ lực của bạn cộng với nhiều sự phức tạp và tối tăm của mã sẽ là vô nghĩa.

+0

Đúng vậy, đó là ý của tôi bằng cưa răng. Tôi biết sẽ luôn luôn có một mô hình răng cưa của một số loại, nhưng mối quan tâm của tôi là với ứng dụng của tôi tần số sawtooth và 'vách đá' là khá cao. Điều thú vị là, các sự kiện GC không hiển thị trên dòng thời gian của tôi - các sự kiện duy nhất xuất hiện trong ngăn 'bản ghi' (ngăn giữa) là: 'khung hoạt ảnh yêu cầu',' khung hoạt ảnh được kích hoạt' và 'các lớp tổng hợp'. Tôi không có ý tưởng tại sao tôi không nhìn thấy 'GC Event' như bạn đang có (đây là phiên bản mới nhất của chrome, và cũng là canary). – UpTheCreek

+2

Tôi đã thử sử dụng profiler với 'phân bổ đống hồ sơ' nhưng cho đến nay vẫn chưa thấy nó rất hữu ích. Có lẽ điều này là bởi vì tôi không biết cách sử dụng nó đúng cách. Dường như có đầy đủ các tham chiếu không có ý nghĩa gì đối với tôi, chẳng hạn như '@ 342342' và' thông tin di chuyển mã'. – UpTheCreek

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