makemap_small和makemap的区别</h2>
<code>makemap_small</code>的代码如下:
<pre><code class="lang-go hljs"><span class="token comment">// makemap_small implements Go map creation for make(map[k]v) and</span>
<span class="token comment">// make(map[k]v, hint) when hint is known to be at most bucketCnt</span>
<span class="token comment">// at compile time and the map needs to be allocated on the heap.</span>
<span class="token keyword">func</span> <span class="token function">makemap_small</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token operator">*</span>hmap <span class="token punctuation">{</span>
h <span class="token operator">:=</span> <span class="token function">new</span><span class="token punctuation">(</span>hmap<span class="token punctuation">)</span>
h<span class="token punctuation">.</span>hash0 <span class="token operator">=</span> <span class="token function">fastrand</span><span class="token punctuation">(</span><span class="token punctuation">)</span>
<span class="token keyword">return</span> h
<span class="token punctuation">}</span>
</code></pre>
该代码的实现十分简单,就设置了一个hash种子,其他的例如申通桶内存的操作只有在真正赋值数据的时候才会创建桶。该方法在什么情况会被调用呢?如注释说说"hint is known to be at most bucketCnt at compile time and the map needs to be allocated on the heap",bucketCnt就是8个,所以上面我的示例代码为何要设初始容量为9的原因就在这里。
我就直接略过这种情况,因为在实际应用场景下还是要指定容量,避免后面因为频繁扩容造成性能损失,<code>makemap</code>的代码如下:
<pre><code class="lang-go hljs"><span class="token comment">// makemap implements Go map creation for make(map[k]v, hint).</span>
<span class="token comment">// If the compiler has determined that the map or the first bucket</span>
<span class="token comment">// can be created on the stack, h and/or bucket may be non-nil.</span>
<span class="token comment">// If h != nil, the map can be created directly in h.</span>
<span class="token comment">// If h.buckets != nil, bucket pointed to can be used as the first bucket.</span>
<span class="token keyword">func</span> <span class="token function">makemap</span><span class="token punctuation">(</span>t <span class="token operator">*</span>maptype<span class="token punctuation">,</span> hint <span class="token builtin">int</span><span class="token punctuation">,</span> h <span class="token operator">*</span>hmap<span class="token punctuation">)</span> <span class="token operator">*</span>hmap <span class="token punctuation">{</span>
mem<span class="token punctuation">,</span> overflow <span class="token operator">:=</span> math<span class="token punctuation">.</span><span class="token function">MulUintptr</span><span class="token punctuation">(</span><span class="token function">uintptr</span><span class="token punctuation">(</span>hint<span class="token punctuation">)</span><span class="token punctuation">,</span> t<span class="token punctuation">.</span>bucket<span class="token punctuation">.</span>size<span class="token punctuation">)</span>
<span class="token comment">// 是否超出了最大的可分配虚拟内存或者超出了uintptr表示的值</span>
<span class="token keyword">if</span> overflow <span class="token operator">||</span> mem <span class="token operator">></span> maxAlloc <span class="token punctuation">{</span>
hint <span class="token operator">=</span> <span class="token number">0</span>
<span class="token punctuation">}</span>
<span class="token comment">// initialize Hmap</span>
<span class="token keyword">if</span> h <span class="token operator">==</span> <span class="token boolean">nil</span> <span class="token punctuation">{</span>
h <span class="token operator">=</span> <span class="token function">new</span><span class="token punctuation">(</span>hmap<span class="token punctuation">)</span>
<span class="token punctuation">}</span>
<span class="token comment">// 随机hash因子</span>
h<span class="token punctuation">.</span>hash0 <span class="token operator">=</span> <span class="token function">fastrand</span><span class="token punctuation">(</span><span class="token punctuation">)</span>
<span class="token comment">// Find the size parameter B which will hold the requested # of elements.</span>
<span class="token comment">// For hint < 0 overLoadFactor returns false since hint < bucketCnt.</span>
<span class="token comment">// 计算B的值,桶的个数为 1 << B</span>
B <span class="token operator">:=</span> <span class="token function">uint8</span><span class="token punctuation">(</span><span class="token number">0</span><span class="token punctuation">)</span>
<span class="token comment">// 不断的循环得到最大的B值</span>
<span class="token keyword">for</span> <span class="token function">overLoadFactor</span><span class="token punctuation">(</span>hint<span class="token punctuation">,</span> B<span class="token punctuation">)</span> <span class="token punctuation">{</span>
B<span class="token operator"> </span>
<span class="token punctuation">}</span>
h<span class="token punctuation">.</span>B <span class="token operator">=</span> B
<span class="token comment">// allocate initial hash table</span>
<span class="token comment">// if B == 0, the buckets field is allocated lazily later (in mapassign)</span>
<span class="token comment">// If hint is large zeroing this memory could take a while.</span>
<span class="token keyword">if</span> h<span class="token punctuation">.</span>B <span class="token operator">!=</span> <span class="token number">0</span> <span class="token punctuation">{</span>
<span class="token keyword">var</span> nextOverflow <span class="token operator">*</span>bmap
<span class="token comment">// 根据B的值去申请桶,包括逸出桶</span>
h<span class="token punctuation">.</span>buckets<span class="token punctuation">,</span> nextOverflow <span class="token operator">=</span> <span class="token function">makeBucketArray</span><span class="token punctuation">(</span>t<span class="token punctuation">,</span> h<span class="token punctuation">.</span>B<span class="token punctuation">,</span> <span class="token boolean">nil</span><span class="token punctuation">)</span>
<span class="token keyword">if</span> nextOverflow <span class="token operator">!=</span> <span class="token boolean">nil</span> <span class="token punctuation">{</span>
h<span class="token punctuation">.</span>extra <span class="token operator">=</span> <span class="token function">new</span><span class="token punctuation">(</span>mapextra<span class="token punctuation">)</span>
<span class="token comment">// nextOverflow指向逸出桶的内存地址</span>
h<span class="token punctuation">.</span>extra<span class="token punctuation">.</span>nextOverflow <span class="token operator">=</span> nextOverflow
<span class="token punctuation">}</span>
<span class="token punctuation">}</span>
<span class="token keyword">return</span> h
<span class="token punctuation">}</span>
</code></pre>
看程序注释大概明白该代码的作用就是得到B值和申请桶,<code>overLoadFactor</code>方法是用了6.5的扩容因子去计算出最大的B值,保证你申请的容量count要大于 (1>> B) * 6.5, 这个扩容因子想必大家都不陌生,在java中是0.75,为什么在go中是0.65呢?在<code>runtime/map.go</code>开头处有测试数据,综合考虑来说选择了6.5。大家可能注意到<code>maptype</code>用来申请桶的内存块了,下面看看<code>maptype</code>的代码,也有助于理解map的结构:
<pre><code class="lang-go hljs"><span class="token keyword">type</span> maptype <span class="token keyword">struct</span> <span class="token punctuation">{</span>
typ _type
<span class="token comment">// map的key的类型</span>
key <span class="token operator">*</span>_type
<span class="token comment">// map的value的类型</span>
elem <span class="token operator">*</span>_type
<span class="token comment">// 桶的类型</span>
bucket <span class="token operator">*</span>_type <span class="token comment">// internal type representing a hash bucket</span>
<span class="token comment">// function for hashing keys (ptr to key, seed) -> hash</span>
hasher <span class="token keyword">func</span><span class="token punctuation">(</span>unsafe<span class="token punctuation">.</span>Pointer<span class="token punctuation">,</span> <span class="token builtin">uintptr</span><span class="token punctuation">)</span> <span class="token builtin">uintptr</span>
<span class="token comment">// key的类型的大小</span>
keysize <span class="token builtin">uint8</span> <span class="token comment">// size of key slot</span>
<span class="token comment">// value类型元素的大小</span>
elemsize <span class="token builtin">uint8</span> <span class="token comment">// size of elem slot</span>
<span class="token comment">// 桶里面所有元素的大小</span>
bucketsize <span class="token builtin">uint16</span> <span class="token comment">// size of bucket</span>
flags <span class="token builtin">uint32</span>
<span class="token punctuation">}</span>
</code></pre>
<code>makemap</code>方法里面<code>math.MulUintptr(uintptr(hint), t.bucket.size)</code>用到了bucket的size,这里这个size和<code>maptype</code>的bucketsize一模一样都是272(《go基础之map-写在前面(一)》有介绍为什么是272),所以就能计算出需要分配的内存。仔细分析<code>makemap</code>的字段,可以发现定义了map的基本数据结构,后面代码用来申请桶的内存块的时候都使用了这个数据结构。
<code>makemap</code>第36行代码调用了方法<code>makeBucketArray</code>方法来申请内存,我们简单看看它里面的细节:
<pre><code class="lang-go hljs"><span class="token comment">// makeBucketArray initializes a backing array for map buckets.</span>
<span class="token comment">// 1<<b is the minimum number of buckets to allocate.</span>
<span class="token comment">// dirtyalloc should either be nil or a bucket array previously</span>
<span class="token comment">// allocated by makeBucketArray with the same t and b parameters.</span>
<span class="token comment">// If dirtyalloc is nil a new backing array will be alloced and</span>
<span class="token comment">// otherwise dirtyalloc will be cleared and reused as backing array.</span>
<span class="token keyword">func</span> <span class="token function">makeBucketArray</span><span class="token punctuation">(</span>t <span class="token operator">*</span>maptype<span class="token punctuation">,</span> b <span class="token builtin">uint8</span><span class="token punctuation">,</span> dirtyalloc unsafe<span class="token punctuation">.</span>Pointer<span class="token punctuation">)</span> <span class="token punctuation">(</span>buckets unsafe<span class="token punctuation">.</span>Pointer<span class="token punctuation">,</span> nextOverflow <span class="token operator">*</span>bmap<span class="token punctuation">)</span> <span class="token punctuation">{</span>
base <span class="token operator">:=</span> <span class="token function">bucketShift</span><span class="token punctuation">(</span>b<span class="token punctuation">)</span>
nbuckets <span class="token operator">:=</span> base
<span class="token comment">// For small b, overflow buckets are unlikely.</span>
<span class="token comment">// Avoid the overhead of the calculation.</span>
<span class="token keyword">if</span> b <span class="token operator">>=</span> <span class="token number">4</span> <span class="token punctuation">{</span>
<span class="token comment">// Add on the estimated number of overflow buckets</span>
<span class="token comment">// required to insert the median number of elements</span>
<span class="token comment">// used with this value of b.</span>
nbuckets <span class="token operator"> =</span> <span class="token function">bucketShift</span><span class="token punctuation">(</span>b <span class="token operator">-</span> <span class="token number">4</span><span class="token punctuation">)</span>
sz <span class="token operator">:=</span> t<span class="token punctuation">.</span>bucket<span class="token punctuation">.</span>size <span class="token operator">*</span> nbuckets
up <span class="token operator">:=</span> <span class="token function">roundupsize</span><span class="token punctuation">(</span>sz<span class="token punctuation">)</span>
<span class="token keyword">if</span> up <span class="token operator">!=</span> sz <span class="token punctuation">{</span>
nbuckets <span class="token operator">=</span> up <span class="token operator">/</span> t<span class="token punctuation">.</span>bucket<span class="token punctuation">.</span>size
<span class="token punctuation">}</span>
<span class="token punctuation">}</span>
<span class="token keyword">if</span> dirtyalloc <span class="token operator">==</span> <span class="token boolean">nil</span> <span class="token punctuation">{</span>
<span class="token comment">// 申请nbuckets个桶,包括了逸出桶,所以所有桶在内存中其实连续的,虽然逻辑上有差异</span>
buckets <span class="token operator">=</span> <span class="token function">newarray</span><span class="token punctuation">(</span>t<span class="token punctuation">.</span>bucket<span class="token punctuation">,</span> <span class="token function">int</span><span class="token punctuation">(</span>nbuckets<span class="token punctuation">)</span><span class="token punctuation">)</span>
b0 <span class="token operator">:=</span> <span class="token punctuation">(</span><span class="token operator">*</span>dmap<span class="token punctuation">)</span><span class="token punctuation">(</span><span class="token function">add</span><span class="token punctuation">(</span>buckets<span class="token punctuation">,</span> <span class="token function">uintptr</span><span class="token punctuation">(</span><span class="token number">0</span><span class="token punctuation">)</span><span class="token operator">*</span><span class="token function">uintptr</span><span class="token punctuation">(</span>t<span class="token punctuation">.</span>bucketsize<span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">)</span>
<span class="token function">println</span><span class="token punctuation">(</span>b0<span class="token punctuation">.</span>debugOverflows<span class="token punctuation">)</span>
<span class="token punctuation">}</span> <span class="token keyword">else</span> <span class="token punctuation">{</span>
<span class="token comment">// dirtyalloc was previously generated by</span>
<span class="token comment">// the above newarray(t.bucket, int(nbuckets))</span>
<span class="token comment">// but may not be empty.</span>
buckets <span class="token operator">=</span> dirtyalloc
size <span class="token operator">:=</span> t<span class="token punctuation">.</span>bucket<span class="token punctuation">.</span>size <span class="token operator">*</span> nbuckets
<span class="token keyword">if</span> t<span class="token punctuation">.</span>bucket<span class="token punctuation">.</span>ptrdata <span class="token operator">!=</span> <span class="token number">0</span> <span class="token punctuation">{</span>
<span class="token function">memclrHasPointers</span><span class="token punctuation">(</span>buckets<span class="token punctuation">,</span> size<span class="token punctuation">)</span>
<span class="token punctuation">}</span> <span class="token keyword">else</span> <span class="token punctuation">{</span>
<span class="token function">memclrNoHeapPointers</span><span class="token punctuation">(</span>buckets<span class="token punctuation">,</span> size<span class="token punctuation">)</span>
<span class="token punctuation">}</span>
<span class="token punctuation">}</span>
<span class="token keyword">if</span> base <span class="token operator">!=</span> nbuckets <span class="token punctuation">{</span>
<span class="token comment">// We preallocated some overflow buckets.</span>
<span class="token comment">// To keep the overhead of tracking these overflow buckets to a minimum,</span>
<span class="token comment">// we use the convention that if a preallocated overflow bucket's overflow</span>
<span class="token comment">// pointer is nil, then there are more available by bumping the pointer.</span>
<span class="token comment">// We need a safe non-nil pointer for the last overflow bucket; just use buckets.</span>
<span class="token comment">// 得到逸出桶的位置</span>
nextOverflow <span class="token operator">=</span> <span class="token punctuation">(</span><span class="token operator">*</span>bmap<span class="token punctuation">)</span><span class="token punctuation">(</span><span class="token function">add</span><span class="token punctuation">(</span>buckets<span class="token punctuation">,</span> base<span class="token operator">*</span><span class="token function">uintptr</span><span class="token punctuation">(</span>t<span class="token punctuation">.</span>bucketsize<span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">)</span>
<span class="token comment">// 得到最后一个桶的位置</span>
last <span class="token operator">:=</span> <span class="token punctuation">(</span><span class="token operator">*</span>bmap<span class="token punctuation">)</span><span class="token punctuation">(</span><span class="token function">add</span><span class="token punctuation">(</span>buckets<span class="token punctuation">,</span> <span class="token punctuation">(</span>nbuckets<span class="token operator">-</span><span class="token number">1</span><span class="token punctuation">)</span><span class="token operator">*</span><span class="token function">uintptr</span><span class="token punctuation">(</span>t<span class="token punctuation">.</span>bucketsize<span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">)</span>
<span class="token comment">// 给最后一个桶的逸出桶指针设置了桶的起始位置,环状了?官方解释说简单为了逸出桶这个指针不是空指针</span>
last<span class="token punctuation">.</span><span class="token function">setoverflow</span><span class="token punctuation">(</span>t<span class="token punctuation">,</span> <span class="token punctuation">(</span><span class="token operator">*</span>bmap<span class="token punctuation">)</span><span class="token punctuation">(</span>buckets<span class="token punctuation">)</span><span class="token punctuation">)</span>
<span class="token punctuation">}</span>
<span class="token keyword">return</span> buckets<span class="token punctuation">,</span> nextOverflow
<span class="token punctuation">}</span>
</code></pre>
注意12行处有个优化点,当B小于4的时候,也就是初始申请map的容量的时候的count < (1 >> B) * 6.5的时候,很大的概率其实不会使用逸出桶。当B大于4的时候,程序就预估出一个逸出桶的个数,在26行处就一并申请总的桶的内存块。第27行的代码是在源码中没有的,我只是用来调试代码所用,这个在《go基础之map-写在前面(一)》有介绍这个小技巧。在第49行就通过bucketsize计算出逸出桶的位置,并且在51到53行有个技巧,给最后一个桶的溢出桶指针设置了桶的起始地址,这个在后面系列的博客会介绍到为何这么使用。
ok,现在map的数据如下:
只有2个桶,而且每个桶的tophash值都是默认值0,由于此时key和value都为空,故没有展示出来。<code>extra</code>没有值是因为B小4没有溢出桶导致的。
<h2>