Location>code7788 >text

NET Non-Invasive Object Pooling Solution

Popularity:325 ℃/2024-10-16 06:28:45

Pooling(/inversionhourglass/Pooling), a compile-time object pooling component that compiles the specified type ofnewThe operations are replaced with object pooling operations, simplifying the coding process and eliminating the need for developers to manually write code for object pooling operations. At the same time, it provides a completely non-intrusive solution, which can be used as a temporary performance optimization solution and an old project performance optimization solution.

Quick Start

quote

dotnet add package

assurefile, if the current project does not have thefile, you can compile the project directly and it will automatically generate theDocumentation:

<Weavers xmlns:xsi="http:///2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="">
  <Pooling /> <!--Ensuring the presence ofPoolingnodal-->
</Weavers>
// 1. The type to be pooled implements the IPoolItem interface.
public class TestItem : IPoolItem
{
    public int Value { get; set; }

    // This method resets the instance state when the object returns to object pooling.
    public bool TryReset()
    {
        return true; }
    }
}

// 2. Use the new keyword anywhere to create an object of that type.
public class Test
public class Test {
    public void M()
    {
        var random = new Random(); var item = new TestItem(); var TestItem()
        
         = (); var item = new TestItem(); var item = new TestItem()
        (); }
    }
}

// Compiled code
public class Test
{
    public void M()
    TestItem item = null; TestItem item = null
        TestItem item = null;
        TestItem item = null; testItem item = null; try
        var random = new Random(); try
            var random = new Random(); item = Pool<TestItem>.
            item = Pool<TestItem>.Get(); var random = new Random(); item = Pool<TestItem>.
             = ().
            ();
        }
        finally
        {
            if (item ! = item !)
            {
                Pool<TestItem>.Return(item);
            }
        }
    }
}

IPoolItem

precisely asQuick StartThe code shown in the implementation of theIPoolItemThe type of an interface is a pooled type, and at compile time Pooling replaces its new operation with an object pooling operation and returns the pooled object instance to the object pool in the finally block.IPoolItemOnly oneTryResetmethod, which is used to reset the state of the object when it returns to the pool. When the method returns false, the state reset fails and the object will be discarded.

PoolingExclusiveAttribute

By default, the implementation of theIPoolItemThe pooled type will perform pooling operations in all methods, but sometimes we may want the pooled type not to perform pooling operations in some types, for example, we may create some pooled types of managed types or Builder types, at this time on the pooled type to apply thePoolingExclusiveAttributeIt is then possible to specify that this pooled type does not perform pooling operations in certain types/methods.

[PoolingExclusive(Types = [typeof(TestItemBuilder)], Pattern = "execution(* TestItemManager.*(..))")]
public class TestItem : IPoolItem
{
    public bool TryReset() => true;
}

public class TestItemBuilder
{
    private readonly TestItem _item;

    private TestItemBuilder()
    {
        // As a result of the adoption ofPoolingExclusive(used form a nominal expression)Typesattribute excludes theTestItemBuilder,So it won't be replaced with an object pool operation here
        _item = new TestItem();
    }

    public static TestItemBuilder Create() => new TestItemBuilder();

    public TestItemBuilder SetXxx()
    {
        // ...
        return this;
    }

    public TestItem Build()
    {
        return _item;
    }
}

public class TestItemManager
{
    private TestItem? _cacheItem;

    public void Execute()
    {
        // As a result of the adoption ofPoolingExclusive(used form a nominal expression)Patternattribute excludes theTestItemManager下(used form a nominal expression)所有方法,So it won't be replaced with an object pool operation here
        var item = _cacheItem ?? new TestItem();
        // ...
    }
}

As shown in the code above, thePoolingExclusiveAttributeThere are two propertiesTypescap (a poem)PatternTypesbecause ofTypeAn array of types where the currently pooled type does not perform pooling operations in the methods of the types in the array;Patternbecause ofstringType AspectN expressions that can be meticulously matched to specific methods (see AspectN expression format for details:/inversionhourglass//blob/master/ ), the current pooled type will not perform pooling operations in the methods it is matched to. Both attributes can be used either or both, and when used together will exclude all types/methods that both attributes match to.

NonPooledAttribute

The previous section describes how it is possible to pass thePoolingExclusiveAttributeSpecifies that the current pooled object does not perform pooling operations in certain types/methods, but since thePoolingExclusiveAttributeneeds to be applied directly to the pooled type, so if you're using a pooled type from a third-party class library, you can't directly apply thePoolingExclusiveAttributeapplied to that pooling type. For such cases, you can use theNonPooledAttributeIndicates that the current method does not perform pooling operations.

public class TestItem1 : IPoolItem
{
    public bool TryReset() => true;
}
public class TestItem2 : IPoolItem
{
    public bool TryReset() => true;
}
public class TestItem3 : IPoolItem
{
    public bool TryReset() => true;
}

public class Test
{
    [NonPooled]
    public void M()
    {
        // Since the method applies theNonPooledAttribute,the following threenewoperations are not replaced with object pool operations
        var item1 = new TestItem1();
        var item2 = new TestItem2();
        var item3 = new TestItem3();
    }
}

There may be times when you don't want all the pooled types in a method to be left out of the pooling operation.NonPooledAttributeThe two attributes of theTypescap (a poem)PatternSpecifies a pooling type that is not available for pooling operations.Typesbecause ofTypeAn array of types, all types in the array are not poolable in the current method;Patternbecause ofstringType AspectN type expression, all matching types are not poolable in the current method.

public class Test
{
    [NonPooled(Types = [typeof(TestItem1)], Pattern = "*. .TestItem3")]
    public void M()
    {
        // Pooling is not allowed for TestItem1 via Types, or for TestItem3 via Pattern, only for TestItem2.
        var item1 = new TestItem1(); var item2 = new TestItem2()
        var item2 = new TestItem2();
        var item3 = new TestItem3();
    }
}

AspectN type expressions are flexible and support logical non-operators!So it's easy to use AspectN type expressions to allow only a certain type, for example, the above example could simply be changed to[NonPooled(Pattern = "!TestItem2")], for more AspectN expression descriptions, see:/inversionhourglass//blob/master/

NonPooledAttributeIt can be applied not only to method hierarchies, but also to types and assemblies. Applying to a class is equivalent to applying to all methods of the class (including attributes and constructors), and applying to an assembly is equivalent to applying to all methods of the current assembly (including attributes and constructors), and additionally, if you don't specify theTypescap (a poem)Patterntwo properties, then it is equivalent to disabling Pooling for the current assembly.

Non-intrusive pooling operation

Read the previous content and then look at the title, you may be muttering "what kind of non-intrusive, this is not pure pure title party". Now comes the part where Pooling provides non-intrusive access for ad-hoc performance optimization and retrofitting of old projects, without the need to implement theIPoolIteminterface, the pooling type can be specified through configuration.

Suppose the following code is currently available:

namespace ;

public class Item1
{
    public object? GetAndDelete() => null;
}

public class Item2
{
    public bool Clear() => true;
}

public class Item3 { }

public class Test
{
    public static void M1()
    {
        var item1 = new Item1();
        var item2 = new Item2();
        var item3 = new Item3();
        ($"{item1}, {item2}, {item3}");
    }

    public static async ValueTask M2()
    {
        var item1 = new Item1();
        var item2 = new Item2();
        await ();
        var item3 = new Item3();
        ($"{item1}, {item2}, {item3}");
    }
}

The project is cited in theAfter compiling the project, the project folder will generate afile, we modify it as shown in the following examplePoolingNodes:

<Weavers xmlns:xsi="http:///2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="">
  <Pooling>
    <Items>
      <Item pattern="." />
      <Item pattern="" inspect="execution(* Test.M1(..))" />
      <Item stateless="*..Item3" not-inspect="method(* Test.M2())" />
	</Items>
  </Pooling>
</Weavers>

In the configuration above, eachItemThe node matches a pooled type, and all four properties are shown in the configuration above, with their respective meanings:

  • pattern: AspectN type + method expression. The matched type is a pooled type and the matched method is a state reset method (equivalent to IPoolItem's TryReset method). Note that the reset method must be parameterless.
  • stateless: AspectN type expression. The matched type is a pooled type, which is stateless and does not require a reset operation to return to the object pool.
  • inspect: AspectN expression.patterncap (a poem)statelessThe type of pooling to match to, and the pooling operation will be performed only in the methods matched by this expression. When this configuration is default it indicates that all methods of the current assembly are matched.
  • not-inspect: AspectN expression.patterncap (a poem)statelessThe matched pooling types will not perform pooling operations in the methods matched by this expression. When this configuration is default it means that no methods are excluded. The set of methods for which the final pooling type can perform pooling operations isinspectCollection andnot-inspectThe difference set of the set.

Then with the configuration above, theTestIn the compiled code for:

public class Test
{
    public static void M1()
    {
        Item1 item1 = null;
        Item2 item2 = null;
        Item3 item3 = null;
        try
        {
            item1 = Pool<Item1>.Get();
            item2 = Pool<Item2>.Get();
            item3 = Pool<Item3>.Get();
            ($"{item1}, {item2}, {item3}");
        }
        finally
        {
            if (item1 != null)
            {
                ();
                Pool<Item1>.Return(item1);
            }
            if (item2 != null)
            {
                if (())
                {
                    Pool<Item2>.Return(item2);
                }
            }
            if (item3 != null)
            {
                Pool<Item3>.Return(item3);
            }
        }
    }

    public static async ValueTask M2()
    {
        Item1 item1 = null;
        try
        {
            item1 = Pool<Item1>.Get();
            var item2 = new Item2();
            await ();
            var item3 = new Item3();
            ($"{item1}, {item2}, {item3}");
        }
        finally
        {
            if (item1 != null)
            {
                ();
                Pool<Item1>.Return(item1);
            }
        }
    }
}

The observant among you may have noticed that inM1method.item1cap (a poem)item2There is a difference in the call to the reset method due to theItem2The return value type of the reset method ofbool, Poolinng will use the result as a basis for whether the reset was successful or not for thevoidor any other type of return value, Pooling will default to a successful reset of the method upon its successful return.

Zero-intrusive pooling operation

See this title is not a little confused, just introduced the non-intrusive, how come there is a zero-intrusive, what is the difference between them?

In the non-intrusive pooling operation described above, we do not need to change any C# code to complete the specified type pooling operation, but we still need to add the NuGet dependency, and need to modify the configuration, which still needs to be done manually by the developer. So how do we make it so that developers don't need to do anything at all? The answer is also simple: put this step into the CI process or the release process. Yes, Zero Intrusion is for developers, it's not really that they don't need to do anything, it's that the steps of referencing NuGet and configuring it are deferred to the CI/Release process.

What's the advantage?

Similar to the object pool this type of optimization is often not just a project need to optimize, this optimization may be universal, then compared to a project by project modification, unified in the CI process/release process configuration is a more rapid choice. In addition, in the face of some antique projects, no one may be willing to change any code, even if it is only the project files and configuration files, at this time you can also modify the CI/release process to complete. Of course, the impact of modifying the unified CI/release process may be broader, here is just to provide a zero-intrusive ideas, the specific circumstances also need to be considered in conjunction with the actual situation.

How to realize

The most straightforward way to do this is in the CI build process or release process via thedotnet add package Add NuGet dependency for the project, and then copy the pre-configured one to the project directory. However, if the project also references other Fody plug-ins, directly overriding the original may result in the original plug-in invalid. Of course, you can also be a little more complex through the content of the script control, here I recommend a .NET CLI tool, .Cli4FodyNuGet dependencies and configuration can be done in one step.

<Weavers xmlns:xsi="http:///2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="">
  <Pooling>
    <Items>
      <Item pattern="." />
      <Item pattern="" inspect="execution(* Test.M1(..))" />
      <Item stateless="*..Item3" not-inspect="method(* Test.M2())" />
	</Items>
  </Pooling>
</Weavers>

Above, the corresponding command using Cli4Fody is:

fody-cli  \
        --addin Pooling -pv 0.1.0 \
            -n Items:Item -a "pattern=." \
            -n Items:Item -a "pattern=" -a "inspect=execution(* Test.M1(..))" \
            -n Items:Item -a "stateless=*..Item3" -a "not-inspect=method(* Test.M2())"

The advantage of Cli4Fody is that NuGet references and can be done at the same time, and Cli4Fody does not modify or delete the configuration of other Fody plugins. More Cli4Fody related configuration, see:/inversionhourglass/Cli4Fody

Rougamo Zero-Invasive Optimization Case

Meat Loaf (Rougamo), a static code weaving AOP component. Meat Loaf in version 2.2.0 added structure support , you can optimize the GC through the structure . but the use of structure is not as convenient as the class , can not inherit from the parent class can only implement the interface , so a lot ofMoAttributeThe default implementation in needs to be duplicated when defining structures. Now you can use Pooling to optimize the GC of meatloaf through object pooling.In this example will use Docker to demonstrate how to accomplish zero-intrusive pooling operations using Cli4Fody in a Docker build flow:

Catalog structure:

.
├── Lib
│ └─── # dependencies
│ └─── # predecessorMoAttribute
└── RougamoPoolingConsoleApp
    └──
    └── Dockerfile
    └─── # quote,there isn't anyFody插件dependencies
    └──

The test program is inThere are two empty test methods defined insideMcap (a poem)N, both methods apply theTestAttribute. This test will use Cli4Fody in the build step of Docker to add a dependency to the project and toTestAttributeConfigure it as a pooling type, and also set it to be available only on themethod for pooling and then compare them via BenchmarkMcap (a poem)NThe GC situation.

// TestAttribute
public class TestAttribute : MoAttribute
{
    // in order forGCThe effect is more pronounced,everyoneTestAttributeBoth will hold the length of1024byte array
    private readonly byte[] _occupy = new byte[1024];
}

// BenchmarkTest
public class BenchmarkTest
{
    [Benchmark]
    [Test]
    public void M() { }

    [Benchmark]
    [Test]
    public void N() { }
}

// Program
var config = ()
    .AddDiagnoser();
var _ = <BenchmarkTest>(config);

Dockerfile

FROM /dotnet/sdk:8.0
WORKDIR /src
COPY . .

ENV PATH="$PATH:/root/.dotnet/tools"
RUN dotnet tool install -g Cli4Fody
RUN fody-cli  --addin Rougamo -pv 4.0.4 --addin Pooling -pv 0.1.0 -n Items:Item -a "stateless=+" -a "inspect=method(* (..))"

RUN dotnet restore

RUN dotnet publish "./RougamoPoolingConsoleApp/" -c Release -o /src/bin/publish

WORKDIR /src/bin/publish
ENTRYPOINT ["dotnet", ""]

With Cli4Fody the finalembeddedTestAttributeA pooling operation was performed, and theembeddedTestAttributeNo pooling operation was performed and the final Benchmark results are as follows:

| Method | Mean     | Error   | StdDev   | Gen0   | Gen1   | Allocated |
|------- |---------:|--------:|---------:|-------:|-------:|----------:|
| M      | 188.7 ns | 3.81 ns |  6.67 ns | 0.0210 |      - |     264 B |
| N      | 195.5 ns | 4.09 ns | 11.74 ns | 0.1090 | 0.0002 |    1368 B |

The full sample code is saved at:/inversionhourglass/Pooling/tree/master/samples/DockerSample

In this example, object pooling optimization for Rougamo is accomplished by using Cli4Fody in the build step of Docker, and the entire process is completely non-intrusive and zero-intrusive to development. If you're going to optimize Rougamo for object pooling in this way, it's important to note that the type of facet in the current example isTestAttributeis stateless, so you need to confirm with the developer that all defined cutout types are stateless, for stateful cutout types you need to define the reset method and use the pattern attribute instead of the stateless attribute when defining the Item node.

Another thing you may not have noticed in this example is that only the Lib project is referenced, the RougamoPoolingConsoleApp project is not, and by default applies to theBenchmarkTest(used form a nominal expression)TestAttributeIt shouldn't work, but it did in my example. This is because the Rougamo related parameters are also specified when using Cli4Fody, and Cli4Fody adds a reference to RougamoPoolingConsoleApp, so Cli4Fody can also be used to avoid omitting direct dependencies on the Team Fody plugin of the project, see more on Cli4Fody for more details:/inversionhourglass/Cli4Fody

configuration item

existNon-intrusive pooling operationIt was introduced in theItemsThe node configuration, in addition to theItemsThe configuration item Pooling also provides other configuration items, the following is a complete configuration example:

<Pooling enabled="true" composite-accessibility="false">
  <Inspects>
    <Inspect>any_aspectn_pattern</Inspect>
    <Inspect>any_aspectn_pattern</Inspect>
  </Inspects>
  <NotInspects>
    <NotInspect>any_aspectn_pattern</NotInspect>
    <NotInspect>any_aspectn_pattern</NotInspect>
  </NotInspects>
  <Items>
    <Item pattern="method_name_pattern" stateless="type_pattern" inspect="any_aspectn_pattern" not-inspect="any_aspectn_pattern" />
    <Item pattern="method_name_pattern" stateless="type_pattern" inspect="any_aspectn_pattern" not-inspect="any_aspectn_pattern" />
  </Items>
</Pooling>
Node Path attribute name use
/Pooling enabled Whether to enable Pooling
/Pooling composite-accessibility Whether AspectN matches using combined class + method accessibility. The default is to match only by method accessibility, for example, if the class accessibility is internal and the method accessibility is public, then by default the accessibility of the method is recognized as public, and after setting this configuration to true, the accessibility of the method is recognized as internal
/Pooling/Inspects/Inspect [Node value] AspectN expression.
global filter, only methods matched by this expression are checked for internal use of pooled types and pooling operations are performed to replace them. Even methods that implement theIPoolItemof the pooling type will also be limited by this configuration.
This node can be configured with multiple entries, and the set of matched methods is the concatenation of the multiple configurations.
This node by default indicates that it matches all methods of the current assembly.
The final set of methods is the set of configuration matches for this node with the/Pooling/NotInspects Configure the difference set of matched sets.
/Pooling/NotInspects/NotInspect [Node value] AspectN expression.
global filter, the internals of methods matched by this expression are not replaced by pooling operations. Even if a method that implements theIPoolItemof the pooling type will also be limited by this configuration.
This node can be configured with multiple entries, and the set of matched methods is the concatenation of the multiple configurations.
This node by default indicates that no methods are excluded.
The final set of methods is/Pooling/Inspects The difference between the set of configuration matches and the set of configuration matches for this node.
/Pooling/Items/Item pattern AspectN type + method name expression.
Matched types will be used as pooled types and matched methods will be used as reset methods.
The reset method must be a parameterless method if the method return value is of typeboolThe return value will also be used as the basis for whether the reset was successful or not.
This property is the same as thestatelessAttributes can only be selected in two.
/Pooling/Items/Item stateless AspectN type expression.
The matched type will be used as the pooled type, which is stateless and does not need to be reset until it is returned to the object pool.
This property is the same as thepatternOnly one of two options is available.
/Pooling/Items/Item inspect AspectN expression.
patterncap (a poem)statelessThe type of pooling that is matched to, and the pooling operation will only be performed in the method that this expression matches to.
When this configuration is defaulted it indicates that all methods of the current program set are matched.
The set of methods to which the current pooled type can eventually be applied is the set of methods that match this configuration with thenot-inspectConfigure the difference set of the set of matched methods.
/Pooling/Items/Item not-inspect AspectN expression.
patterncap (a poem)statelessThe matched pooling type will not perform pooling operations in the methods matched by this expression.
When this configuration is defaulted it means that no methods are excluded.
The set of methods that the current pooled type can eventually apply isinspectThe difference between the set of methods matched by the configuration and the set of methods matched by that configuration.

You can see that the configuration makes extensive use of AspectN expressions, to learn more about the usage of AspectN expressions see:/inversionhourglass//blob/master/

Also note that all methods in a program set are like memory and AspectN is like pointers, and extra care is needed when manipulating memory via pointers. Matching unintended types to pooled types may result in concurrent use of the same object instance, so try to use exact matching and avoid fuzzy matching when using AspectN expressions.

Object Pool Configuration

Maximum number of objects held in the object pool

The maximum number of objects held in the object pool for each pooling type is the number of logical processors multiplied by 2 * 2, there are two ways to modify this default setting.

  1. Specify by code

    pass (a bill or inspection etc)You can set the maximum number of objects held in the object pool for all pooling types, via thePool<T>.MaximumRetainedYou can set the maximum number of objects held in the object pool for the specified pooling type. The latter has a higher priority than the former.

  2. Specify via environment variable

    Specifying an environment variable at application startup modifies the maximum number of objects held in the object pool.NET_POOLING_MAX_RETAINis used to set the maximum number of objects held in the object pool for all pooled types.NET_POOLING_MAX_RETAIN_{PoolItemFullName}is used to set the maximum number of objects held in the object pool for the specified pooling type, where{PoolItemFullName}is the full name of the pooled type (namespace. Class Name), it is important to note that you need to set the full name in the.Replace with_For exampleNET_POOLING_MAX_RETAIN_System_Text_StringBuilder. Environment variables are prioritized over code specification and are recommended for more flexible control.

Customized Object Pools

We know that there is an official object pooling libraryPooling does not directly reference this library but chooses to build its own object pool because Pooling, as a compile-time component, has its method calls woven directly into it via IL, so if it references a third-party library and the method signatures are modified by the third-party library in a subsequent update, it may throw theMethodNotFoundException, so minimizing three-way dependencies is the best option for compile-time components.

Some of you may be concerned about the performance of self-built object pools, you can rest assured that Pooling object pools are implemented from theCopied from, while streamlining theObjectPoolProvider, PooledObjectPolicyand other elements, keeping the default object pooling implementation the leanest. At the same time, Pooling supports custom object pools that implement theIPoolinterface defines a generic object pool that implements theIPool<T>interface defines an object pool for a specific pooling type. The following is a simple demonstration of how to replace the object pooling implementation with a custom object pooling

// Universal Object Pool
public class MicrosoftPool : IPool
{
    private static readonly ConcurrentDictionary<Type, object> _Pools = [];

    public T Get<T>() where T : class, new()
    {
        return GetPool<T>().Get();
    }

    public void Return<T>(T value) where T : class, new()
    {
        GetPool<T>().Return(value);
    }

    private ObjectPool<T> GetPool<T>() where T : class, new()
    {
        return (ObjectPool<T>)_Pools.GetOrAdd(typeof(T), t =>
        {
            var provider = new DefaultObjectPoolProvider();
            var policy = new DefaultPooledObjectPolicy<T>();
            return (policy);
        });
    }
}

// Pool of objects of a particular pooling type
public class SpecificalMicrosoftPool<T> : IPool<T> where T : class, new()
{
    private readonly ObjectPool<T> _pool;

    public SpecificalMicrosoftPool()
    {
        var provider = new DefaultObjectPoolProvider();
        var policy = new DefaultPooledObjectPolicy<T>();
        _pool = (policy);
    }

    public T Get()
    {
        return _pool.Get();
    }

    public void Return(T value)
    {
        _pool.Return(value);
    }
}

// Replacement operations are best performed in theMainPortal Direct Completion,Once the object pool has been used, it is no longer run for replacement.

// 替换Universal Object Pool实现
(new MicrosoftPool());
// Replacing a pool of type-specific objects
Pool<Xyz>.Set(new SpecificalMicrosoftPool<Xyz>());

Not just for object pooling

While the intent of Pooling is to simplify object pooling operations and non-intrusive project transformation optimizations, thanks to the way Pooling is implemented and the custom object pooling capabilities it provides, you can use Pooling to accomplish more than just object pooling; Pooling's implementation is the equivalent of burying a probe in the middle of all of the parameter-less constructor calls, where you can do Anything, here are a few simple examples.

singleton example

// Defining the singleton object pool
public class SingletonPool<T> : IPool<T> where T : class, new()
{
    private readonly T _value = new();

    public T Get() => _value;

    public void Return(T value) { }
}

// Replacing Object Pool Implementations
Pool<ConcurrentDictionary<Type, object>>.Set(new SingletonPool<ConcurrentDictionary<Type, object>>());

// By configuring the,commander-in-chief (military)ConcurrentDictionary<Type, object>Set to pooling type
// <Item stateless="&lt;, object&gt;" />

With the above changes, you've managed to get all theConcurrentDictionary<Type, object>>Share an instance.

Control semaphore

// Defining a Pool of Signal Objects
public class SemaphorePool<T> : IPool<T> where T : class, new()
{
    private readonly Semaphore _semaphore = new(3, 3);
    private readonly DefaultPool<T> _pool = new();

    public T Get()
    {
        if (!_semaphore.WaitOne(100)) return null;

        return _pool.Get();
    }

    public void Return(T value)
    {
        _pool.Return(value);
        _semaphore.Release();
    }
}

// Replacing Object Pool Implementations
Pool<Connection>.Set(new SemaphorePool<Connection>());

// By configuring the,commander-in-chief (military)ConnectionSet to pooling type
// <Item stateless="" />

Use the pool of semaphore objects in this example to control theConnectionnumber, which is great for some flow-limiting scenarios.

thread singularity

// Define a pool of ready-made singleton objects
public class ThreadLocalPool<T> : IPool<T> where T : class, new()
{
    private readonly ThreadLocal<T> _random = new(() => new());

    public T Get() => _random.Value!;

    public void Return(T value) { }
}

// Replacing Object Pool Implementations
Pool<Random>.Set(new ThreadLocalPool<Random>());

// By configuring the,commander-in-chief (military)ConnectionSet to pooling type
// <Item stateless="" />

When you want to reduce GC pressure through singletons but the object is not thread-safe, theThreadLocalImplementing in-thread singletons.

Additional initialization

// Define a pool of present property injection objects
public class ServiceSetupPool : IPool<Service1>
{
    public Service1 Get()
    {
        var service1 = new Service1();
        var service2 = ?.GetService<Service2>();
        service1.Service2 = service2;

        return service1;
    }

    public void Return(Service1 value) { }
}

// Define the pooling type
public class Service2 { }

[PoolingExclusive(Types = [typeof(ServiceSetupPool)])]
public class Service1 : IPoolItem
{
    public Service2? Service2 { get; set; }

    public bool TryReset() => true;
}

// Replacing Object Pool Implementations
Pool<Service1>.Set(new ServiceSetupPool());

In this example use Pooling in conjunction withTo complete the property injection, other initialization operations can be completed using the same approach.

Use your imagination.

These examples may not be practical, the main purpose of these examples is to inspire you to open up the idea of understanding the basic principle of Pooling is to replace the new operation of temporary variables with object pooling operations, and to understand the scalability of custom object pooling. Maybe you can't use Pooling now, but in the future, you may be able to use Pooling to quickly realize a scenario without a lot of changes to the code.

caveat

  1. Don't perform reuse-time initialization operations in the constructor methods of pooled types

    Objects obtained from the object pool may be reused, and reused objects do not execute the constructor method again, so if you have some initialization operation that you want to execute every time you reuse it, you should put that operation in a separate method and call it after the new operation instead of putting it in the constructor method

    // Pooled object definition before modification
    public class Connection : IPoolItem
    {
        private readonly Socket _socket;
    
        public Connection()
        {
            _socket = new(, , );
            // It shouldn't be here.Connect,should incorporateConnectoperation as a separate and independent method,then (afterwards)newpostoperation call
            _socket.Connect("127.0.0.1", 8888);
        }
    
        public void Write(string message)
        {
            // ...
        }
    
        public bool TryReset()
        {
            _socket.Disconnect(true);
            return true;
        }
    }
    // Pooled objects before modification use the
    var connection = new Connection();
    ("message");
    
    // Modified Pooling Object Definition
    public class Connection : IPoolItem
    {
        private readonly Socket _socket;
    
        public Connection()
        {
            _socket = new(, , );
        }
    
        public void Connect()
        {
            _socket.Connect("127.0.0.1", 8888);
        }
    
        public void Write(string message)
        {
            // ...
        }
    
        public bool TryReset()
        {
            _socket.Disconnect(true);
            return true;
        }
    }
    // Modified pooled objects use
    var connection = new Connection();
    ();
    ("message");
    
  2. Replacing the new operation of a parameterless constructor with an object pool operation is only supported

    Since reused objects cannot execute constructor methods again, constructor parameters are meaningless for pooled objects. If you want to do some initialization operations with the constructor parameters, you can either create a new initialization method to receive these parameters and complete the initialization, or receive them through properties.

    Pooling will check at compile time if the new operation calls a non-referential constructor, and will not replace this new operation with an object pooling operation if a referential constructor is called.

  3. Be careful not to save pooled type instances persistently

    Pooling's object pooling operations are method-level, that is, the pooled object is created in the current method and released at the end of the current method, and the pooled object must not be persisted into a field, otherwise there is a risk of concurrent use. If the declaration cycle of a pooled object spans more than one method, you should manually create the pool and manage the object manually.

    Pooling will perform a simple persistence check at compile time, and will not perform pooling operations on the pooled objects that are checked out. However, it should be noted that this kind of checking can only check some simple persistence operations, and can not check the persistence operations in complex cases, such as when you call another method in the current method to pass in a pooled object instance, and then carry out the persistence operation in the called method. So fundamentally you still need to pay attention to yourself and avoid saving pooled objects persistently.

  4. Assemblies that require compile-time replacement by object pooling operations require a reference to the

    The principle of Pooling is to check the MSIL of all methods (some methods can also be selected by configuration) at compile time, and check all newobj operations to complete the object pooling replacement operation, triggered by the operation is completed through the Fody to add a MSBuild task, and only the current assembly directly references the Fody to be able to complete the addition of the MSBuild task. operation. The MSBuild task can be added only if the current assembly references Fody directly.

  5. Considerations when using multiple Fody plug-ins at the same time

    When a project references a Fody plugin, it will automatically generate afile, if thefile already exists and then references another Fody plugin, then compiles it, the new plugin will not be appended to thefile, which needs to be configured manually. Also when referencing multiple Fody plugins you need to be aware that they are in theThe order in which theThe order corresponds to the plug-in execution order. Some Fody plug-ins may have cross-functionality, and different orders may produce different effects.

AspectN

At the end of the article to mention AspectN, before it has been called AspectJ-Like expression, because it is indeed designed with reference to the format of the AspectJ expression, but has been so called is not a solution, and is now renamed in accordance with the customary AspectN expression (searched, .NET does not have this term, there should be no conflict). AspectN first originated in Meat Loaf 2.0 to provide more accurate entry point matching, and is now once again put to use in Pooling.

When using Fody or directly developing MSBuild task plugins, finding the type or method that needs to be modified is always a priority. The most common way is to locate the type and method through the Attribute metadata , but this basically determines that you must modify the code to add Attribute application , which is intrusive.AspectN provides a non-intrusive type and method matching mechanism , the string can carry endless information to give AspectN unlimited fine-tuning matching possibilities . Many Fody plug-ins can use AspectN to achieve non-intrusive code weaving , for example , you can use AspectN to achieve through the configuration to specify which types or methods need to apply ConfigureAwait, which do not need .

AspectN doesn't depend on Fody, only on, if you are using Fody or, maybe try AspectN (/inversionhourglass/AspectN is a Shared Project, it does not publish NuGet, nor does it depend on a specific version, to use AspectN you need to clone AspectN locally as a shared project directly referenced, if your project uses git for management, then it is recommended that AspectN as a submodule be Add AspectN as a submodule to your repository (seeRougamocap (a poem)Pooling)。