类的结构之cache_t

215 阅读1分钟

cache_t的内存结构

类的结构与class_data_bits_t中,我们分析了类的结构,包括isasuperclasscachebits。今天我们来看一下cache_t的结构。

struct objc_class : objc_object {
    // Class ISA;
    Class superclass;
    cache_t cache;             
    class_data_bits_t bits;  
}

查看源码,cache_t的底层结构大致如下,并没有发现特别的东西,那么继续向下看,发现了很多方法与bucket_t有关,查看其结构,诶,发现了sel imp。在这里,也能说明类的cache是用来的缓存方法的,调用过的方法会被缓存在cache_t中,下次调用这个方法可以直接从缓存中取。

struct cache_t {
private:
    explicit_atomic<uintptr_t> _bucketsAndMaybeMask;
    union {
        struct {
            explicit_atomic<mask_t>    _maybeMask;
#if __LP64__
            uint16_t                   _flags;
#endif
            uint16_t                   _occupied;
        };
        explicit_atomic<preopt_cache_t *> _originalPreoptCache;
    };
}
struct bucket_t {
private:
    // IMP-first is better for arm64e ptrauth and no worse for arm64.
    // SEL-first is better for armv7* and i386 and x86_64.
#if __arm64__
    explicit_atomic<uintptr_t> _imp;
    explicit_atomic<SEL> _sel;
#else
    explicit_atomic<SEL> _sel;
    explicit_atomic<uintptr_t> _imp;
#endif
}

LLDB取出cache_t中的方法

定义一个类,调用对象方法,通过源码中的lldb,取出cache_t中缓存的方法。 image.png

(lldb) p/x SwwPerson.class
(Class) $0 = 0x0000000100008768 SwwPerson
(lldb) p (cache_t *)0x0000000100008778
(cache_t *) $1 = 0x0000000100008778
(lldb) p *$1
(cache_t) $2 = {
  _bucketsAndMaybeMask = {
    std::__1::atomic<unsigned long> = {
      Value = 4302622544
    }
  }
   = {
     = {
      _maybeMask = {
        std::__1::atomic<unsigned int> = {
          Value = 3
        }
      }
      _flags = 32812
      _occupied = 1
    }
    _originalPreoptCache = {
      std::__1::atomic<preopt_cache_t *> = {
        Value = 0x0001802c00000003
      }
    }
  }
}
(lldb) p $1->buckets()
(bucket_t *) $3 = 0x000000010074cf50
(lldb) p *$3
(bucket_t) $4 = {
  _sel = {
    std::__1::atomic<objc_selector *> = (null) {
      Value = nil
    }
  }
  _imp = {
    std::__1::atomic<unsigned long> = {
      Value = 0
    }
  }
}
(lldb) p $1->buckets()[1]
(bucket_t) $5 = {
  _sel = {
    std::__1::atomic<objc_selector *> = "" {
      Value = ""
    }
  }
  _imp = {
    std::__1::atomic<unsigned long> = {
      Value = 48616
    }
  }
}
(lldb) p $5.sel
(SEL) $6 = "sayHi"
  Fix-it applied, fixed expression was: 
    $5.sel()
(lldb) p $1->buckets()[2]

cache的插入流程

向cache中插入时,occupied = occupied + 1, 当第一次插入方法时,occupied = 1, 初始化cache,开辟的空间大小为4, capacity = 4。再次插入方法时,occupied = 2, occupied + 1 <= 3/4,所以什么都不做。当再次添加方法时,occupied = 3, cache会进行扩容,capacity*2 = 8,并清除了之前缓存的方法。

为什么会选择3/4做为扩容的大小呢,有一个负载因子的说法,0.75是空间利用率最高的。并且再使用哈希函数计算下标时,也有有效的避免冲突,提高效率。

if (slowpath(isConstantEmptyCache())) {
        // Cache is read-only. Replace it.
        if (!capacity) capacity = INIT_CACHE_SIZE;
        reallocate(oldCapacity, capacity, /* freeOld */false);
    }
    
INIT_CACHE_SIZE 为 1<<2就是4

else if (fastpath(newOccupied + CACHE_END_MARKER <= cache_fill_ratio(capacity))) {
        // Cache is less than 3/4 or 7/8 full. Use it as-is.
    }
CACHE_END_MARKER = 1

else {
        capacity = capacity ? capacity * 2 : INIT_CACHE_SIZE;
        if (capacity > MAX_CACHE_SIZE) {
            capacity = MAX_CACHE_SIZE;
        }
        reallocate(oldCapacity, capacity, true);
    }