Giảm mức sử dụng bộ nhớ của các ứng dụng .NET?


108

Một số mẹo để giảm mức sử dụng bộ nhớ của các ứng dụng .NET là gì? Hãy xem xét chương trình C # đơn giản sau.

class Program
{
    static void Main(string[] args)
    {
        Console.ReadLine();
    }
}

Được biên dịch trong chế độ phát hành cho x64 và chạy bên ngoài Visual Studio, trình quản lý tác vụ báo cáo những điều sau:

Working Set:          9364k
Private Working Set:  2500k
Commit Size:         17480k

Sẽ tốt hơn một chút nếu nó được biên dịch chỉ cho x86 :

Working Set:          5888k
Private Working Set:  1280k
Commit Size:          7012k

Sau đó, tôi đã thử chương trình sau, chương trình này cũng tương tự nhưng cố gắng cắt bớt kích thước quy trình sau khi khởi chạy thời gian chạy:

class Program
{
    static void Main(string[] args)
    {
        minimizeMemory();
        Console.ReadLine();
    }

    private static void minimizeMemory()
    {
        GC.Collect(GC.MaxGeneration);
        GC.WaitForPendingFinalizers();
        SetProcessWorkingSetSize(Process.GetCurrentProcess().Handle,
            (UIntPtr) 0xFFFFFFFF, (UIntPtr)0xFFFFFFFF);
    }

    [DllImport("kernel32.dll")]
    [return: MarshalAs(UnmanagedType.Bool)]
    private static extern bool SetProcessWorkingSetSize(IntPtr process,
        UIntPtr minimumWorkingSetSize, UIntPtr maximumWorkingSetSize);
}

Kết quả trên Bản phát hành x86 bên ngoài Visual Studio:

Working Set:          2300k
Private Working Set:   964k
Commit Size:          8408k

Điều đó tốt hơn một chút, nhưng nó vẫn có vẻ quá mức đối với một chương trình đơn giản như vậy. Có thủ thuật nào để làm cho quy trình C # gọn gàng hơn một chút không? Tôi đang viết một chương trình được thiết kế để chạy trong nền hầu hết thời gian. Tôi đã thực hiện bất kỳ nội dung giao diện người dùng nào trong Miền ứng dụng riêng biệt , có nghĩa là nội dung giao diện người dùng có thể được tải xuống một cách an toàn, nhưng việc chiếm 10 MB khi nó chỉ ở chế độ nền có vẻ quá mức.

Tái bút Tại sao tôi lại quan tâm --- Người dùng (Power) có xu hướng lo lắng về những điều này. Ngay cả khi nó gần như không ảnh hưởng đến hiệu suất, những người dùng bán hiểu biết về công nghệ (đối tượng mục tiêu của tôi) có xu hướng gặp rắc rối về việc sử dụng bộ nhớ ứng dụng nền. Ngay cả khi tôi phát hoảng khi thấy Adobe Updater chiếm 11 MB bộ nhớ và cảm thấy được xoa dịu bởi cảm giác êm dịu của Foobar2000, có thể chiếm dưới 6 MB ngay cả khi chơi. Tôi biết trong các hệ điều hành hiện đại, thứ này thực sự không quan trọng lắm về mặt kỹ thuật, nhưng điều đó không có nghĩa là nó không ảnh hưởng đến nhận thức.


14
Tại sao bạn sẽ quan tâm? Nhóm làm việc riêng là khá thấp. Hệ điều hành hiện đại sẽ trang ra đĩa nếu bộ nhớ không cần thiết. Đó là năm 2009. Trừ khi bạn đang xây dựng nội dung trên hệ thống nhúng, bạn không nên quan tâm đến 10MB.
Mehrdad Afshari

8
Ngừng sử dụng .NET và bạn có thể có các chương trình nhỏ. Để tải .NET framework, cần phải tải nhiều DLL lớn vào bộ nhớ.
Adam Sills

2
Giá bộ nhớ giảm theo cấp số nhân (vâng, bạn có thể đặt mua hệ thống máy tính gia đình Dell với RAM 24 GB ngay bây giờ). Trừ khi ứng dụng của bạn đang sử dụng> 500MB tối ưu hóa là không cần thiết.
Alex

28
@LeakyCode Tôi thực sự ghét việc các lập trình viên hiện đại nghĩ theo cách này, bạn nên quan tâm đến việc sử dụng bộ nhớ của ứng dụng của mình. Tôi có thể nói rằng hầu hết các ứng dụng hiện đại, được viết chủ yếu bằng java hoặc c # là khá kém hiệu quả khi nói đến quản lý tài nguyên, và nhờ đó vào năm 2014, chúng tôi có thể chạy nhiều ứng dụng như hồi năm 1998 trên win95 và RAM 64mb. ... chỉ 1 phiên bản trình duyệt bây giờ ăn 2gb ram và IDE đơn giản khoảng 1gb. Ram tuy rẻ nhưng không có nghĩa là bạn nên lãng phí.
Petr

6
@Petr Bạn nên quan tâm đến quản lý tài nguyên. Thời gian của lập trình viên cũng là một nguồn lực. Vui lòng không tập trung quá mức gây lãng phí 10MB đến 2GB.
Mehrdad Afshari

Câu trả lời:


33
  1. Bạn có thể muốn xem câu hỏi Stack Overflow . Dấu chân bộ nhớ .NET EXE .
  2. Bài đăng trên blog MSDN Working set! = Bộ nhớ thực tế là tất cả về việc làm sáng tỏ bộ làm việc, bộ nhớ xử lý và cách thực hiện các phép tính chính xác về tổng mức tiêu thụ RAM trong bộ nhớ của bạn.

Tôi sẽ không nói rằng bạn nên bỏ qua dấu vết bộ nhớ của ứng dụng của bạn - rõ ràng, nhỏ hơn và hiệu quả hơn có xu hướng được mong muốn. Tuy nhiên, bạn nên cân nhắc nhu cầu thực tế của mình là gì.

Nếu bạn đang viết các ứng dụng khách Windows Forms và WPF tiêu chuẩn được thiết kế để chạy trên PC của một cá nhân và có khả năng là ứng dụng chính mà người dùng vận hành, bạn có thể tránh xa việc phân bổ bộ nhớ thiếu cân nhắc hơn. (Miễn là tất cả đều được phân bổ.)

Tuy nhiên, để giải quyết một số người ở đây nói rằng đừng lo lắng về điều đó: Nếu bạn đang viết một ứng dụng Windows Forms sẽ chạy trong môi trường dịch vụ đầu cuối, trên một máy chủ được chia sẻ có thể được sử dụng bởi 10, 20 người dùng trở lên, thì có , bạn hoàn toàn phải xem xét việc sử dụng bộ nhớ. Và bạn sẽ cần phải cảnh giác. Cách tốt nhất để giải quyết vấn đề này là thiết kế cấu trúc dữ liệu tốt và thực hiện theo các phương pháp hay nhất về thời điểm và những gì bạn phân bổ.


45

Các ứng dụng .NET sẽ có dấu ấn lớn hơn so với các ứng dụng gốc do thực tế là cả hai đều phải tải thời gian chạy và ứng dụng trong quá trình này. Nếu bạn muốn thứ gì đó thực sự gọn gàng, .NET có thể không phải là lựa chọn tốt nhất.

Tuy nhiên, hãy nhớ rằng nếu ứng dụng của bạn chủ yếu ở chế độ ngủ, các trang bộ nhớ cần thiết sẽ được hoán đổi khỏi bộ nhớ và do đó không thực sự là gánh nặng cho hệ thống trong hầu hết thời gian.

Nếu bạn muốn giữ cho dấu chân nhỏ, bạn sẽ phải nghĩ đến việc sử dụng bộ nhớ. Dưới đây là một số ý tưởng:

  • Giảm số lượng đối tượng và đảm bảo không giữ bất kỳ đối tượng nào lâu hơn yêu cầu.
  • Hãy lưu ý List<T>và các loại tương tự tăng gấp đôi công suất khi cần thiết vì chúng có thể dẫn đến lãng phí tới 50%.
  • Bạn có thể cân nhắc sử dụng các loại giá trị thay vì các loại tham chiếu để buộc nhiều bộ nhớ hơn trên ngăn xếp, nhưng hãy nhớ rằng không gian ngăn xếp mặc định chỉ là 1 MB.
  • Tránh các đối tượng có kích thước lớn hơn 85000 byte, vì chúng sẽ chuyển đến LOH mà không được nén chặt và do đó có thể dễ dàng bị phân mảnh.

Đó có lẽ không phải là một danh sách đầy đủ, mà chỉ là một vài ý tưởng.


IOW, các loại kỹ thuật giống nhau giúp giảm kích thước mã gốc có hoạt động trong .NET không?
Robert Fraser

Tôi đoán có một số trùng lặp, nhưng với mã gốc, bạn có nhiều lựa chọn hơn khi sử dụng bộ nhớ.
Brian Rasmussen

17

Một điều bạn cần xem xét trong trường hợp này là chi phí bộ nhớ của CLR. CLR được tải cho mọi quy trình .Net và do đó các yếu tố được xem xét trong bộ nhớ. Đối với một chương trình đơn giản / nhỏ như vậy, chi phí của CLR sẽ chiếm ưu thế trong bộ nhớ của bạn.

Sẽ có nhiều hướng dẫn hơn nếu xây dựng một ứng dụng thực tế và xem chi phí của nó so với chi phí của chương trình cơ sở này.


7

Không có đề xuất cụ thể nào, nhưng bạn có thể xem qua Hồ sơ CLR (tải xuống miễn phí từ Microsoft).
Khi bạn đã cài đặt xong, hãy xem trang hướng dẫn này .

Từ cách thực hiện:

Cách thực hiện này chỉ cho bạn cách sử dụng công cụ Hồ sơ CLR để điều tra hồ sơ cấp phát bộ nhớ của ứng dụng của bạn. Bạn có thể sử dụng CLR Profiler để xác định mã gây ra sự cố bộ nhớ, chẳng hạn như rò rỉ bộ nhớ và thu gom rác quá mức hoặc không hiệu quả.


7

Có thể muốn xem việc sử dụng bộ nhớ của một ứng dụng "thực".

Tương tự như Java, có một số chi phí cố định cho thời gian chạy bất kể kích thước chương trình là bao nhiêu, nhưng mức tiêu thụ bộ nhớ sẽ hợp lý hơn nhiều sau thời điểm đó.


4

Vẫn có những cách để giảm tập hợp làm việc riêng của chương trình đơn giản này:

  1. NGEN ứng dụng của bạn. Điều này loại bỏ chi phí biên dịch JIT khỏi quy trình của bạn.

  2. Đào tạo ứng dụng của bạn bằng cách sử dụng MPGO để giảm mức sử dụng bộ nhớ và sau đó NGEN nó.


2

Có nhiều cách để giảm dấu chân của bạn.

Một điều bạn sẽ luôn phải tuân theo trong .NET là kích thước của hình ảnh gốc của mã IL của bạn rất lớn

Và mã này không thể được chia sẻ hoàn toàn giữa các phiên bản ứng dụng. Ngay cả khi lắp ráp NGEN'ed không hoàn toàn tĩnh, chúng vẫn có một số bộ phận nhỏ cần JITting.

Mọi người cũng có xu hướng viết mã chặn bộ nhớ lâu hơn mức cần thiết.

Một ví dụ thường thấy: Sử dụng Datareader, tải nội dung vào DataTable chỉ để ghi nó vào tệp XML. Bạn có thể dễ dàng chạy vào một OutOfMemoryException. OTOH, bạn có thể sử dụng XmlTextWriter và cuộn qua Datareader, tạo ra các XmlNodes khi bạn cuộn qua con trỏ cơ sở dữ liệu. Bằng cách đó, bạn chỉ có bản ghi cơ sở dữ liệu hiện tại và đầu ra XML của nó trong bộ nhớ. Cái nào sẽ không bao giờ (hoặc không có khả năng) có được lượng rác thu gom cao hơn và do đó có thể được tái sử dụng.

Điều tương tự cũng áp dụng cho việc lấy danh sách một số trường hợp, thực hiện một số thứ (sinh ra hàng nghìn trường hợp mới, có thể vẫn được tham chiếu ở đâu đó) và mặc dù bạn không cần chúng sau đó, bạn vẫn tham chiếu mọi thứ cho đến sau phần mở đầu. Nhập rõ ràng danh sách đầu vào của bạn và các sản phẩm phụ tạm thời của bạn có nghĩa là, bộ nhớ này có thể được sử dụng lại ngay cả trước khi bạn thoát khỏi vòng lặp của mình.

C # có một tính năng tuyệt vời được gọi là trình vòng lặp. Chúng cho phép bạn truyền trực tuyến các đối tượng bằng cách cuộn qua đầu vào của bạn và chỉ giữ bản sao hiện tại cho đến khi bạn nhận được bản tiếp theo. Ngay cả khi sử dụng LINQ, bạn vẫn không cần phải giữ tất cả xung quanh chỉ vì bạn muốn nó được lọc.


1

Giải quyết câu hỏi chung trong tiêu đề chứ không phải câu hỏi cụ thể:

Nếu bạn đang sử dụng thành phần COM trả về nhiều dữ liệu (giả sử mảng 2xN lớn gấp đôi) và chỉ cần một phần nhỏ thì người ta có thể viết thành phần COM bao bọc để ẩn bộ nhớ khỏi .NET và chỉ trả lại dữ liệu cần thiết.

Đó là những gì tôi đã làm trong ứng dụng chính của mình và nó đã cải thiện đáng kể mức tiêu thụ bộ nhớ.


0

Tôi nhận thấy rằng việc sử dụng API SetProcessWorkingSetSize hoặc EmptyWorkingSet để buộc các trang bộ nhớ vào đĩa định kỳ trong một quá trình chạy dài có thể khiến tất cả bộ nhớ vật lý có sẵn trên máy biến mất một cách hiệu quả cho đến khi máy được khởi động lại. Chúng tôi đã tải .NET DLL vào một quy trình gốc sẽ sử dụng API EmptyWorkingSet (một giải pháp thay thế cho việc sử dụng SetProcessWorkingSetSize) để giảm tập hợp làm việc sau khi thực hiện tác vụ tốn nhiều bộ nhớ. Tôi nhận thấy rằng sau bất kỳ nơi nào từ 1 ngày đến một tuần, máy sẽ hiển thị mức sử dụng bộ nhớ vật lý 99% trong Trình quản lý tác vụ trong khi không có quy trình nào được hiển thị đang sử dụng bất kỳ mức sử dụng bộ nhớ đáng kể nào. Ngay sau đó, máy sẽ không phản hồi, yêu cầu khởi động lại cứng. Các máy cho biết có hơn 2 chục máy chủ Windows Server 2008 R2 và 2012 R2 chạy trên cả phần cứng vật lý và ảo.

Có lẽ việc tải mã .NET vào một quy trình gốc có liên quan gì đến nó, nhưng bạn có thể tự chịu rủi ro khi sử dụng EmptyWorkingSet (hoặc SetProcessWorkingSetSize). Có lẽ chỉ sử dụng nó một lần sau khi khởi chạy ứng dụng lần đầu. Tôi đã quyết định tắt mã và để Trình thu gom rác tự quản lý việc sử dụng bộ nhớ.

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.