zoukankan      html  css  js  c++  java
  • netty源码解析(4.0)-27 ByteBuf内存池:PoolArena-PoolThreadCache

      前面两章分析的PoolChunk和PoolSubpage,从功能上来说已经可以直接拿来用了。但直接使用这个两个类管理内存在高频分配/释放内存场景下会有性能问题,PoolChunk分配内存时算法复杂度最高的是allocateNode方法,释放内存时算法复杂度最高的是free方法。 PoolChunk中二叉树的高度是maxOrder,  那么算法负责度是O(maxOrder),netty默认的maxOrder是11。另外,PoolChunk不是线程安全的,如果在多线程环境下需要加锁调用,这个开销比算法开销还要大。

      为了解决性能问题,netty设计PoolThreadCache(PTC)。每个线程持有一个PTC对象,每个PTC对象持有多个MemoryRegionCache(MRC)对象。MRC对象缓存了大小相同的内存块。PooledByteBuf在释放内存时,会把内存缓存到,MRC对象中,下次分配内存是会优先从MRC中取出缓存的内存。这样,在高频,多线程分配/释放的场景下,可以避免绝大部分PoolChunk算法开销和锁开销。

    cache的设计

      在netty源码解解析(4.0)-25 ByteBuf内存池:PoolArena-PoolChunk中讲到,PoolArena把内存按内存大小把内存分为4中类型。PTC只缓存Tiny,Small, Normal三种内存。PTC内部维护了这三种内存的缓存数组,每种内存有两个数组,分别用来缓存堆内存和直接内存。

        private final MemoryRegionCache<byte[]>[] tinySubPageHeapCaches;
        private final MemoryRegionCache<byte[]>[] smallSubPageHeapCaches;
        private final MemoryRegionCache<ByteBuffer>[] tinySubPageDirectCaches;
        private final MemoryRegionCache<ByteBuffer>[] smallSubPageDirectCaches;
        private final MemoryRegionCache<byte[]>[] normalHeapCaches;
        private final MemoryRegionCache<ByteBuffer>[] normalDirectCaches;

      这几十个数组都在PTC的构造方法中初始化,tinySubPageHeapCahes和tinSubPageDirectCaches的长度,PoolArena.numTinySubpagePools。smallSubPageHeapCaches和smallSubPageDirectCaches的长度是heapArena.numSmallSubpagePools。这个两种类型的cache都是调用createSubPageCaches方法创建。normalHeadpCaches和normalDirectCaches的长度取决于传递给构造方法的maxCachedBufferCapacity参数和PoolArena.pageSize,这种cache是调用createNormalCaches创建。

      PoolArena.numTinySubpagePools和PoolArena.numSmallSubpagePools的含义在netty源码解解析(4.0)-26 ByteBuf内存池:PoolArena-PoolSubpage中有详细的分析。

      下面以createNormalCaches方法的实现为例分析cache的创建:

     1     private static <T> MemoryRegionCache<T>[] createNormalCaches(
     2             int cacheSize, int maxCachedBufferCapacity, PoolArena<T> area) {
     3         if (cacheSize > 0 && maxCachedBufferCapacity > 0) {
     4             int max = Math.min(area.chunkSize, maxCachedBufferCapacity);
     5             int arraySize = Math.max(1, log2(max / area.pageSize) + 1);
     6 
     7             @SuppressWarnings("unchecked")
     8             MemoryRegionCache<T>[] cache = new MemoryRegionCache[arraySize];
     9             for (int i = 0; i < cache.length; i++) {
    10                 cache[i] = new NormalMemoryRegionCache<T>(cacheSize);
    11             }
    12             return cache;
    13         } else {
    14             return null;
    15         }
    16     }

      和createSubPageCaches不同,这个方法没有数组长度的参数,需要自己计算数组长度。

      4,5行,计算cache数组长度。max是最大运行缓存的内存大小,它被限制为<=chunkSize。arraySize是数组的大小。如果max/area.pageSize = 2k, (k<=maxOrder)。log2(max/ares.pageSize) = k。arraySize 最小是1, 最大是maxOrder + 1。这意味着可缓存的内存大小是pageSize * 20, paggeSize * 21, ...... pageSize * 2arraySize-1

      8-11行,创建cache数组,并逐个初始化。

      

      这三种类型的数组有不同的特性,这些特性就是它们缓存内存的方式:

      tinySubPageHeapCahes和tinSubPageDirectCaches:  这两个数组的长度是512 >> 4 = 512/16 = 32。索引idx位置缓存的内存长度normCapacity = idx  * 16, 已知normCapacity,idx = normCapacity/16 = normCapacity >> 4。

      smallSubPageHeapCaches和smallSubPageDirectCaches: 这个数组的长度是log2(pageSize) - 9。索引idx位置缓存内存的长度normCapacity = (1 << 9) * 2idx =29+idx,  已知normCapacity,idx = log2(normCapacity) - 9。

      normalHeadpCaches和normalDirectCaches: 这个数组的长度范围是[1, maxOrder + 1)。索引idx位置缓存的内存长度normCapacity = pageSize * 2idx, 已知normCapacity,idx=log2(normCapacity/pageSize)。

    向cache中添加内存

      在PooledByteBuf是否内存时,会优调用PTC对象的add方法先把内存添添加到cache中:

     1     boolean add(PoolArena<?> area, PoolChunk chunk, long handle, int normCapacity, SizeClass sizeClass) {
     2         MemoryRegionCache<?> cache = cache(area, normCapacity, sizeClass);
     3         if (cache == null) {
     4             return false;
     5         }
     6         return cache.add(chunk, handle);
     7     }
     8 
     9     private MemoryRegionCache<?> cache(PoolArena<?> area, int normCapacity, SizeClass sizeClass) {
    10         switch (sizeClass) {
    11         case Normal:
    12             return cacheForNormal(area, normCapacity);
    13         case Small:
    14             return cacheForSmall(area, normCapacity);
    15         case Tiny:
    16             return cacheForTiny(area, normCapacity);
    17         default:
    18             throw new Error();
    19         }
    20     }

      2行,调用cache方法找定位到MRC对象。

      6行,把内存添加MRC对象。

      10-19行,根据sizeClass调用不同的方法定位MRC对象。这里的sizeClass是根据normCapacity得到的,

        normCapacity < 512: sizeClass = Tiny

        512 <= normCapacity < pageSize: sizeClass = Small

        pageSize <= normCapacity < chunkSize: sizeClass = Nomral

      接下来看看这三个用来定位MRC对象的方法是如何实现的。首先来看cacheForTiny:

     1     private MemoryRegionCache<?> cacheForTiny(PoolArena<?> area, int normCapacity) {
     2         int idx = PoolArena.tinyIdx(normCapacity);
     3         if (area.isDirect()) {
     4             return cache(tinySubPageDirectCaches, idx);
     5         }
     6         return cache(tinySubPageHeapCaches, idx);
     7     }
     8 
     9     private static <T> MemoryRegionCache<T> cache(MemoryRegionCache<T>[] cache, int idx) {
    10         if (cache == null || idx > cache.length - 1) {
    11             return null;
    12         }
    13         return cache[idx];
    14     }

      第2行, 计算数组的索引 idx = normapCapacity >> 4。

      第4,6行调用的cache实现代码在9-14行。把MRC对象从数组中取出。

      cacheForSmall,cacheForNormal方法和cacheForTiny类似,不同的是计算idx的方法。

     1     private MemoryRegionCache<?> cacheForSmall(PoolArena<?> area, int normCapacity) {
     2         int idx = PoolArena.smallIdx(normCapacity);
     3         if (area.isDirect()) {
     4             return cache(smallSubPageDirectCaches, idx);
     5         }
     6         return cache(smallSubPageHeapCaches, idx);
     7     }
     8 
     9     private MemoryRegionCache<?> cacheForNormal(PoolArena<?> area, int normCapacity) {
    10         if (area.isDirect()) {
    11             int idx = log2(normCapacity >> numShiftsNormalDirect);
    12             return cache(normalDirectCaches, idx);
    13         }
    14         int idx = log2(normCapacity >> numShiftsNormalHeap);
    15         return cache(normalHeapCaches, idx);
    16     }

      第2行计算idx方法和第11行类似: log2(val),  初始化res=0,循环计算(val >>> 1) == 0 ? res : res += 1。当res不变时返回,这个是就是log2(val)的值。 

      第11行,numShiftsNormalDirect = log2(pageSize),  normCapacity >> numShiftsNormalDirect = normCapacity/pageSize。第14行同理。 

    从cache中分配内存

      分配内存的过程也依赖前面分析的几个cacheForXXX方法:

     1 /**
     2      * Try to allocate a tiny buffer out of the cache. Returns {@code true} if successful {@code false} otherwise
     3      */
     4     boolean allocateTiny(PoolArena<?> area, PooledByteBuf<?> buf, int reqCapacity, int normCapacity) {
     5         return allocate(cacheForTiny(area, normCapacity), buf, reqCapacity);
     6     }
     7 
     8     /**
     9      * Try to allocate a small buffer out of the cache. Returns {@code true} if successful {@code false} otherwise
    10      */
    11     boolean allocateSmall(PoolArena<?> area, PooledByteBuf<?> buf, int reqCapacity, int normCapacity) {
    12         return allocate(cacheForSmall(area, normCapacity), buf, reqCapacity);
    13     }
    14 
    15     /**
    16      * Try to allocate a small buffer out of the cache. Returns {@code true} if successful {@code false} otherwise
    17      */
    18     boolean allocateNormal(PoolArena<?> area, PooledByteBuf<?> buf, int reqCapacity, int normCapacity) {
    19         return allocate(cacheForNormal(area, normCapacity), buf, reqCapacity);
    20     }

      allocate方法实现比较简单,它调用MRC对象的allocate方法为PooledByteBuf分配内存,并初始化。

      

    MemoryRegionCache(MRC)实现

      PTC使用MRC对象缓存大小相同的内存块。它内部维护了一个队列,队列中保存的是大小从PoolChunk中分配的内存块。它有两个最重要的属性:

      Queue<Entry<T>> queue:  缓存内存块的队列。

      SizeClass sizeClass:  内存的类型, Tiny, Small或Normal。

      MRC有三个类:

      MemoryRegionCache<T>: 抽象类,定义了抽象方法initBuf。

      SubPageMemoryRegionCache<T>: 实现initBuf方法,使用Tiny或Small内存初始化PooledByteBuf。

      NormalMemoryRegionCache<T>: 实现initBuf方法,使用Normal内存初始化PooledByteBuf。

      MRC的主要功能是:缓存一块内存,把PoolChunk, handle代表的内存添加到queue中。从queue中取出一块内存,调用initBuf方法初始化PooledByteBuf。

    缓存内存

     1         public final boolean add(PoolChunk<T> chunk, long handle) {
     2             Entry<T> entry = newEntry(chunk, handle);
     3             boolean queued = queue.offer(entry);
     4             if (!queued) {
     5                 // If it was not possible to cache the chunk, immediately recycle the entry
     6                 entry.recycle();
     7             }
     8 
     9             return queued;
    10         }

      这个方法用来吧chunk和handle代表的内存添加的queue中。Entry<T>是MRC的内部类,实现很简单,只是为了能在queue中缓存chunk和handle数据,它使用了Recycler功能,把自己放进了可循环使用的对象池中。

    从取出一块内存,并初始化PooledByteBuf

     1         public final boolean allocate(PooledByteBuf<T> buf, int reqCapacity) {
     2             Entry<T> entry = queue.poll();
     3             if (entry == null) {
     4                 return false;
     5             }
     6             initBuf(entry.chunk, entry.handle, buf, reqCapacity);
     7             entry.recycle();
     8 
     9             // allocations is not thread-safe which is fine as this is only called from the same thread all time.
    10             ++ allocations;
    11             return true;
    12         }

      2-5行,取出一块内存。

      6行,初始化PooledByteBuf。

      下面是两个initBuf实现。

     1        //SubPageMemoryRegionCache<T>
     2         @Override
     3         protected void initBuf(
     4                 PoolChunk<T> chunk, long handle, PooledByteBuf<T> buf, int reqCapacity) {
     5             chunk.initBufWithSubpage(buf, handle, reqCapacity);
     6         } 
     7     
     8         //NormalMemoryRegionCache<T>
     9         @Override
    10         protected void initBuf(
    11                 PoolChunk<T> chunk, long handle, PooledByteBuf<T> buf, int reqCapacity) {
    12             chunk.initBuf(buf, handle, reqCapacity);
    13         }    

      由5, 12行,可以看到,这两个方法只是用来调用PoolChunk实现的PooledByteBuf初始化方法。

  • 相关阅读:
    2019年2月8日训练日记(文件操作知识点小结)
    2019年2月7日训练日记
    2019年2月6日训练日记
    2019年2月5日训练日记
    2019年2月4日训练日记(递归学习小结)
    【Java】Java中的IO流
    【Java】Java中线程的使用
    【Java】Java图形化用户界面-GUI
    【Java】Java中的集合类
    C++程序学习之实现手机通讯录功能模拟
  • 原文地址:https://www.cnblogs.com/brandonli/p/11649251.html
Copyright © 2011-2022 走看看