首页 > 编程语言 >C#中通过ObjectPool重用对象提高程序性能

C#中通过ObjectPool重用对象提高程序性能

时间:2024-12-10 11:32:07浏览次数:8  
标签:11 14 16 C# 重用 对象 ObjectPool public

环境说明:

  • .NET 8.0
  • Microsoft.Extensions.DependencyInjection v9.0.0
  • Microsoft.Extensions.ObjectPool v9.0.0

ObjectPool重用对象

先看微软官方文档的描述:

Microsoft.Extensions.ObjectPool它支持将一组对象保留在内存中以供重用,而不是允许对对象进行垃圾回收

如果要管理的对象具有以下特征,应用可能希望使用对象池:

  • 分配/初始化成本高昂
  • 表示有限资源
  • 可预见地频繁使用

比如使用对象池来重用StringBuilder实例。 StringBuilder分配并管理自己的缓冲区来保存字符数据。如果经常使用StringBuilder来实现功能,重用这些对象会带来性能优势

对象池并不总是能提高性能:

  • 除非对象的初始化成本很高,否则从池中获取对象通常较慢
  • 在池解除分配之前,池管理的对象无法解除分配
  • 仅在使用应用或库的真实场景收集性能数据后才使用对象池

注意:ObjectPool不限制分配的对象数量,但限制保留的对象数量

使用ObjectPool

调用Get获取对象,调用Return返回对象。 不必返回每个对象。 如果某个对象未返回,系统将对其进行垃圾回收

使用示例

这个示例展示了如何在控制台应用程序中使用对象池来重用大型缓冲区,避免重复分配内存,提高性能。每次计算哈希值时都会重用同一个缓冲区对象,而不是创建新的

要点:

  • ObjectPoolProvider添加到依赖项注入 (DI) 容器
  • 实现IResettable接口,以在返回到对象池时自动清除缓冲区的内容

IResettable接口的作用是当对象被返回到对象池时:

  • 对象池会自动调用TryReset()方法
  • 可以自定义清除缓冲区中的敏感数据
  • 为下次使用做准备
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.ObjectPool;
using System.Security.Cryptography;

class Program
{
    public class ReusableBuffer : IResettable
    {
        private readonly string _id = Guid.NewGuid().ToString("N").Substring(0, 6);
        private int _useCount = 0;
        public byte[] Data { get; } = new byte[1024 * 1024]; // 1 MB

        public ReusableBuffer()
        {
            Console.WriteLine($"创建新的缓冲区 ID: {_id}");
        }

        public bool TryReset()
        {
            _useCount++;
            Console.WriteLine($"重置缓冲区 ID: {_id}, 使用次数: {_useCount}");
            Array.Clear(Data); // 只重置数据内容
            return true;
        }

        public string GetId() => $"{_id} (使用次数: {_useCount})";
    }

    static void Main(string[] args)
    {
        // 设置依赖注入
        var services = new ServiceCollection();

        // 注册对象池提供程序
        services.AddSingleton<ObjectPoolProvider, DefaultObjectPoolProvider>();

        // 注册 ReusableBuffer 的对象池
        services.AddSingleton<ObjectPool<ReusableBuffer>>(serviceProvider =>
        {
            var provider = serviceProvider.GetRequiredService<ObjectPoolProvider>();
            var policy = new DefaultPooledObjectPolicy<ReusableBuffer>();
            return provider.Create(policy);
        });

        var serviceProvider = services.BuildServiceProvider();

        // 获取对象池
        var bufferPool = serviceProvider.GetRequiredService<ObjectPool<ReusableBuffer>>();

        // 测试哈希计算
        Console.WriteLine("输入字符串计算哈希值 (输入 'exit' 退出):");

        while (true)
        {
            Console.Write("\n请输入字符串: ");
            string input = Console.ReadLine() ?? "";

            if (input.ToLower() == "exit")
                break;

            var hash = CalculateHash(input, bufferPool);
            Console.WriteLine($"Hash: {hash}");
        }
    }

    static string CalculateHash(string input, ObjectPool<ReusableBuffer> bufferPool)
    {
        var buffer = bufferPool.Get();
        Console.WriteLine($"获取缓冲区 ID: {buffer.GetId()}");

        try
        {
            // 将输入字符串转换为字节并存储在缓冲区中
            for (var i = 0; i < input.Length; i++)
            {
                buffer.Data[i] = (byte)input[i];
            }

            Span<byte> hash = stackalloc byte[32];
            SHA256.HashData(buffer.Data.AsSpan(0, input.Length), hash);
            return Convert.ToHexString(hash);
        }
        finally
        {
            // 归还缓冲区到对象池
            Console.WriteLine($"归还缓冲区 ID: {buffer.GetId()}");
            bufferPool.Return(buffer);
        }
    }
}

程序结果:

输入字符串计算哈希值 (输入 'exit' 退出):

请输入字符串: Hello World
创建新的缓冲区 ID: f493ed
获取缓冲区 ID: f493ed (使用次数: 0)
归还缓冲区 ID: f493ed (使用次数: 0)
重置缓冲区 ID: f493ed, 使用次数: 1
Hash: A591A6D40BF420404A011733CFB7B190D62C65BF0BCDA32B57B277D9AD9F146E

请输入字符串: Test123
获取缓冲区 ID: f493ed (使用次数: 1)
归还缓冲区 ID: f493ed (使用次数: 1)
重置缓冲区 ID: f493ed, 使用次数: 2
Hash: D9B5F58F0B38198293971865A14074F59EBA3E82595BECBE86AE51F1D9F1F65E

请输入字符串: exit

扩展类

前面我们通过实现IResettable接口来自动回收重用,如果需要自定义对象创建、复杂初始化或自定义回收重用逻辑的场景,通过策略模式实现,相比通过接口实现,扩展性更强

using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.ObjectPool;
using System;


namespace Microsoft.Extensions.ObjectPool
{
    internal class PooledObjectByFuncPolicy<T> : PooledObjectPolicy<T> where T : class
    {
        private readonly Func<T> _createfunc;
        private readonly Func<T, bool> _returnfunc;
        private static int _returnCount = 0;
        private static int _createCount = 0;  // 添加创建计数

        public PooledObjectByFuncPolicy(Func<T> createfunc, Func<T, bool> returnfunc)
        {
            _createfunc = createfunc;
            _returnfunc = returnfunc;
        }

        public override T Create()
        {
            int count = Interlocked.Increment(ref _createCount);
            Console.WriteLine($"[{DateTime.Now:HH:mm:ss}] Policy: 创建新对象 (第{count}次创建)");
            return _createfunc.Invoke();
        }

        public override bool Return(T obj)
        {
            int returnCount = Interlocked.Increment(ref _returnCount);
            bool canReuse = _returnfunc.Invoke(obj);
            Console.WriteLine(
                $"[{DateTime.Now:HH:mm:ss}] Policy: 对象返回池中 " +
                $"(第{returnCount}次返回, {(canReuse ? "可以重用" : "不可重用")})");
            return canReuse;
        }
    }
    public static class ObjectPoolExtension
    {
        /// <summary>
        /// 添加类型为<typeparamref name="T"/>至对象池进行复用
        /// </summary>
        /// <typeparam name="T"></typeparam>
        /// <param name="services"></param>
        /// <returns></returns>
        public static IServiceCollection AddObjectPool<T>(this IServiceCollection services) where T : class,new ()
        {
            return services.AddSingleton(s =>
            {
                var provider = s.GetRequiredService<ObjectPoolProvider>();
                return provider.Create<T>();
            });
        }
        /// <summary>
        /// 将<typeparamref name="T"/>类型添加至对象池, 并使用<paramref name="_create"/>进行创建初始化, 使用<paramref name="_returnfunc"/>归还
        /// </summary>
        /// <typeparam name="T"></typeparam>
        /// <param name="services"></param>
        /// <param name="_create"></param>
        /// <param name="_returnfunc"></param>
        /// <returns></returns>
        public static IServiceCollection AddObjectPool<T>(this IServiceCollection services, Func<T> _create, Func<T, bool> _returnfunc) where T : class
        {
            return services.AddSingleton(s =>
            {
                var provider = s.GetRequiredService<ObjectPoolProvider>();
                return provider.Create(new PooledObjectByFuncPolicy<T>(_create, _returnfunc));
            });
        }
        /// <summary>
        ///  将<typeparamref name="T"/>类型添加至对象池, 并使用<paramref name="_create"/>进行创建初始化
        /// </summary>
        /// <typeparam name="T"></typeparam>
        /// <param name="services"></param>
        /// <param name="_create"></param>
        /// <returns></returns>
        public static IServiceCollection AddObjectPool<T>(this IServiceCollection services, Func<T> _create) where T : class
        {
            return services.AddObjectPool(_create, t => true);
        }
    }
}
  1. PooledObjectByFuncPolicy类

这是一个对象池策略类,负责:

  • 通过传入的Func委托来创建对象
  • 通过传入的Func<T, bool>委托来决定对象是否可以被回收重用
  1. ObjectPoolExtension类

提供了三个扩展方法,用于在依赖注入容器中注册对象池:

// 最简单的注册方式,适用于有无参构造函数的类
public static IServiceCollection AddObjectPool<T>(this IServiceCollection services) 
    where T : class, new()

// 完整的注册方式,可以自定义对象的创建和回收逻辑
public static IServiceCollection AddObjectPool<T>(
    this IServiceCollection services, 
    Func<T> _create, 
    Func<T, bool> _returnfunc)

// 简化版注册方式,只需要提供创建逻辑
public static IServiceCollection AddObjectPool<T>(
    this IServiceCollection services, 
    Func<T> _create)

应用实例

using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.ObjectPool;
using System;
using System.Threading.Tasks;

namespace Microsoft.Extensions.ObjectPool
{
    internal class PooledObjectByFuncPolicy<T> : PooledObjectPolicy<T> where T : class
    {
        private readonly Func<T> _createfunc;
        private readonly Func<T, bool> _returnfunc;
        private static int _returnCount = 0;
        private static int _createCount = 0;  // 添加创建计数

        public PooledObjectByFuncPolicy(Func<T> createfunc, Func<T, bool> returnfunc)
        {
            _createfunc = createfunc;
            _returnfunc = returnfunc;
        }

        public override T Create()
        {
            int count = Interlocked.Increment(ref _createCount);
            Console.WriteLine($"[{DateTime.Now:HH:mm:ss}] Policy: 创建新对象 (第{count}次创建)");
            return _createfunc.Invoke();
        }

        public override bool Return(T obj)
        {
            int returnCount = Interlocked.Increment(ref _returnCount);
            bool canReuse = _returnfunc.Invoke(obj);
            Console.WriteLine(
                $"[{DateTime.Now:HH:mm:ss}] Policy: 对象返回池中 " +
                $"(第{returnCount}次返回, {(canReuse ? "可以重用" : "不可重用")})");
            return canReuse;
        }
    }

    public static class ObjectPoolExtension
    {
        public static IServiceCollection AddObjectPool<T>(this IServiceCollection services) where T : class, new()
        {
            return services.AddSingleton(s =>
            {
                var provider = s.GetRequiredService<ObjectPoolProvider>();
                return provider.Create<T>();
            });
        }

        public static IServiceCollection AddObjectPool<T>(this IServiceCollection services, Func<T> _create, Func<T, bool> _returnfunc) where T : class
        {
            return services.AddSingleton(s =>
            {
                var provider = s.GetRequiredService<ObjectPoolProvider>();
                return provider.Create(new PooledObjectByFuncPolicy<T>(_create, _returnfunc));
            });
        }

        public static IServiceCollection AddObjectPool<T>(this IServiceCollection services, Func<T> _create) where T : class
        {
            return services.AddObjectPool(_create, t => true);
        }
    }
    // 模拟一个耗资源的对象
    public class ExpensiveObject : IDisposable
    {
        public string Id { get; }
        private bool _isDisposed;
        private static int _totalObjects = 0;
        private readonly int _objectNumber;

        public ExpensiveObject()
        {
            _objectNumber = Interlocked.Increment(ref _totalObjects);
            Id = Guid.NewGuid().ToString("N");
            // 模拟耗时的初始化
            Thread.Sleep(5000);
            Console.WriteLine($"[{DateTime.Now:HH:mm:ss}] 创建新对象 #{_objectNumber}: {Id}");
        }

        public void DoWork()
        {
            Console.WriteLine($"[{DateTime.Now:HH:mm:ss}] 对象 #{_objectNumber} ({Id}) 执行工作");
        }

        public void Dispose()
        {
            if (!_isDisposed)
            {
                Console.WriteLine($"[{DateTime.Now:HH:mm:ss}] 对象 #{_objectNumber} ({Id}) 被释放");
                _isDisposed = true;
            }
        }
    }

    // 测试程序
    public class Program
    {
        static async Task Main(string[] args)
        {
            // 1. 配置依赖注入
            var services = new ServiceCollection();
            // 注册默认对象池提供程序
            services.AddSingleton<ObjectPoolProvider, DefaultObjectPoolProvider>();

            // 使用自定义策略注册对象池
            services.AddObjectPool<ExpensiveObject>(
                () => new ExpensiveObject(),
                obj =>
                {
                    Console.WriteLine($"[{DateTime.Now:HH:mm:ss}] 检查对象 {obj.Id} 是否可以重用");
                    return true; // 是否允许重用
                }
            );

            using var serviceProvider = services.BuildServiceProvider();

            // 2. 获取对象池
            var objectPool = serviceProvider.GetRequiredService<ObjectPool<ExpensiveObject>>();

            Console.WriteLine("开始测试对象池...\n");

            for (int i = 0; i < 5; i++)
            {
                await Task.Run(() =>
                {
                    Console.WriteLine($"\n[{DateTime.Now:HH:mm:ss}] 开始第 {i + 1} 次操作");
                    // 从池中获取对象
                    var obj = objectPool.Get();
                    try
                    {
                        // 使用对象
                        obj.DoWork();
                        Thread.Sleep(100);// 模拟工作时间
                    }
                    finally
                    {
                        Console.WriteLine($"[{DateTime.Now:HH:mm:ss}] 准备归还对象到池中");
                        // 归还对象到池中
                        objectPool.Return(obj);
                    }
                    Console.WriteLine("-------------------");
                });
            }
            Console.WriteLine("\n测试完成!");
        }
    }
}

“是否允许重用”设置为ture时,结果如下:

开始测试对象池...


[11:14:25] 开始第 1 次操作
[11:14:25] Policy: 创建新对象 (第1次创建)
[11:14:30] 创建新对象 #1: d1ef227f13f443c887574b0a826cf954
[11:14:30] 对象 #1 (d1ef227f13f443c887574b0a826cf954) 执行工作
[11:14:30] 准备归还对象到池中
[11:14:30] 检查对象 d1ef227f13f443c887574b0a826cf954 是否可以重用
[11:14:30] Policy: 对象返回池中 (第1次返回, 可以重用)
-------------------

[11:14:30] 开始第 2 次操作
[11:14:30] 对象 #1 (d1ef227f13f443c887574b0a826cf954) 执行工作
[11:14:30] 准备归还对象到池中
[11:14:30] 检查对象 d1ef227f13f443c887574b0a826cf954 是否可以重用
[11:14:30] Policy: 对象返回池中 (第2次返回, 可以重用)
-------------------

[11:14:30] 开始第 3 次操作
[11:14:30] 对象 #1 (d1ef227f13f443c887574b0a826cf954) 执行工作
[11:14:30] 准备归还对象到池中
[11:14:30] 检查对象 d1ef227f13f443c887574b0a826cf954 是否可以重用
[11:14:30] Policy: 对象返回池中 (第3次返回, 可以重用)
-------------------

[11:14:30] 开始第 4 次操作
[11:14:30] 对象 #1 (d1ef227f13f443c887574b0a826cf954) 执行工作
[11:14:30] 准备归还对象到池中
[11:14:30] 检查对象 d1ef227f13f443c887574b0a826cf954 是否可以重用
[11:14:30] Policy: 对象返回池中 (第4次返回, 可以重用)
-------------------

[11:14:30] 开始第 5 次操作
[11:14:30] 对象 #1 (d1ef227f13f443c887574b0a826cf954) 执行工作
[11:14:30] 准备归还对象到池中
[11:14:30] 检查对象 d1ef227f13f443c887574b0a826cf954 是否可以重用
[11:14:30] Policy: 对象返回池中 (第5次返回, 可以重用)
-------------------

测试完成!
[11:14:30] 对象 #1 (d1ef227f13f443c887574b0a826cf954) 被释放

通过日志的时间,我们得知只有第一次创建对象比较耗时

“是否允许重用”设置为false时,结果如下:

开始测试对象池...


[11:16:03] 开始第 1 次操作
[11:16:03] Policy: 创建新对象 (第1次创建)
[11:16:08] 创建新对象 #1: 80923f55ac2445d3a62685e4166ca436
[11:16:08] 对象 #1 (80923f55ac2445d3a62685e4166ca436) 执行工作
[11:16:08] 准备归还对象到池中
[11:16:08] 检查对象 80923f55ac2445d3a62685e4166ca436 是否可以重用
[11:16:08] Policy: 对象返回池中 (第1次返回, 不可重用)
[11:16:08] 对象 #1 (80923f55ac2445d3a62685e4166ca436) 被释放
-------------------

[11:16:08] 开始第 2 次操作
[11:16:08] Policy: 创建新对象 (第2次创建)
[11:16:13] 创建新对象 #2: 5b15f09ff293427a93fe7dce01cf9d3e
[11:16:13] 对象 #2 (5b15f09ff293427a93fe7dce01cf9d3e) 执行工作
[11:16:13] 准备归还对象到池中
[11:16:13] 检查对象 5b15f09ff293427a93fe7dce01cf9d3e 是否可以重用
[11:16:13] Policy: 对象返回池中 (第2次返回, 不可重用)
[11:16:13] 对象 #2 (5b15f09ff293427a93fe7dce01cf9d3e) 被释放
-------------------

[11:16:13] 开始第 3 次操作
[11:16:13] Policy: 创建新对象 (第3次创建)
[11:16:18] 创建新对象 #3: ef65bab6f6a142dbb3a16f7daa668514
[11:16:18] 对象 #3 (ef65bab6f6a142dbb3a16f7daa668514) 执行工作
[11:16:18] 准备归还对象到池中
[11:16:18] 检查对象 ef65bab6f6a142dbb3a16f7daa668514 是否可以重用
[11:16:18] Policy: 对象返回池中 (第3次返回, 不可重用)
[11:16:18] 对象 #3 (ef65bab6f6a142dbb3a16f7daa668514) 被释放
-------------------

[11:16:18] 开始第 4 次操作
[11:16:18] Policy: 创建新对象 (第4次创建)
[11:16:23] 创建新对象 #4: 81b791bb198f4d618ef93a6567618cec
[11:16:23] 对象 #4 (81b791bb198f4d618ef93a6567618cec) 执行工作
[11:16:23] 准备归还对象到池中
[11:16:23] 检查对象 81b791bb198f4d618ef93a6567618cec 是否可以重用
[11:16:23] Policy: 对象返回池中 (第4次返回, 不可重用)
[11:16:23] 对象 #4 (81b791bb198f4d618ef93a6567618cec) 被释放
-------------------

[11:16:23] 开始第 5 次操作
[11:16:23] Policy: 创建新对象 (第5次创建)
[11:16:28] 创建新对象 #5: 60d4737360464aa3bb16d2894df3386c
[11:16:28] 对象 #5 (60d4737360464aa3bb16d2894df3386c) 执行工作
[11:16:28] 准备归还对象到池中
[11:16:28] 检查对象 60d4737360464aa3bb16d2894df3386c 是否可以重用
[11:16:28] Policy: 对象返回池中 (第5次返回, 不可重用)
[11:16:28] 对象 #5 (60d4737360464aa3bb16d2894df3386c) 被释放
-------------------

测试完成!

通知日志,可以看出,每次都会创建新对象,非常耗时

总结

对象池的核心是对象重用,而对象池的目的是避免频繁创建和销毁对象

参考

标签:11,14,16,C#,重用,对象,ObjectPool,public
From: https://www.cnblogs.com/vinciyan/p/18596981

相关文章

  • 【数据结构与算法】回溯算法:LeetCode“排列问题” 求解,解释并模拟递归+回溯的遍历过程
      【作者自述:记录学习笔记,既然写了就让更多的人看到吧!欢迎大家关注交流学习,一步一个脚印持续更新!】【更多推荐笔记】【数据结构与算法】动态规划:解密“完全背包问题”的真相!附LeetCode四大问题的实现-CSDN博客【数据结构与算法】动态规划:解密“0-1背包问题”的真相!附LeetC......
  • PHP版谷歌验证 (Google Authenticator)
    地址https://github.com/PHPGangsta/GoogleAuthenticator示例index.php<?phprequire_once'PHPGangsta/GoogleAuthenticator.php';$ga=newPHPGangsta_GoogleAuthenticator();//创建一个新的"安全密匙SecretKey"//把本次的"安全密匙SecretKey"入库,和账户关......
  • 【comfyui教程】开源文生图模型之光!ComfyUI - AuraFlow详细搭建教程
    前言被广大网友誉为“开源文生图模型之光”的AuraFlow详细搭建教程来啦!下面将介绍ComfyUI-AuraFlow在算家云的搭建流程以及本地部署教程,希望能帮助到大家~一模型介绍AuraFlow是唯一一个真正开源的文生图模型,由Fal团队开源,其代码和权重都放在了FOSS许可证下。模......
  • Vmware系列&虚拟机系列【仅供参考】:windows sever 2012R2虚拟机蓝屏报错CRITICAL_STRU
    windowssever2012R2虚拟机蓝屏报错CRITICAL_STRUCTURE_CORRUPTIONwindowssever2012R2虚拟机蓝屏报错CRITICAL_STRUCTURE_CORRUPTION在ESXI5.0主机上windowssever2012R2和windowsserver2016虚拟机出现经常蓝屏重启。在VMware知识库查找解决办法,升级ESXI......
  • Vmware系列&虚拟机系列【仅供参考】:vCenter8.0以上无DNS环境部署
    vCenter8.0以上无DNS环境部署vCenter8.0以上无DNS环境部署无DNS解决办法1.在安装-第1阶段:部署vCenterSever的第7步配置网络设置注意FQDN和IP地址,DNS服务器地址都统一填vCenter的IP地址。2.安装部署第一阶段时,不能点继续。3.打开浏览器登入部署的ESXI主机上......
  • 【Linux】自定义开机启动service
    【Linux】自定义开机启动service1.确认SELinux是否开启1.1临时关闭SELinux。临时禁用SELinux后,系统会立即生效,但重新启动后会恢复为启用状态。要临时禁用SELinux,请使用以下命令:sudosetenforce0这个命令将SELinux模式从Enforcing设置为Permissive,即系统会记录......
  • mc日常维护
     1.设置服务器/opt/minio_client/mcaliassetmyminio03http://192.168.1.135:9000/minioadminminioadmin 2.查看设置minio服务器[root@yunwei-k8s-devsoft]#/opt/minio_client/mcaliaslistmyminioURL:http://192.168.1.246:9000AccessKey:rootSe......
  • python给excel单元格批量生成超链接(panda+openpyxl)
    最近做些数据处理,要给Excel表单元格根据规则批量生成超链接,VBA看起来好麻烦,就还是用python处理了,选了一圈发现panda+openpyxl能较好满足需求。我需要根据表格1的【代码】【名称】列,调用函数生成链接到新表格的【链接1】【链接2】列:源文件:目标文件(含有链接):直接上代码。......
  • JDBC详解
    JDBC详解1、什么是JDBC,有什么作用,优缺点是什么?JDBC(JavaDatabaseConnectivity)是一个JavaAPI,它提供了一种标准的方法,允许Java程序连接到数据库并执行SQL语句。JDBC为不同类型的数据库提供了统一的访问方式,包括关系型数据库如MySQL、Oracle、SQLServer等。作用:数据库连......
  • 【C#】禁用本地连接和修改IE设置
    原文链接:https://www.cnblogs.com/stalwart/archive/2011/09/20/2182663.html///<summary>///实现启用或停用本地网络链接///</summary>///<paramname="netWorkName">本地连接名称</param>///<paramname......