การลดการใช้หน่วยความจำของแอพพลิเคชั่น. NET?


108

มีเคล็ดลับอะไรบ้างในการลดการใช้หน่วยความจำของแอปพลิเคชัน. NET พิจารณาโปรแกรม C # อย่างง่ายต่อไปนี้

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

คอมไพล์ในโหมดรีลีสสำหรับx64และรันนอก Visual Studio ตัวจัดการงานรายงานสิ่งต่อไปนี้:

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

จะดีกว่าเล็กน้อยถ้าคอมไพล์สำหรับx86 :

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

จากนั้นฉันลองใช้โปรแกรมต่อไปนี้ซึ่งทำเหมือนกัน แต่พยายามตัดขนาดของกระบวนการหลังจากการเริ่มต้นรันไทม์:

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);
}

ผลลัพธ์บนx86 Releaseภายนอก Visual Studio:

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

ซึ่งดีกว่าเล็กน้อย แต่ก็ยังดูเหมือนมากเกินไปสำหรับโปรแกรมง่ายๆเช่นนี้ มีเทคนิคใดบ้างที่จะทำให้กระบวนการ C # มีขนาดเล็กลงหรือไม่? ฉันกำลังเขียนโปรแกรมที่ออกแบบมาให้ทำงานอยู่เบื้องหลังเกือบตลอดเวลา ฉันกำลังทำสิ่งต่างๆของอินเทอร์เฟซผู้ใช้ในApplication Domainแยกต่างหากซึ่งหมายความว่าสามารถยกเลิกการโหลดเนื้อหาส่วนติดต่อผู้ใช้ได้อย่างปลอดภัย แต่การใช้พื้นที่ 10 MB เมื่อนั่งอยู่เบื้องหลังดูเหมือนจะมากเกินไป

ปล.ทำไมฉันถึงสนใจผู้ใช้ --- (Power) มักจะกังวลกับสิ่งเหล่านี้ แม้ว่าจะแทบไม่มีผลกระทบต่อประสิทธิภาพ แต่ผู้ใช้กึ่งเทคโนโลยี (กลุ่มเป้าหมายของฉัน) มักจะรู้สึกแย่กับการใช้หน่วยความจำของแอปพลิเคชันพื้นหลัง แม้ว่าฉันจะรู้สึกประหลาดใจเมื่อเห็น Adobe Updater ใช้หน่วยความจำ 11 MB และรู้สึกผ่อนคลายด้วยสัมผัสที่เงียบสงบของ Foobar2000 ซึ่งอาจใช้เวลาน้อยกว่า 6 MB แม้ในขณะที่เล่น ฉันรู้ในระบบปฏิบัติการสมัยใหม่สิ่งนี้ไม่ได้มีความสำคัญมากนักในทางเทคนิค แต่ก็ไม่ได้หมายความว่ามันจะไม่มีผลต่อการรับรู้


14
จะแคร์ทำไม ชุดทำงานส่วนตัวค่อนข้างต่ำ ระบบปฏิบัติการสมัยใหม่จะเลื่อนไปที่ดิสก์หากไม่จำเป็นต้องใช้หน่วยความจำ มันเป็นปี 2009 เว้นแต่คุณจะสร้างสิ่งต่างๆบนระบบฝังตัวคุณไม่ควรสนใจ 10MB
Mehrdad Afshari

8
หยุดใช้. NET และคุณสามารถมีโปรแกรมเล็ก ๆ ในการโหลด. NET Framework จำเป็นต้องโหลด DLL ขนาดใหญ่จำนวนมากในหน่วยความจำ
Adam Sills

2
ราคาหน่วยความจำลดลงอย่างทวีคูณ (ใช่คุณสามารถสั่งซื้อระบบคอมพิวเตอร์ที่บ้านของ Dell ที่มี RAM 24 GB ได้แล้ว) เว้นแต่แอปพลิเคชันของคุณกำลังใช้การเพิ่มประสิทธิภาพ> 500MB ก็ไม่จำเป็น
Alex

28
@LeakyCode ฉันเกลียดจริงๆที่โปรแกรมเมอร์ยุคใหม่คิดแบบนี้คุณควรใส่ใจเกี่ยวกับการใช้หน่วยความจำของแอปพลิเคชันของคุณ ฉันสามารถพูดได้ว่าแอปพลิเคชั่นสมัยใหม่ส่วนใหญ่เขียนด้วย java หรือ c # นั้นค่อนข้างไม่มีประสิทธิภาพเมื่อพูดถึงการจัดการทรัพยากรและด้วยเหตุนี้ในปี 2014 เราจึงสามารถเรียกใช้แอปพลิเคชั่นได้มากที่สุดเท่าที่จะทำได้ในปี 1998 บน win95 และ 64mb ของ ram ... เบราว์เซอร์เพียง 1 อินสแตนซ์ตอนนี้กิน ram 2gb และ IDE ธรรมดาประมาณ 1gb รามมีราคาถูก แต่ไม่ได้หมายความว่าคุณควรเสียมัน
Petr

6
@Petr คุณควรใส่ใจเรื่องการจัดการทรัพยากร เวลาโปรแกรมเมอร์ก็เป็นทรัพยากรเช่นกัน โปรดอย่าเสียค่าใช้จ่าย 10MB ถึง 2GB มากเกินไป
Mehrdad Afshari

คำตอบ:


33
  1. คุณอาจต้องการที่จะตรวจสอบกองมากเกินคำถาม.NET EXE รอยความทรงจำ
  2. โพสต์บล็อก MSDN Working set! = รอยเท้าหน่วยความจำจริงเป็นข้อมูลเกี่ยวกับการทำให้เข้าใจผิดชุดการทำงานหน่วยความจำประมวลผลและวิธีการคำนวณที่ถูกต้องเกี่ยวกับการใช้ RAM ทั้งหมดใน RAM ของคุณ

ฉันจะไม่บอกว่าคุณควรเพิกเฉยต่อหน่วยความจำของแอปพลิเคชันของคุณ - เห็นได้ชัดว่ามีขนาดเล็กและมีประสิทธิภาพมากขึ้นมีแนวโน้มที่จะเป็นที่ต้องการ อย่างไรก็ตามคุณควรพิจารณาว่าความต้องการที่แท้จริงของคุณคืออะไร

หากคุณกำลังเขียนแอปพลิเคชันไคลเอนต์ Windows Forms และ WPF มาตรฐานซึ่งถูกกำหนดให้ทำงานบนพีซีของแต่ละบุคคลและมีแนวโน้มว่าจะเป็นแอปพลิเคชันหลักที่ผู้ใช้ใช้งานคุณสามารถหลีกเลี่ยงการจัดสรรหน่วยความจำได้มากขึ้น (ตราบเท่าที่ทุกอย่างถูกยกเลิกการจัดสรร)

อย่างไรก็ตามเพื่อพูดคุยกับคนบางคนที่นี่ที่บอกว่าไม่ต้องกังวล: หากคุณกำลังเขียนแอปพลิเคชัน Windows Forms ซึ่งจะทำงานในสภาพแวดล้อมบริการเทอร์มินัลบนเซิร์ฟเวอร์ที่ใช้ร่วมกันอาจใช้โดยผู้ใช้ 10, 20 คนขึ้นไปใช่ คุณต้องพิจารณาการใช้หน่วยความจำอย่างแน่นอน และคุณจะต้องระมัดระวัง วิธีที่ดีที่สุดในการแก้ไขปัญหานี้คือการออกแบบโครงสร้างข้อมูลที่ดีและปฏิบัติตามแนวทางปฏิบัติที่ดีที่สุดเกี่ยวกับเวลาและสิ่งที่คุณจัดสรร


45

แอปพลิเคชัน. NET จะมีขนาดใหญ่กว่าเมื่อเทียบกับแอปพลิเคชันเนทีฟเนื่องจากทั้งสองต้องโหลดรันไทม์และแอปพลิเคชันในกระบวนการ หากคุณต้องการสิ่งที่เป็นระเบียบเรียบร้อยจริงๆ. NET อาจไม่ใช่ตัวเลือกที่ดีที่สุด

อย่างไรก็ตามโปรดทราบว่าหากแอปพลิเคชันของคุณอยู่ในโหมดสลีปเป็นส่วนใหญ่เพจหน่วยความจำที่จำเป็นจะถูกสลับออกจากหน่วยความจำดังนั้นจึงไม่เป็นภาระของระบบโดยส่วนใหญ่

หากคุณต้องการให้มีขนาดเล็กคุณจะต้องคิดถึงการใช้หน่วยความจำ นี่คือแนวคิดสองสามข้อ:

  • ลดจำนวนออบเจ็กต์และอย่ายึดอินสแตนซ์นานเกินกำหนด
  • ระวัง List<T>และประเภทที่คล้ายกันที่ความจุสองเท่าเมื่อจำเป็นเพราะอาจทำให้เสียได้ถึง 50%
  • คุณสามารถพิจารณาใช้ประเภทค่ากับประเภทการอ้างอิงเพื่อบังคับหน่วยความจำบนสแต็กได้มากขึ้น แต่โปรดทราบว่าพื้นที่สแต็กเริ่มต้นคือ 1 MB
  • หลีกเลี่ยงวัตถุที่มีขนาดมากกว่า 85,000 ไบต์เนื่องจากจะไปที่ LOH ซึ่งไม่ได้รับการบดอัดจึงอาจแยกส่วนได้ง่าย

นั่นอาจไม่ใช่รายการที่ละเอียดถี่ถ้วนไม่ว่าจะด้วยวิธีใด ๆ แต่เป็นเพียงแนวคิดสองสามข้อ


IOW เทคนิคประเภทเดียวกับที่ใช้ลดขนาดโค้ดเนทีฟใน. NET หรือไม่
Robert Fraser

ฉันเดาว่ามีการทับซ้อนกัน แต่ด้วยรหัสเนทีฟคุณมีทางเลือกมากขึ้นเมื่อต้องใช้หน่วยความจำ
Brian Rasmussen

17

สิ่งหนึ่งที่คุณต้องพิจารณาในกรณีนี้คือต้นทุนหน่วยความจำของ CLR CLR ถูกโหลดสำหรับทุกกระบวนการ. Net และด้วยเหตุนี้ปัจจัยในการพิจารณาหน่วยความจำ สำหรับโปรแกรมที่เรียบง่าย / เล็กเช่นนี้ค่าใช้จ่ายของ CLR จะครอบงำหน่วยความจำของคุณ

การสร้างแอปพลิเคชันจริงจะเป็นประโยชน์มากขึ้นและดูค่าใช้จ่ายเมื่อเทียบกับต้นทุนของโปรแกรมพื้นฐานนี้


7

ไม่มีข้อเสนอแนะเฉพาะเจาะจง แต่คุณสามารถดูCLR Profiler (ดาวน์โหลดฟรีจาก Microsoft)
เมื่อคุณติดตั้งมันจะดูที่นี้หน้าวิธีการ

จากวิธีการ:

วิธีการนี้จะแสดงวิธีใช้เครื่องมือ CLR Profiler เพื่อตรวจสอบโปรไฟล์การจัดสรรหน่วยความจำของแอปพลิเคชันของคุณ คุณสามารถใช้ CLR Profiler เพื่อระบุรหัสที่ทำให้เกิดปัญหาหน่วยความจำเช่นการรั่วไหลของหน่วยความจำและการรวบรวมขยะมากเกินไปหรือไม่มีประสิทธิภาพ


7

อาจต้องการดูการใช้งานหน่วยความจำของแอปพลิเคชัน "จริง"

เช่นเดียวกับ Java มีค่าใช้จ่ายคงที่สำหรับรันไทม์โดยไม่คำนึงถึงขนาดของโปรแกรม แต่การใช้หน่วยความจำจะสมเหตุสมผลกว่ามากหลังจากนั้น


4

ยังมีวิธีลดชุดการทำงานส่วนตัวของโปรแกรมง่ายๆนี้:

  1. NGEN ใบสมัครของคุณ สิ่งนี้จะลบต้นทุนการรวบรวม JIT ออกจากกระบวนการของคุณ

  2. ฝึกแอปพลิเคชันของคุณโดยใช้ MPGO เพื่อลดการใช้หน่วยความจำแล้ว NGEN


2

มีหลายวิธีในการลดรอยเท้าของคุณ

สิ่งหนึ่งที่คุณจะต้องใช้ใน. NETคือขนาดของภาพเนทีฟของรหัส IL ของคุณนั้นใหญ่มาก

และไม่สามารถแชร์รหัสนี้ระหว่างอินสแตนซ์ของแอปพลิเคชันได้อย่างสมบูรณ์ แม้แต่ชุดประกอบNGEN'edก็ไม่ได้นิ่งสนิท แต่ก็ยังมีชิ้นส่วนเล็ก ๆ น้อย ๆ ที่ต้องใช้ JITting

ผู้คนมักจะเขียนโค้ดที่บล็อกหน่วยความจำนานเกินความจำเป็น

ตัวอย่างที่เห็นบ่อยๆ: การใช้ Datareader โหลดเนื้อหาลงใน DataTable เพียงเพื่อเขียนลงในไฟล์ XML คุณสามารถเรียกใช้ OutOfMemoryException ได้อย่างง่ายดาย OTOH คุณสามารถใช้ XmlTextWriter และเลื่อนดู Datareader โดยปล่อย XmlNodes เมื่อคุณเลื่อนดูเคอร์เซอร์ของฐานข้อมูล ด้วยวิธีนี้คุณจะมีระเบียนฐานข้อมูลปัจจุบันและเอาต์พุต XML ในหน่วยความจำเท่านั้น ซึ่งจะไม่ (หรือไม่น่าจะเป็นไปได้) ได้รับการรวบรวมขยะที่สูงขึ้นและสามารถนำกลับมาใช้ใหม่ได้

เช่นเดียวกับการรับรายการของบางอินสแตนซ์การทำบางสิ่งบางอย่าง (ซึ่งเกิดจากอินสแตนซ์ใหม่หลายพันรายการซึ่งอาจมีการอ้างอิงอยู่ที่ใดที่หนึ่ง) และแม้ว่าคุณจะไม่ต้องการมันในภายหลังคุณก็ยังคงอ้างอิงทุกอย่างจนกว่าจะมีการ foreach ลบรายการอินพุตและผลพลอยได้ชั่วคราวอย่างชัดเจนหน่วยความจำนี้สามารถใช้ซ้ำได้ก่อนที่คุณจะออกจากลูป

C # มีคุณสมบัติที่ยอดเยี่ยมที่เรียกว่าตัวทำซ้ำ อนุญาตให้คุณสตรีมวัตถุโดยการเลื่อนดูข้อมูลที่คุณป้อนและเก็บเฉพาะอินสแตนซ์ปัจจุบันจนกว่าคุณจะได้รับรายการถัดไป แม้จะใช้ LINQ คุณก็ยังไม่จำเป็นต้องเก็บข้อมูลทั้งหมดไว้เพียงเพราะคุณต้องการให้กรอง


1

ตอบคำถามทั่วไปในชื่อเรื่องไม่ใช่คำถามเฉพาะ:

หากคุณใช้คอมโพเนนต์ COM ที่ส่งคืนข้อมูลจำนวนมาก (เช่นอาร์เรย์ 2xN ขนาดใหญ่เป็นสองเท่า) และต้องการเศษส่วนเพียงเล็กน้อยก็สามารถเขียนคอมโพเนนต์ Wrapper COM ที่ซ่อนหน่วยความจำจาก. NET และส่งคืนเฉพาะข้อมูลที่เป็น จำเป็น

นั่นคือสิ่งที่ฉันทำในแอปพลิเคชันหลักของฉันและมันช่วยเพิ่มการใช้หน่วยความจำได้อย่างมาก


0

ฉันพบว่าการใช้ SetProcessWorkingSetSize หรือ EmptyWorkingSet API เพื่อบังคับให้เพจหน่วยความจำลงดิสก์เป็นระยะ ๆ ในกระบวนการทำงานที่ยาวนานอาจส่งผลให้หน่วยความจำกายภาพที่มีอยู่ทั้งหมดบนเครื่องหายไปอย่างมีประสิทธิภาพจนกว่าจะรีบูตเครื่อง เรามี. NET DLL ที่โหลดไว้ในกระบวนการดั้งเดิมซึ่งจะใช้ EmptyWorkingSet API (ทางเลือกอื่นนอกเหนือจากการใช้ SetProcessWorkingSetSize) เพื่อลดชุดการทำงานหลังจากดำเนินงานที่ต้องใช้หน่วยความจำมาก ฉันพบว่าหลังจากที่ใดก็ได้ระหว่าง 1 วันถึงหนึ่งสัปดาห์เครื่องจะแสดงการใช้หน่วยความจำกายภาพ 99% ในตัวจัดการงานในขณะที่ไม่มีกระบวนการใดที่แสดงว่าใช้หน่วยความจำที่สำคัญใด ๆ ไม่นานหลังจากนั้นเครื่องจะไม่ตอบสนองทำให้ต้องรีบูตเครื่องอย่างหนัก เครื่องดังกล่าวมีเซิร์ฟเวอร์ Windows Server 2008 R2 และ 2012 R2 มากกว่า 2 โหลที่ทำงานบนฮาร์ดแวร์จริงและเสมือน

บางทีการโหลดโค้ด. NET ลงในโปรเซสเนทีฟอาจมีบางอย่างที่เกี่ยวข้อง แต่ใช้ EmptyWorkingSet (หรือ SetProcessWorkingSetSize) โดยยอมรับความเสี่ยงเอง อาจใช้เพียงครั้งเดียวหลังจากเปิดตัวแอปพลิเคชันของคุณครั้งแรก ฉันได้ตัดสินใจที่จะปิดการใช้งานรหัสและปล่อยให้ Garbage Collector จัดการการใช้หน่วยความจำด้วยตัวเอง

โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.