zoukankan      html  css  js  c++  java
  • .Net性能调优-ArrayPool

    定义

    高性能托管数组缓冲池,可重复使用,用租用空间的方式代替重新分配数组空间的行为

    好处

    可以在频繁创建和销毁数组的情况下提高性能,减少垃圾回收器的压力

    使用

    • 获取缓冲池实例:Create/Shared var pool=ArrayPool[byte].Shared
    • 调用缓冲池实例Rent()函数,租用缓冲区空间 byte[] array=pool.Rent(1024)
    • 调用缓冲池实例Return(array[T])函数,归还租用的空间 pool.Return(array)

    Shared

    Shared返回为一个静态共享实例,实际返回了一个TlsOverPerCoreLockedStacksArrayPool

    internal sealed class TlsOverPerCoreLockedStacksArrayPool<T> : ArrayPool<T>
    {
        private static readonly TlsOverPerCoreLockedStacksArrayPool<T> s_shared = new TlsOverPerCoreLockedStacksArrayPool<T>();
    
        public static ArrayPool<T> Shared => s_shared;
    }
    

    特点

    • 租用数组长度不可超过 2^20( 1024*1024 = 1 048 576),否则会从GC中重新开辟内存空间
    • Rent租用数组实际返回的长度可能比请求的长度大,返回长度一是(16*2^n)
    • Return归还缓冲区的时候,如果不设置clearArray,下一个租用者可能会看到之前的填充的值(在返回的数组长度刚好是下一个租用者请求的长度时会被看到)
    • 缓冲池的内存释放不是实时释放,在缓冲区空闲时,大概10到20秒之后,会随着第2代GC一起释放,分批释放
    • 并发数量持续增长时,缓冲池占用的内存空间也会持续增长,而且似乎没有上限

    耗时对比

    private static void TimeMonitor()
    {
        //随机生成3000个数组的长度值
        var sizes = new int[30000];
        Parallel.For(0, 10000, x => { sizes[x] = new Random().Next(1024 * 800, 1024 * 1024); });
    
        //缓冲池方式租用数组
        var gcAllocate0 = GC.GetTotalAllocatedBytes();
        var watch = new Stopwatch();
        Console.WriteLine("start");
        watch.Start();
        for (int i = 0; i < 10000; i++)
        {
            //CreateArrayByPool(ArrayPool<int>.Shared, 1024 * 1024,sizes[i], false);
    
            var arr = ArrayPool<int>.Shared.Rent(sizes[i]);
            for (int j = 0; j < sizes[i]; j++)
            {
                arr[j] = i;
            }
            ArrayPool<int>.Shared.Return(arr, true);
        }
        var time1 = watch.ElapsedMilliseconds;
        var gcAllocate1 = GC.GetTotalAllocatedBytes(true);
    
        //new 方式分配数组空间
        watch.Restart();
        for (int i = 0; i < 30000; i++)
        {
            //CreateArrayDefault(i, sizes[i], false);
            var arr = new int[sizes[i]];
            for (int j = 0; j < sizes[i]; j++)
            {
                arr[j] = i;
            }
        }
        var time2 = watch.ElapsedMilliseconds;
        var gcAllocate2 = GC.GetTotalAllocatedBytes(true);
    
        Console.WriteLine("ArrayPool方式创建数组耗时:" + time1 + "  Gc总分配量" + (gcAllocate1 - gcAllocate0));
        Console.WriteLine("默认方式创建数组耗时:" + time2 + "  Gc总分配量" + (gcAllocate2 - gcAllocate1 - gcAllocate0));
    }
    

    内存使用截图:左侧没有波动的横线是缓冲池执行的过程,右侧为手动创建数组的执行过程

    执行结果:

    ArrayPool方式创建数组耗时:17545  Gc总分配量4130800
    默认方式创建数组耗时:26870  Gc总分配量37354100896
    

    示例(前端文件通过后端Api上传OSS)

    private static void PostFileByBytesPool(FormFile file)
    {
        HttpClient client = new HttpClient() { BaseAddress = new Uri("https://fileserver.com") };
    
        var fileLen = (int)file.Length;
        var fileArr = ArrayPool<byte>.Shared.Rent(fileLen);
    
        using var stream = file.OpenReadStream();
        stream.Read(fileArr, 0, fileLen);
    
        MultipartFormDataContent content = new MultipartFormDataContent();
        content.Add(new ByteArrayContent(fileArr, 0, fileLen), "id_" + Guid.NewGuid().ToString(), file.FileName);
    
        client.PostAsync("/myfile/" + file.FileName, content).Wait();
        ArrayPool<byte>.Shared.Return(fileArr, true);
    }
    

    Create()

    ArrayPool的Create()函数会创建一个ConfigurableArrayPool对象

    ConfigurableArrayPool的构造函数接收两个参数

    • maxArrayLength:单次租借的数组最大长度,不可超过1024*1024*1024
    • maxArraysPerBucket:最多可以存在的未归还缓冲区数量

    通过这两个参数可以解决Shared方式的两个问题:

    1. 自定义单个数组的最大长度,可以获取更大的内存空间用来存储大文件等

    2. 限定了数组的长度和最大缓冲区数量,就限定了最大的不可回收内存数量,防止高并发时缓冲池内存持续增长

    示例

    //创建一个自定义缓冲池实例,单个数组最大长度为1024 * 2048,最大可同时租用10个缓冲区
    ArrayPool<int> CustomerArrayPool = ArrayPool<int>.Create(1024 * 2048,10);
    

    与Shared不同的是,如果设置CustomerArrayPool=Null那么在下一次垃圾回收时该缓冲池所占的内存会立马全部释放。

    为防止不可预测的风险,应该保持CustomerArrayPool的存活。

    同时为了防止内存的滥用应该限制CustomerArrayPool的数量

    无论是Create还是Shared的方式来获取缓冲池实例,都应该尽量保证实例所被租借的内存大小基本是差不了太多的,否则归还的内存被复用的命中率就很低了。

  • 相关阅读:
    [原创]项目管理知识体系指南之 10项目沟通管理思维导图
    [原创]项目管理知识体系指南之 9项目人力资源管理思维导图
    [原创]项目管理知识体系指南之 5范围管理思维导图
    [原创]项目管理知识体系指南之 6项目时间管理思维导图
    [原创]2013年测试人员薪水分布图
    [原创]项目管理知识体系指南之 11项目风险管理维导图
    [原创]项目管理知识体系指南之 8项目质量管理思维导图
    [原创]2013年上半年测试技术学习文档任务
    [原创]Linux下网络性能测试Netperf工具介绍及安装
    [原创]数据库安全思维导图
  • 原文地址:https://www.cnblogs.com/bluesummer/p/15263871.html
Copyright © 2011-2022 走看看