This article is a mirror article of machine translation, please click here to jump to the original article.

View: 6846|Reply: 2

[Source] [Turn]. .NET Performance Optimization - Collections.Pooled is recommended

[Copy link]
Posted on 2022-5-29 13:45:22 | | | |
Brief introduction

Performance optimization is how to ensure that the same number of requests are processed with fewer resources, which are generally CPU or memory, and of course, operating system IO handles, network traffic, disk usage, etc. But most of the time, we are reducing CPU and memory usage.
The content shared before has some limitations, it is difficult to transform directly, today I want to share with you a simple method, only need to replace a few collection types, to achieve the effect of improving performance and reducing memory footprint.
Today I want to share with you a class library, thisThe class library is called Collections.Pooled, As can be seen from the name, it is through pooled memory to achieve the purpose of reducing memory footprint and GC, and we will directly see how its performance is, and we will also take you to see the source code, why it brings these performance improvements.

Collections.Pooled

Project link:The hyperlink login is visible.

The library is based on classes in System.Collections.Generic, which have been modified to take advantage of the new System.Span <T>and System.Buffers.ArrayPool <T>class libraries for the purpose of reducing memory allocation, improving performance, and allowing greater interoperability with modern APIs.
Collections.Pooled supports .NET Standnd 2.0 (.NET Framework 4.6.1+), as well as support for . NET Core 2.1+. An extensive set of unit tests and benchmarks has been ported from CoreFX.

Total number of tests: 27501. Via: 27501. Failure: 0. Skip: 0.
The test run was successful.
Test execution time: 9.9019 seconds

How to use

You can easily install this library through Nuget, NuGet Version.

In the Collections.Pooled library, it implements pooled versions for the collection types we commonly use, as shown in the comparison with the .NET native types.

. .NET nativeCollections.Pooledremark
List<T>PooledList<T>Generic collection classes
Dictionary<TKey, TValue>PooledDictionary<TKey, TValue>Generic dictionary class
HashSet<T>PooledSet<T>Generic hash collection classes
Stack<T>Stack<T>Generic stacks
Queue<T>PooledQueue<T>Generic cohort

When using, we only need to add the corresponding . .NET native version with the Collections.Pooled version, as shown in the code below:

However, we need to note that the Pooled type implements the IDispose interface, which returns the used memory to the pool through the Dispose() method, so we need to call its Dispose() method after using the Pooled collection object. Or you can use the using var keyword directly.

Note: Use the collection object inside Collections.PooledIt is best to need to release it manually, but it doesn't matter if you don't release it, the GC will eventually recycle it, but it can't be returned to the pool, and it won't achieve the effect of saving memory.
Since it reuses memory space, when returning memory space to the pool, it needs to process the elements in the collection, and it provides an enumeration called ClearMode for use, defined as follows:




By default, you can use the default value Auto, and if there are special performance requirements, you can use Never after knowing the risks.
For reference types and value types containing reference types, we must empty the array reference when returning the memory space to the pool, if not cleared, the GC will not be able to free up this part of the memory space (because the reference of the element has always been held by the pool), if it is a pure value type, then it can not be emptied, in this article I describe the storage difference between reference types and struct (value type) arrays, pure value types do not have object header recycling and do not require GC intervention.


. .NET Performance Optimization - Use struct alternative classes:The hyperlink login is visible.

Performance comparison

I didn't do Benchmark alone, and the running score results of open source projects that I directly used were 0 for the memory usage of many projects, which was because the pooled memory used had no extra allocation.

PooledList<T>

Loop through the 2048 elements added to the set in Benchmark, . .NET native List <T>requires 110us (according to the actual benchmark results, the milliseconds in the figure should be a clerical error) and 263KB memory, while PooledList <T>only needs 36us and 0KB memory.




PooledDictionary<TKey, TValue>

Add 10_0000 elements to the dictionary in a loop in Benchmark, . .NET native Dictionary<TKey, TValue> requires 11ms and 13MB of memory, while PooledDictionary<TKey, TValue> only requires 7ms and 0MB of memory.




PooledSet<T>

Loop through the hash collection in Benchmark add 10_0000 elements, . The .NET native HashSet <T>requires 5348ms and 2MB, while the PooledSet <T>requires only 4723ms and 0MB memory.




PooledStack<T>

Loop through the stack in Benchmark to add 10_0000 elements, . .NET native PooledStack <T>requires 1079ms and 2MB, while PooledStack <T>only requires 633ms and 0MB memory.




PooledQueue<T>

Loop through the loops in Benchmark to add 10_0000 elements to the queue, . .NET native <T>PooledQueue requires 681ms and 1MB, while PooledQueue <T>only requires 408ms and 0MB of memory.




The scene is not manually released

In addition, we mentioned above that the pooled collection type needs to be released, but it doesn't matter if it is not released, because the GC will recycle.


The Benchmark results are as follows:



The conclusion can be drawn from the Benchmark results above.

Releasing the Pooled type collection in time barely triggers GC and allocates memory, from the above graph it only allocates 56Byte of memory.
Even if the Pooled type collection is not released, because it allocates memory from the pool, it will still reuse memory during the ReSize expansion operation, and skip the GC allocation memory initialization step, which is relatively fast.
The slowest is to use the normal collection type, each ReSize expansion operation needs to apply for new memory space, and the GC also needs to reclaim the previous memory space.


Principle analysis

If you have read my previous blog post, you should set the initial size for collection types and analyze the implementation principle of C# Dictionary, you can know that .NET BCL developers use the underlying data structures of these basic collection types to be arrays for high-performance random access, let's take List <T>as an example.

Create a new array to store the added elements.
If there is not enough space in the array, the expansion operation is triggered to request twice the space size.
The constructor code is as follows, and you can see that it is a generic array created directly:


So if you want to pool memory, you only need to change the place where the new keyword application is used in the class library to use pooled application. Here I share it with you. NET BCL is a type called ArrayPool, which provides an array resource pool of reusable generic instances, which can be used to reduce the pressure on GC and improve performance in the case of frequent array creation and destruction.

The underlying layer of our Pooled type is to use ArrayPool to share resource pools, and from its constructor, we can see that it uses ArrayPool by default<T>. Shared to assign array objects, and of course you can also create your own ArrayPool to use it.


In addition, when performing a capacity adjustment operation (expansion), the old array is returned to the thread pool, and the new array is also acquired from the pool.

In addition, the author uses Span to optimize APIs such as Add and Insert to give them better random access performance. In addition, the TryXXX series API has been added, so you can use it in a more convenient way. For example, the List <T>class <T>has up to 170 modifications compared to PooledList.



summary

In our actual online use, we can replace the native collection type with the collection type provided by Pooled, which is very helpful in reducing memory usage and P95 latency.
Also, even if you forget to release it, the performance will not be much worse than using the native collection type. Of course, the best habit is to release it in time.


Original:The hyperlink login is visible.




Previous:RecyclableMemoryStream provides high-performance .NET streaming
Next:[Practical combat] The server builds LibreSpeed to test the network speed
Posted on 2022-5-29 17:12:36 |
Learn it
Posted on 2022-6-20 09:09:22 |
Learn the mix
Disclaimer:
All software, programming materials or articles published by Code Farmer Network are only for learning and research purposes; The above content shall not be used for commercial or illegal purposes, otherwise, users shall bear all consequences. The information on this site comes from the Internet, and copyright disputes have nothing to do with this site. You must completely delete the above content from your computer within 24 hours of downloading. If you like the program, please support genuine software, purchase registration, and get better genuine services. If there is any infringement, please contact us by email.

Mail To:help@itsvse.com