2014-12-09 20 views
5

Chúng tôi có một Dịch vụ Web sử dụng WebApi 2, .NET 4.5 trên Máy chủ 2012. Chúng tôi đã thấy độ trễ không thường xuyên tăng 10-30ms mà không có lý do chính đáng. Chúng tôi đã có thể theo dõi đoạn mã có vấn đề với LOH và GC.Sử dụng rộng rãi LOH gây ra vấn đề hiệu suất đáng kể

Có một số văn bản mà chúng tôi chuyển đổi sang biểu diễn byte UTF8 của nó (thực sự, thư viện tuần tự mà chúng tôi sử dụng thực hiện điều đó). Miễn là văn bản ngắn hơn 85000 byte, độ trễ ổn định và ngắn: trung bình ~ 0,2 mili giây và ở mức 99%. Ngay sau khi vượt qua ranh giới 85000, độ trễ trung bình tăng lên ~ 1ms trong khi 99% nhảy tới 16-20ms. Profiler cho thấy hầu hết thời gian được sử dụng trong GC. Để chắc chắn, nếu tôi đặt GC.Collect giữa các lần lặp lại, độ trễ được đo sẽ quay lại 0,2 mili giây.

Tôi đã hai câu hỏi:

  1. đâu trễ đến từ đâu? Theo như tôi hiểu, LOH không được nén chặt. SOH đang được nén chặt, nhưng không hiển thị độ trễ.
  2. Có cách nào thực tế để giải quyết vấn đề này không? Lưu ý rằng tôi không thể kiểm soát kích thước của dữ liệu và làm cho dữ liệu nhỏ hơn.

-

public void PerfTestMeasureGetBytes() 
{ 
    var text = File.ReadAllText(@"C:\Temp\ContactsModelsInferences.txt"); 
    var smallText = text.Substring(0, 85000 + 100); 
    int count = 1000; 
    List<double> latencies = new List<double>(count); 
    for (int i = 0; i < count; i++) 
    { 
     Stopwatch sw = new Stopwatch(); 
     sw.Start(); 
     var bytes = Encoding.UTF8.GetBytes(smallText); 
     sw.Stop(); 
     latencies.Add(sw.Elapsed.TotalMilliseconds); 

     //GC.Collect(2, GCCollectionMode.Default, true); 
    } 

    latencies.Sort(); 
    Console.WriteLine("Average: {0}", latencies.Average()); 
    Console.WriteLine("99%: {0}", latencies[(int)(latencies.Count * 0.99)]); 
} 
+0

Điều này có giúp http://stackoverflow.com/questions/686950/large-object-heap-fragmentation – Sid

+0

không thực sự, điều này nói về phân mảnh và vấn đề của tôi là với độ trễ. –

Trả lời

5

Các vấn đề hiệu suất thường đến từ hai lĩnh vực: phân bổ và phân mảnh.

Allocation

Bộ thực thi đảm bảo bộ nhớ sạch để dành chu kỳ làm sạch nó. Khi bạn phân bổ một đối tượng lớn, đó là rất nhiều bộ nhớ và bắt đầu thêm mili giây vào một phân bổ đơn (khi cho phép trung thực, phân bổ đơn giản trong .NET thực sự rất nhanh, vì vậy chúng tôi thường không bao giờ quan tâm đến điều này).

Phân mảnh xảy ra khi các đối tượng LOH được cấp phát sau đó được xác nhận lại. Cho đến gần đây, GC không thể tổ chức lại bộ nhớ để loại bỏ các "khoảng trống" đối tượng cũ, và do đó chỉ có thể phù hợp với đối tượng tiếp theo trong khoảng trống đó nếu nó có cùng kích thước hoặc nhỏ hơn. Gần đây, GC đã được cung cấp khả năng compact LOH, loại bỏ vấn đề này, nhưng chi phí thời gian trong quá trình nén chặt.

My đoán trong trường hợp của bạn là bạn đang gặp phải cả hai vấn đề và kích hoạt chạy GC, nhưng nó phụ thuộc vào tần suất mã của bạn đang cố phân bổ các mục trong LOH. Nếu bạn đang thực hiện rất nhiều phân bổ, hãy thử tuyến đường gộp đối tượng. Nếu bạn không thể kiểm soát một nhóm hiệu quả (thời gian tồn tại đối tượng lumpy hoặc các mẫu sử dụng khác nhau), hãy thử chunking dữ liệu bạn đang làm việc chống lại để tránh nó hoàn toàn.


Lựa chọn của bạn

Tôi đã gặp hai cách tiếp cận với LOH:

  • Tránh nó.
  • Sử dụng nó, nhưng nhận ra bạn đang sử dụng và quản lý nó một cách rõ ràng.

Tránh nó

này liên quan đến chunking đối tượng lớn của mình (thường là một mảng của một số loại) vào, tốt, khối mà mỗi mùa thu dưới hàng rào LOH. Chúng tôi làm điều này khi serialising dòng đối tượng lớn. Hoạt động tốt, nhưng việc triển khai sẽ cụ thể đối với môi trường của bạn nên tôi do dự để cung cấp ví dụ được mã hóa.

Sử dụng nó

Một cách đơn giản để giải quyết cả việc phân bổ và phân mảnh là đối tượng tồn tại lâu dài. Rõ ràng làm cho một mảng trống (hoặc mảng) của một kích thước lớn để chứa đối tượng lớn của bạn, và không loại bỏ nó (hoặc họ). Để nó xung quanh và tái sử dụng nó như một hồ bơi đối tượng. Bạn trả tiền cho phân bổ này, nhưng có thể thực hiện việc này hoặc sử dụng lần đầu hoặc trong thời gian không sử dụng ứng dụng, nhưng bạn trả ít hơn cho phân bổ lại (vì bạn không phân bổ lại) và giảm các vấn đề phân mảnh vì bạn không liên tục yêu cầu phân bổ công cụ và bạn không khai hoang các mục (gây ra khoảng trống ở địa điểm đầu tiên).

Điều đó nói rằng, nhà nửa chừng có thể theo thứ tự. Đặt trước một phần bộ nhớ cho một nhóm đối tượng. Được thực hiện sớm, các phân bổ này phải được tiếp giáp trong bộ nhớ, do đó bạn sẽ không nhận được bất kỳ khoảng trống nào và để phần đuôi của bộ nhớ khả dụng cho các mục không được kiểm soát. Do hãy cẩn thận mặc dù điều này rõ ràng là có tác động đến bộ làm việc của ứng dụng của bạn - một nhóm đối tượng chiếm không gian bất kể nó đang được sử dụng hay không.


Tài

Các LOH được bao phủ rất nhiều trong trang web, nhưng chú ý đến ngày của tài nguyên. Trong các phiên bản .NET mới nhất, LOH đã nhận được một số tình yêu và đã được cải thiện. Điều đó nói rằng, nếu bạn đang ở trên một phiên bản cũ hơn tôi nghĩ rằng các nguồn tài nguyên trên mạng là khá chính xác như LOH không bao giờ thực sự nhận được bất kỳ bản cập nhật nghiêm trọng trong một thời gian dài giữa khi thành lập và .NET 4.5 (ish).

Ví dụ, có bài viết này từ năm 2008 http://msdn.microsoft.com/en-us/magazine/cc534993.aspx

Và một bản tóm tắt của những cải tiến trong .NET 4.5: http://blogs.msdn.com/b/dotnet/archive/2011/10/04/large-object-heap-improvements-in-net-4-5.aspx

+0

@downvoter Tôi muốn biết lý do cho downvote. Sự hiểu biết của tôi về LOH không hoàn chỉnh nên tôi muốn lấp đầy khoảng trống. Hãy đóng góp và giúp tôi cải thiện câu trả lời. Tôi đã viết lại câu trả lời của tôi rất nhiều kể từ khi downvote, vì vậy hy vọng tôi đã sửa chữa vấn đề, nhưng tôi sẽ không biết. –

+0

Cảm ơn Adam, vấn đề với cả hai giải pháp được đề xuất là chúng tôi đang xử lý các đối tượng được tạo bởi bên thứ ba. Một trong những lĩnh vực là một chuỗi mà chỉ xảy ra được trên ngưỡng. Ngay sau khi điều này xảy ra, toàn bộ hệ thống sẽ chậm lại. Chuỗi đi từ dạng tuần tự thành chuỗi, được sao chép một vài lần vì logic nghiệp vụ riêng. Vì tôi không kiểm soát đối tượng, tôi không thể thay thế chuỗi bằng cái gì khác, điều này sẽ thân thiện hơn với LOH. –

+0

Tôi sẽ cho nó thêm thời gian và sẽ chấp nhận câu trả lời của bạn, nếu tôi không tìm thấy/nhận được câu trả lời hay hơn. –

3

Ngoài những điều sau đây, chắc chắn mà bạn đang sử dụng server garbage collector . Điều đó không ảnh hưởng đến cách LOH được sử dụng, nhưng kinh nghiệm của tôi là nó làm giảm đáng kể lượng thời gian dành cho GC.

Công việc tốt nhất xung quanh tôi được tìm thấy để tránh các vấn đề heap đối tượng lớn là tạo bộ đệm liên tục và sử dụng lại nó. Vì vậy, thay vì phân bổ một mảng byte mới với mỗi cuộc gọi đến Encoding.GetBytes, chuyển mảng byte vào phương thức.

Trong trường hợp này, hãy sử dụng GetBytes overload để lấy mảng byte. Phân bổ một mảng đủ lớn để giữ các byte cho chuỗi dài nhất được mong đợi của bạn và giữ lại nó. Ví dụ:

// allocate buffer at class scope 
private byte[] _theBuffer = new byte[1024*1024]; 

public void PerfTestMeasureGetBytes() 
{ 
    // ... 
    for (...) 
    { 
     var sw = Stopwatch.StartNew(); 
     var numberOfBytes = Encoding.UTF8.GetBytes(smallText, 0, smallText.Length, _theBuffer, 0); 
     sw.Stop(); 
     // ... 
    } 

Vấn đề duy nhất ở đây là bạn phải đảm bảo bộ đệm đủ lớn để giữ chuỗi lớn nhất. Những gì tôi đã làm trong quá khứ là phân bổ bộ đệm với kích thước lớn nhất mà tôi mong đợi, nhưng sau đó kiểm tra để đảm bảo bộ đệm đủ lớn bất cứ khi nào tôi sử dụng nó. Nếu nó không đủ lớn, hãy phân bổ lại nó. Làm thế nào bạn làm điều đó phụ thuộc vào mức độ nghiêm ngặt mà bạn muốn. Khi làm việc với chủ yếu là văn bản Tây Âu, tôi chỉ cần tăng gấp đôi chiều dài chuỗi.Ví dụ:

string textToConvert = ... 
if (_theBuffer.Length < 2*textToConvert.Length) 
{ 
    // reallocate the buffer 
    _theBuffer = new byte[2*textToConvert.Length]; 
} 

Một cách khác để làm điều đó là chỉ cần thử GetString và phân bổ lại khi không thành công. Sau đó thử lại. Ví dụ:

while (!good) 
{ 
    try 
    { 
     numberOfBytes = Encoding.UTF8.GetString(theString, ....); 
     good = true; 
    } 
    catch (ArgumentException) 
    { 
     // buffer isn't big enough. Find out how much I really need 
     var bytesNeeded = Encoding.UTF8.GetByteCount(theString); 
     // and reallocate the buffer 
     _theBuffer = new byte[bytesNeeded]; 
    } 
} 

Nếu bạn đặt kích thước ban đầu của bộ đệm đủ lớn để chứa chuỗi lớn nhất bạn mong đợi, thì có thể bạn sẽ không nhận được ngoại lệ đó thường xuyên. Điều đó có nghĩa là số lần bạn phải tái phân bổ bộ đệm sẽ rất nhỏ. Bạn có thể, tất nhiên, thêm một số đệm vào bytesNeeded để bạn phân bổ nhiều hơn, trong trường hợp bạn có một số ngoại lệ khác.

+1

Cần lưu ý rằng khi sử dụng bộ đệm, bạn cần phải bỏ qua phần đệm không cần thiết cho đối tượng, vì vậy mã của bạn sẽ lặp lại hoặc bất cứ điều gì cần chú ý đến kích thước của dữ liệu thực tế bên trong và không thể dựa vào 'Array.Length'. –

+0

@AdamHouldsworth: Đúng. Nhưng đó là giá trị trả về từ 'GetBytes'. . . –

+0

Thật vậy, tôi không chỉ trích giải pháp. Tôi đã làm điều này trong quá khứ, nhưng nó rất dễ dàng để quên rằng khi bạn đang làm việc theo giải pháp đó mảng nhỏ của bạn có thể được ngồi bên trong một lớn hơn nhiều. Kỹ thuật này cũng có thể được sử dụng để tránh phân bổ nhiều mảng nhỏ hoặc bật hoặc tắt LOH. –

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