.NET აპლიკაციის მეხსიერების მოხმარება ძირითადად garbage collector-ის გადაწყვეტილებაა და არა შენი კოდისა. ASP.NET Core აპლიკაციები ნაგულისხმევად Server GC-ზე მუშაობს, რომელიც მეხსიერებას გამტარუნარიანობაზე ცვლის: თითო CPU-ზე ცალკე heap-ს ინახავს და უფრო იშვიათად აგროვებს; console აპლიკაციები და worker service-ები Workstation GC-ზე მუშაობს, რომელიც უფრო ხშირად აგროვებს და უფრო პატარა რჩება. კონტეინერის შიგნით runtime კითხულობს მეხსიერების ლიმიტს და managed heap-ს ნაგულისხმევად მის 75%-ზე ზღუდავს, ხოლო .NET 9-იდან Server GC თავისი heap-ების რაოდენობას აპლიკაციის რეალურ ზომას უსადაგებს (DATAS). ასე რომ, პატარა API, რომელიც 4 GB გეგმაზე "400 MB-ს იყენებს", ხშირად უბრალოდ GC-ია, რომელსაც ადგილი მისცეს და ის მას იყენებს - ხოლო იგივე აპლიკაცია 1 GB გეგმაზე გაცილებით ნაკლებში იმუშავებს.
ეს პოსტი ხსნის, რას აკეთებს GC მისთვის მიცემული მეხსიერებით, რომელი პარამეტრები ცვლის ამას, როგორ წაიკითხო ის რიცხვები, რომლებსაც რეალურად აქვს მნიშვნელობა, და როგორ განასხვავო გაჟონვა heap-ისგან, რომელიც უბრალოდ დიდია.
სად მიდის .NET პროცესის მეხსიერება#
მეხსიერების გრაფიკი მთელ პროცესს აჩვენებს. managed heap - ობიექტები, რომლებსაც შენი კოდი გამოყოფს - ყველაზე დიდი ნაწილია, მაგრამ არა ყველაფერი:
- GC heap, თაობებად დაყოფილი. ახალი ობიექტები generation 0-ში ხვდება; გადარჩენილები 1-ში და შემდეგ 2-ში გადადის. 85,000 ბაიტის ან მეტი ზომის ობიექტები პირდაპირ large object heap-ში (LOH) ხვდება, რომელიც generation 2-თან ერთად იწმინდება და ნაგულისხმევად არ კომპაქტდება. pinned ობიექტებს .NET 5-იდან საკუთარი heap აქვს.
- committed, მაგრამ ცარიელი heap სივრცე. შეგროვების შემდეგ GC ინახავს მეხსიერებას, რომელიც მოელის, რომ ისევ დასჭირდება, და მას ოპერაციულ სისტემას მაშინვე არ უბრუნებს. ეს არის მთავარი მიზეზი, რის გამოც გრაფიკი ტრაფიკის აფეთქების შემდეგ არ ეცემა.
- JIT-კომპილირებული კოდი და runtime-ის მონაცემები: ტიპების მეტამონაცემები, მეთოდების ცხრილები, კომპილირებული კოდი ყველა მეთოდისთვის, რომელიც გაეშვა. დიდი აპლიკაცია ბევრი დამოკიდებულებით ამის ათობით მეგაბაიტს ატარებს ტრაფიკისგან დამოუკიდებლად.
- native მეხსიერება: ყველაფერი, რაც GC-ის გარეთ გამოიყოფა - მონაცემთა ბაზის დრაივერები native ნაწილებით, სურათების ბიბლიოთეკები, შეკუმშვის buffer-ები, SQLite.
- thread-ების stack-ები, თითო thread-ზე ერთი. ჯამში ჩვეულებრივ პატარაა, თუ რაღაც ასობით thread-ს არ ქმნის.
აქედან ორი განსხვავებული ჩავარდნა გამომდინარეობს. როცა managed heap თავის ლიმიტს აღწევს, GC შენს აპლიკაციაში OutOfMemoryException-ს აგდებს. როცა მთელი პროცესი კონტეინერის ლიმიტს აღწევს, kernel მას გარედან აჩერებს, გამონაკლისისა და ლოგის ბოლო ხაზის გარეშე. RE:NODE-ზე კონტეინერი, რომელიც მეხსიერების ლიმიტს აღწევს, ჩერდება და სუფთად იწყება ხელახლა და არა swap-ში რჩება, ამიტომ მეორე შემთხვევა restart-ად ჩანს. Linux swap და OOM killer kernel-ის მხარეს ხსნის.
Workstation და Server GC#
.NET-ს GC-ის ორი სახეობა აქვს, და რომელს მიიღებ, პროექტის ტიპზეა დამოკიდებული და არა სერვერზე:
| Workstation GC | Server GC | |
|---|---|---|
| ნაგულისხმევია | console აპლიკაციებისთვის, worker service-ებისთვის | ASP.NET Core (Web SDK) პროექტებისთვის |
| Heap-ები | ერთი | თითო ლოგიკურ CPU-ზე (DATAS არეგულირებს) |
| GC thread-ები | აგროვებს გამომწვევ thread-ზე | ცალკე thread თითო heap-ზე |
| Gen 0 ბიუჯეტი | პატარა | დიდი |
| მეხსიერების მოხმარება | ნაკლები | მეტი |
| გამტარუნარიანობა | ნაკლები დატვირთვისას | მეტი დატვირთვისას |
ორივე ნაგულისხმევად concurrent (ფონურ) რეჟიმში მუშაობს, რაც generation 2-ის შეგროვებებს შენი კოდის პარალელურად ხდომის საშუალებას აძლევს, მისი გაჩერების ნაცვლად.
Server GC-ის უფრო დიდი ბიუჯეტები ნიშნავს, რომ ის შეგროვებებს შორის უფრო დიდხანს გამოყოფს და ამ დროს მეტ მეხსიერებას ინახავს. 16-ბირთვიან მანქანაზე ლიმიტის გარეშე Server GC პროცესი შეიძლება რამდენიმე ასეულ მეგაბაიტზე იდგეს და თითქმის არაფერს აკეთებდეს. აქედან მოდის ფოლკლორი, რომ ASP.NET Core მეხსიერებას ბევრს ჭამს. runtime გამტარუნარიანობისთვის ოპტიმიზირებს, რადგან უთხრეს, რომ შეეძლო.
ნაგულისხმევებზე ორი წესი, რომლებსაც პატარა გეგმებზე მნიშვნელობა აქვს:
- ერთი CPU ნიშნავს Workstation GC-ს. თუ runtime ერთ პროცესორს ხედავს, ის Workstation GC-ს იყენებს, რაც არ უნდა ეწეროს პარამეტრში. ნახევარი vCPU-ის გეგმა ჩვეულებრივ ერთ პროცესორად ჩანს (იხილე ქვემოთ), ამიტომ პატარა ASP.NET Core აპლიკაცია ყველაზე პატარა გეგმაზე უკვე ეკონომიურ collector-ზე მუშაობს.
- შეგიძლია აშკარად აირჩიო. პროექტის ფაილში ან გარემოს ცვლადად:
<PropertyGroup> <ServerGarbageCollection>false</ServerGarbageCollection> <ConcurrentGarbageCollection>true</ConcurrentGarbageCollection></PropertyGroup>DOTNET_gcServer=0ვებ აპლიკაციის Workstation GC-ზე გადართვა ორბირთვიან გეგმაზე მეხსიერებას შესამჩნევად ამცირებს და მოთხოვნების მაღალ სიხშირეზე გამტარუნარიანობის ნაწილი ჯდება. აპლიკაციისთვის, რომელიც წამში რამდენიმე მოთხოვნას ემსახურება, გამტარუნარიანობის განსხვავებას ვერ დაინახავ; მეხსიერების განსხვავებას - დაინახავ.
DATAS: Server GC, რომელიც ზომას თავად ირჩევს#
Dynamic Adaptation To Application Sizes (DATAS) Server GC-ს ცვლის "თითო heap თითო ბირთვზე, ყოველთვის"-იდან "იმდენი heap, რამდენსაც ამ აპლიკაციის დატვირთვა ამართლებს"-ზე. ის ერთი heap-ით იწყება და მეტს ამატებს, როცა გამოყოფის წნეხი იზრდება, და generation 0-ის ბიუჯეტს ცოცხალი მონაცემების ზომას უსადაგებს და არა ბირთვების რაოდენობას.
.NET 8-ში ის opt-in იყო, ხოლო .NET 9-იდან Server GC-თან ნაგულისხმევად ჩართულია. თუ .NET 9-ზე ან 10-ზე ხარ და შენი აპლიკაცია Server GC-ს იყენებს, ის უკვე გაქვს. პრაქტიკული ეფექტი ის არის, რომ მშვიდი ASP.NET Core აპლიკაცია ბევრბირთვიან მანქანაზე მეხსიერებას აღარ იტოვებს ისე, თითქოს სრული დატვირთვის ქვეშ იყოს.
შეგიძლია გამორთო, რაც ზოგჯერ სწორია გამტარუნარიანობისთვის კრიტიკული სერვისისთვის, რომელიც მუდამ დაკავებულია:
DOTNET_GCDynamicAdaptationMode=0MSBuild-ის ეკვივალენტია <GarbageCollectionAdaptationMode>0</GarbageCollectionAdaptationMode>. .NET 8-ზე ჩასართავად დააყენე 1. პატარა და საშუალო აპლიკაციებისთვის მოკრძალებულ გეგმებზე DATAS ჩართული დატოვე.
რას ხედავს runtime კონტეინერის შიგნით#
runtime კონტეინერის cgroup ლიმიტებს კითხულობს, როგორც cgroup v1-ის, ისე v2-ის, და ორ რამეს არეგულირებს.
მეხსიერების ლიმიტი. როცა კონტეინერს მეხსიერების ლიმიტი აქვს, GC managed heap-ზე მკაცრ ლიმიტს აყენებს მისი 75%-ის ოდენობით (ან 20 MB, რომელიც უფრო დიდია). დარჩენილი მეოთხედი რჩება native მეხსიერებისთვის, კოდისთვის, stack-ებისთვის და ყველაფრისთვის, რაც პროცესს სჭირდება. 1 GB გეგმაზე managed heap შეიძლება დაახლოებით 768 MB-მდე გაიზარდოს; 4 GB-ზე - დაახლოებით 3 GB-მდე.
CPU-ების რაოდენობა. Environment.ProcessorCount ასახავს კონტეინერის CPU კვოტას, მთელ რიცხვამდე ზემოთ დამრგვალებულს. ნახევარი CPU-ის ლიმიტი 1-ად ჩანს; ერთნახევარი - 2-ად. ეს რიცხვი წყვეტს, რამდენი heap შეუძლია შექმნას Server GC-ს, როგორ ირჩევს ზომას thread pool და რამდენ რამეს აკეთებს runtime პარალელურად. RE:NODE-ზე CPU-ის ლიმიტი მკაცრი შეზღუდვაა შენ მიერ ნაყიდ წილამდე, ამიტომ runtime-ის ხედვა "2 პროცესორის" შესახებ 1.5 vCPU გეგმაზე რაოდენობაში სწორია, მაგრამ დროში გულუხვი: პროცესი ამ thread-ებზე ერთი ბირთვის დროის 150%-ს იღებს, არა მეტს. შეგიძლია რაოდენობა DOTNET_PROCESSOR_COUNT-ით გადაფარო, თუ ნაგულისხმევი ზედმეტად ბევრ heap-ს ან thread-ს იწვევს.
ზომის შერჩევისთვის შედეგი ის არის, რომ ერთი და იგივე აპლიკაცია სხვადასხვა გეგმაზე სხვადასხვა რაოდენობის მეხსიერებას იყენებს, რადგან GC-ს სხვადასხვა ადგილი ეძლევა. ნუ წაიკითხავ მეხსიერების მაჩვენებელს დიდი მანქანიდან და ნუ ჩათვლი, რომ ამდენი გჭირდება.
პარამეტრები, რომლებიც მეხსიერების მოხმარებას ცვლის#
| პარამეტრი | runtimeconfig / MSBuild | გარემოს ცვლადი | ეფექტი |
|---|---|---|---|
| Server GC | ServerGarbageCollection | DOTNET_gcServer | 0 workstation-ისთვის, 1 server-ისთვის |
| Concurrent GC | ConcurrentGarbageCollection | DOTNET_gcConcurrent | gen 2-ის ფონური შეგროვებები |
| Heap-ის მკაცრი ლიმიტი | System.GC.HeapHardLimit | DOTNET_GCHeapHardLimit | managed heap-ის აბსოლუტური ზღვარი |
| Heap-ის ლიმიტი პროცენტით | System.GC.HeapHardLimitPercent | DOTNET_GCHeapHardLimitPercent | ზღვარი ლიმიტის წილად |
| მეხსიერების დაზოგვა | System.GC.ConserveMemory | DOTNET_GCConserveMemory | 0-9; მეტს კომპაქტავს მეხსიერების დასაზოგად |
| DATAS | GarbageCollectionAdaptationMode | DOTNET_GCDynamicAdaptationMode | 1 ჩართული, 0 გამორთული |
როდის შეეხო heap-ის ლიმიტს: თუ შენი აპლიკაცია მნიშვნელოვან native მეხსიერებას იყენებს - სურათების დამუშავების ბიბლიოთეკა, დიდი SQLite cache, ჩაშენებული ძრავა - ნაგულისხმევი 25%-იანი მარაგი შეიძლება საკმარისი არ იყოს, და პროცესს კონტეინერის ლიმიტი კლავს, სანამ GC წნეხს საერთოდ იგრძნობს. managed ლიმიტის 60%-მდე შემცირება native კოდს მეტ ადგილს აძლევს და GC-ს უფრო ადრე და უფრო მკაცრად აგროვებინებს. საპირისპირო შემთხვევა, მისი 85%-მდე ან მეტამდე გაზრდა, უსაფრთხოა მხოლოდ იმ აპლიკაციებისთვის, რომლებსაც თითქმის არ აქვთ native გამოყოფა.
GCConserveMemory იმ აპლიკაციებისთვისაა, რომლებსაც LOH-ის ძლიერი ფრაგმენტაცია აქვთ: დიდი buffer-ები, რომლებიც განმეორებით გამოიყოფა და თავისუფლდება, ტოვებს ხვრელებს, რომლებსაც GC არ კომპაქტავს. დაახლოებით 5-ის მნიშვნელობა მას LOH-ს აკომპაქტებინებს, როცა ფრაგმენტაცია მაღალია, CPU-ის გარკვეული ფასით.
რიცხვების წაკითხვა, რომლებსაც მნიშვნელობა აქვს#
პროცესის working set (რიცხვი გრაფიკზე) გეუბნება, მიაღწევ თუ არა ლიმიტს. ის არ გეუბნება, რატომ. ამისთვის GC-ს ჰკითხე:
app.MapGet("/debug/memory", () =>{ var info = GC.GetGCMemoryInfo(); return new { heapMB = info.HeapSizeBytes / 1_048_576, committedMB = info.TotalCommittedBytes / 1_048_576, fragmentedMB = info.FragmentedBytes / 1_048_576, limitMB = info.TotalAvailableMemoryBytes / 1_048_576, workingSetMB = Environment.WorkingSet / 1_048_576, gen0 = GC.CollectionCount(0), gen1 = GC.CollectionCount(1), gen2 = GC.CollectionCount(2), serverGc = System.Runtime.GCSettings.IsServerGC, cpus = Environment.ProcessorCount };});დამალე ის ავთენტიკაციის უკან ან გამოყენების შემდეგ წაშალე. როგორ წაიკითხო:
limitMBის მეხსიერებაა, რომელიც GC-ს ჰგონია, რომ აქვს. თუ ის ჰოსტის მთელ მეხსიერებას აჩვენებს და არა შენს გეგმას, runtime-მა კონტეინერის ლიმიტი ვერ აღმოაჩინა, და ამ პოსტში სხვა არაფერი მოქმედებს, სანამ არ აღმოაჩენს.heapMBworkingSetMB-თან შედარებით გეუბნება, ზრდა managed-ია (შენი ობიექტები) თუ native (რაღაც GC-ის გარეთ).committedMB, რომელიცheapMB-ზე გაცილებით მეტია, არის მეხსიერება, რომელსაც GC ხელახლა გამოყენებისთვის ინახავს. ეს გაჟონვა არ არის.gen2, რომელიც სტაბილური დატვირთვისას სწრაფად იზრდება, ნიშნავს, რომ ობიექტები იმდენ ხანს ცოცხლობს, რომ დაწინაურდეს, და შემდეგ კვდება - ხშირად ეს არის cache ზომის ლიმიტის გარეშე, ან მოთხოვნის დონის ობიექტები, რომლებიც რაღაც ხანგრძლივმა ობიექტმა დაიჭირა.
უფრო ღრმა დათვალიერებისთვის დიაგნოსტიკური ხელსაწყოები dotnet-counters და dotnet-gcdump იმავე მონაცემებს ცოცხლად კითხულობს და heap-ის snapshot-ს იღებს, რომლის გახსნაც Visual Studio-ში ან PerfView-ში შეგიძლია. ისინი იქ უნდა გაეშვას, სადაც პროცესი მუშაობს, რაც VDS-ზე მარტივია, სხვაგან კი გარემოზეა დამოკიდებული. .NET 9-იდან runtime ამათ ჩაშენებულ მეტრიკებადაც აქვეყნებს System.Runtime meter-ზე, ამიტომ OpenTelemetry exporter-ს შეუძლია ისინი იქ გაგზავნოს, სადაც მეტრიკებს ინახავ. სერვერის დატვირთვის გრაფიკის წაკითხვა პანელის გრაფიკს განიხილავს.
გაჟონვები და ის, რაც გაჟონვას ჰგავს#
რეალური გაჟონვა არის მეხსიერება, რომელიც სტაბილური დატვირთვისას უსაზღვროდ იზრდება და არასოდეს იწმინდება, რადგან რაღაც მას ჯერ კიდევ მიმართავს. .NET-ში ჩვეულებრივი დამნაშავეები არიან:
- შეუზღუდავი cache-ები. სტატიკური
DictionaryანConcurrentDictionary, რომელიც მხოლოდ ჩანაწერებს იძენს.IMemoryCacheშეუზღუდავია, თუSizeLimit-ს არ დააყენებ და ყველა ჩანაწერსSize-ს არ მისცემ. - event handler-ები ხანგრძლივ ობიექტებზე. ხანმოკლე ობიექტის გამოწერა singleton-ის event-ზე ხანმოკლე ობიექტს სამუდამოდ ცოცხლად ინახავს, თუ გამოწერას არ გააუქმებს.
- root provider-იდან მიღებული disposable-ები. transient
IDisposable, რომელიც აპლიკაციის root service provider-იდან არის მიღებული - scope-ის ნაცვლად - აპლიკაციის გამორთვამდე ტრეკინგდება. მიიღე ის scope-იდან, განსაკუთრებით ფონურ სერვისებში. - ხანგრძლივი `DbContext`. EF Core-ის change tracker ინახავს ყველა entity-ს, რომელიც ჩატვირთა. context, რომელიც singleton-ში ან ფონურ ციკლში ცოცხლად ინახება, იზრდება, სანამ dispose არ გაუკეთდება. გამოიყენე scope სამუშაოს თითო ერთეულზე და
AsNoTracking()წაკითხვებისთვის. - buffer-ში ჩატვირთული დიდი მოთხოვნები და პასუხები. მთელი ატვირთვის
MemoryStream-ში წაკითხვა მას LOH-ზე ათავსებს; რამდენიმე ერთდროული ატვირთვა თითო 50 MB-ით რამდენიმე ასეული მეგაბაიტია. ნაცვლად ამისა stream-ით გადაიტანე დისკზე ან object storage-ში.
რაც გაჟონვას ჰგავს და არ არის: მეხსიერება, რომელიც გაშვების შემდეგ იზრდება და პლატოზე გადის (JIT, cache-ების გათბობა, GC-ის ბიუჯეტზე დადგომა); მეხსიერება, რომელიც ტრაფიკის პიკის შემდეგ არ ეცემა (ხელახლა გამოყენებისთვის შენახული committed სივრცე); ხერხის კბილისებური ფორმა, რომელიც იმავე იატაკზე ბრუნდება (ნორმალური შეგროვება). გაჟონვის იატაკი მაღლა იწევს. უყურე გრაფიკის დაბალ წერტილებს საათების განმავლობაში და არა პიკებს წუთების განმავლობაში. თამაშის სერვერის მეხსიერების გაჟონვები იგივე მსჯელობას აღწერს სხვა ტიპის პროცესისთვის.
გეგმის შერჩევა .NET აპლიკაციისთვის#
ეს არის სამუშაო მაჩვენებლები framework-dependent აპლიკაციისთვის .NET 8-დან 10-მდე მოკრძალებული დატვირთვისას. გაზომე საკუთარი; დამოკიდებულებები მათ ყველაფერზე მეტად ცვლის.
| აპლიკაცია | ტიპური working set | საწყისი გეგმა |
|---|---|---|
| Discord ბოტი ან worker service | 60-150 MB | 1 GB |
| მინიმალური API, ORM-ის გარეშე | 50-120 MB | 1 GB |
| API EF Core-ით, მსუბუქი ტრაფიკი | 120-300 MB | 1-2 GB |
| Razor Pages ან MVC საიტი | 150-400 MB | 2 GB |
| Blazor Server, ათეულობით მომხმარებელი | 300 MB პლუს თითო მომხმარებლის მდგომარეობა | 2-4 GB |
დაამატე ადგილი build-ებისთვის, თუ სერვერზე აკომპილირებ: SDK-სა და კომპილატორს მეტი მეხსიერება სჭირდება, ვიდრე პატარა აპლიკაციების უმეტესობას მუშაობისას. ASP.NET Core აპლიკაციის deploy GitHub-იდან build-ის CI-ში გადატანას განიხილავს, ხოლო Blazor Server თუ WebAssembly ხსნის, რატომ იზრდება Blazor Server მომხმარებლებთან და არა მოთხოვნებთან ერთად.
გეგმა გაზარდე, როცა მეხსიერების გრაფიკის იატაკი, და არა პიკი, ლიმიტის დაახლოებით 70%-ზე მაღლაა, ან როცა აპლიკაცია ლიმიტზე გადაიტვირთება. RE:NODE-ზე მეხსიერების ლიმიტზე restart crash watcher-ში ითვლება - საათში სამი გაფრთხილებას აჩენს და ticket-ს ხსნის - ამიტომ გაჟონვა დიდხანს შეუმჩნეველი არ რჩება. უფრო დიდ გეგმაზე გადასვლა არსებულ სერვერზე ლიმიტს ცვლის და მას თავიდან არ აგებს.
FAQ#
რატომ იყენებს ჩემი ASP.NET Core აპლიკაცია მეტ მეხსიერებას უფრო დიდ გეგმაზე?
იმიტომ, რომ GC-ს ეუბნებიან, რომ მას მეტი მეხსიერება და მეტი CPU აქვს, და ის თავის ბიუჯეტებს შესაბამისად ირჩევს. აპლიკაცია მეტს იმიტომ კი არ იყენებს, რომ სჭირდება; ის უფრო იშვიათად აგროვებს, რადგან შეუძლია. უფრო პატარა ლიმიტზე ის უფრო ხშირად შეაგროვებს და უფრო პატარა დარჩება.
უნდა გამოვიძახო GC.Collect() მეხსიერების გასათავისუფლებლად?
არა. იძულებითი შეგროვებები აპლიკაციას აპაუზებს, აწინაურებს ობიექტებს, რომლებიც სიკვდილის პირას იყო, და ჩვეულებრივ წუთის შემდეგ მეხსიერებას იქ ტოვებს, სადაც იყო. თუ მეხსიერება პრობლემაა, გაასწორე ის, რაც მას იკავებს, ან დააყენე heap-ის ლიმიტი.
Workstation GC ვებ აპლიკაციისთვის უფრო ნელია?
ძლიერი კონკურენტული დატვირთვისას - კი. აპლიკაციისთვის ერთ ან ორ CPU-ზე, რომელიც მოკრძალებულ ტრაფიკს ემსახურება, განსხვავების გაზომვა რთულია, ხოლო მეხსიერების დაზოგვა რეალურია. ერთ-CPU-იან კონტეინერზე უკვე Workstation GC-ზე მუშაობ.
რა ხდება, როცა managed heap თავის მკაცრ ლიმიტს აღწევს?
GC რაც შეიძლება მკაცრად აგროვებს და, თუ ეს საკმარისი არ არის, აგდებს OutOfMemoryException-ს იმ გამოყოფაზე, რომელიც ჩავარდა. თუ მთელი პროცესი კონტეინერის ლიმიტს პირველი აღწევს, kernel მას საერთოდ გამონაკლისის გარეშე აჩერებს.
Native AOT ნაკლებ მეხსიერებას იყენებს?
უქმ მდგომარეობაში - კი, რადგან JIT არ არის და runtime-ის მეტამონაცემები ნაკლებია. დატვირთვისას GC იგივენაირად იქცევა. dotnet publish-ის პარამეტრები განიხილავს, რა უჯდება Native AOT-ს თავსებადობაში.




კომენტარები
სრულიად ანონიმურად: ანგარიშის, ელფოსტის და cookie-ის გარეშე. ინახება მხოლოდ სახელი, ტექსტი და დრო - სხვა არაფერი. ბმულების რაოდენობა ლიმიტირებულია.