iis服务器助手广告广告
返回顶部
首页 > 资讯 > 后端开发 > GO >go map搬迁的实现
  • 433
分享到

go map搬迁的实现

go map搬迁Go map迁移 2023-05-16 15:05:07 433人浏览 安东尼
摘要

目录能具体解释一下这两种情况吗?下面开始准备搬迁了别着急,正式搬迁才刚刚开始先找找可能的目标桶位置吧遍历bucket链表,一个个迁移事后事(整理)也别忘记了最后,要不要看下是不是全搬

为什么要搬迁?无非是要么桶用的太多,要么太多的数据都到了overflow里面了

Go针对这两种情况做出了不同的搬迁处理

  • bucket用太多:扩容两倍,重新hash
  • overflow用太多:不扩容,不重新hash,只是搬迁而已

以下代码基于go1.17

能具体解释一下这两种情况吗?

桶用太多

go用了一个负载因子loadFactor来衡量。也就是如果数量count大于loadFactor * bucket数,那么就要扩容

代码如下

const (
    // Maximum number of key/elem pairs a bucket can hold.
    bucketCntBits = 3
    bucketCnt     = 1 << bucketCntBits

    // Maximum average load of a bucket that triggers growth is 6.5.
    // Represent as loadFactorNum/loadFactorDen, to allow integer math.
    loadFactorNum = 13
    loadFactorDen = 2
)

// 在元素数量大于8且元素数量大于负载因子(6.5)*桶总数,就要进行扩容
func overLoadFactor(count int, B uint8) bool {
    return count > bucketCnt && uintptr(count) > loadFactorNum*(bucketShift(B)/loadFactorDen)
}

overflow太多

overflow太多在go中分两种情况

  • bucket数量小于1<<15时,overflow超过桶总数
  • bucket数量大于1<<15时,overflow超过1<<15
// overflow buckets 太多
func tooManyOverflowBuckets(noverflow uint16, B uint8) bool {
    if B > 15 {
        B = 15
    }
    return noverflow >= uint16(1)<<(B&15)
}

下面开始准备搬迁了

准备搬迁工作是由hashGrow方法完成的,他主要就是进行申请新buckets空间、初始化搬迁进度为0、将原桶标记为旧桶等

func hashGrow(t *maptype, h *hmap) {
    // 判断是bucket多还是overflow多,然后根据这两种情况去申请新buckets空间
    bigger := uint8(1)
    if !overLoadFactor(h.count+1, h.B) {
        bigger = 0
        h.flags |= sameSizeGrow
    }
    oldbuckets := h.buckets
    newbuckets, nextOverflow := makeBucketArray(t, h.B+bigger, nil)

    flags := h.flags &^ (iterator | oldIterator)
    if h.flags&iterator != 0 {
        flags |= oldIterator
    }
    // commit the grow (atomic wrt GC)
  // 更新最新的bucket总数、将原桶标记为旧桶(后面判断是否在搬迁就是通过这个进行判断的)
    h.B += bigger
    h.flags = flags
    h.oldbuckets = oldbuckets
    h.buckets = newbuckets
  // 初始化搬迁进度为0
    h.nevacuate = 0
  // 初始化新桶overflow数量为0
    h.noverflow = 0

  // 将原来extra的overflow挂载到old overflow,代表这已经是旧的了
    if h.extra != nil && h.extra.overflow != nil {
        // Promote current overflow buckets to the old generation.
        if h.extra.oldoverflow != nil {
            throw("oldoverflow is not nil")
        }
        h.extra.oldoverflow = h.extra.overflow
        h.extra.overflow = nil
    }
  // extra指向下一块空闲的overflow空间
    if nextOverflow != nil {
        if h.extra == nil {
            h.extra = new(mapextra)
        }
        h.extra.nextOverflow = nextOverflow
    }
}

别着急,正式搬迁才刚刚开始

正式搬迁其实是在添加、删除元素的时候顺便干的。在发现搬迁的时候,就可能会做一到两次的搬迁,每次搬迁一个bucket。具体是一次还是两次就是下面的逻辑决定的

搬迁正在使用的bucket对应old bucket(如果没有搬迁过的话)

若还正在搬迁就再搬一个以加快搬迁

func growWork(t *maptype, h *hmap, bucket uintptr) {
    // make sure we evacuate the oldbucket corresponding
    // to the bucket we're about to use
    evacuate(t, h, bucket&h.oldbucketmask())

    // evacuate one more oldbucket to make progress on growing
    if h.growing() {
        evacuate(t, h, h.nevacuate)
    }
}

先找找可能的目标桶位置吧

对于不扩容的情况,可能只有一个——就是原来序号对应的桶(就是下面的x)。

对于扩容2倍的情况,显然既有可能是在原来序号对应桶,也有可能是原来序号加上扩容的桶数的序号

比如B由2变成了3,那么就要看hash第3bit到底是0还是1了,如果是001,那么和原来的一样,是序号为1的桶;如果是101,那么就是原来序号1+22 (扩容桶数)=序号为5的桶

        // xy contains the x and y (low and high) evacuation destinations.
        var xy [2]evacDst
        x := &xy[0]
        x.b = (*bmap)(add(h.buckets, oldbucket*uintptr(t.bucketsize)))
        x.k = add(unsafe.Pointer(x.b), dataOffset)
        x.e = add(x.k, bucketCnt*uintptr(t.keysize))

        if !h.sameSizeGrow() {
            // Only calculate y pointers if we're growing bigger.
            // Otherwise GC can see bad pointers.
            y := &xy[1]
      // newBit在扩容的情况下等于1<<(B-1)
            y.b = (*bmap)(add(h.buckets, (oldbucket+newbit)*uintptr(t.bucketsize)))
            y.k = add(unsafe.Pointer(y.b), dataOffset)
            y.e = add(y.k, bucketCnt*uintptr(t.keysize))
        }

遍历bucket链表,一个个迁移

每一个bucket在溢出之后都会附接overflow桶,每个bucket包括overflow储存着8个元素

  • 若元素tophash为空,则表示被搬迁过,继续下一个
  • 计算hash值
  • 若超出当前bucket容量,就新建一个overflow bucket
  • 将原来的key、value复制到新bucket新位置
  • 定位到下一个元素

在上面的步骤计算hash值在overflow用太多的情况下是不用的

此外,在桶用太多的情况下,计算hash

for ; b != nil; b = b.overflow(t) {
  // 找到key开始位置k,和value开始位置e
    k := add(unsafe.Pointer(b), dataOffset)
    e := add(k, bucketCnt*uintptr(t.keysize))
  // 遍历bucket中元素进行搬迁
    for i := 0; i < bucketCnt; i, k, e = i+1, add(k, uintptr(t.keysize)), add(e, uintptr(t.elemsize)) {
    // 获取tophash,若发现是空,说明已经搬迁过。并标记为空且已搬迁
        top := b.tophash[i]
        if isEmpty(top) {
            b.tophash[i] = evacuatedEmpty
            continue
        }
        if top < minTopHash {
            throw("bad map state")
        }
        k2 := k
    // 若key为引用类型就解引用
        if t.indirecTKEy() {
            k2 = *((*unsafe.Pointer)(k2))
        }
    // X指的就是原序号桶
    // Y指的就是扩容情况下,新的最高位为1的时候应该去的桶
        var useY uint8
        if !h.sameSizeGrow() {
            // Compute hash to make our evacuation decision (whether we need
            // to send this key/elem to bucket x or bucket y).
            hash := t.hasher(k2, uintptr(h.hash0))
      // 若正在迭代,且key为NaNs。是不是使用Y就取决最低位是不是1了
            if h.flags&iterator != 0 && !t.reflexivekey() && !t.key.equal(k2, k2) {
                useY = top & 1
                top = tophash(hash)
            } else {
        // 如果最高位为1,那么就应该选Y桶
                if hash&newbit != 0 {
                    useY = 1
                }
            }
        }

        if evacuatedX+1 != evacuatedY || evacuatedX^1 != evacuatedY {
            throw("bad evacuatedN")
        }

    // 标记X还是Y搬迁,并依此获取到搬迁目标桶
        b.tophash[i] = evacuatedX + useY 
        dst := &xy[useY]                 

    // 若目标桶已经超出桶容量,就分配新overflow
        if dst.i == bucketCnt {
            dst.b = h.newoverflow(t, dst.b)
            dst.i = 0
            dst.k = add(unsafe.Pointer(dst.b), dataOffset)
            dst.e = add(dst.k, bucketCnt*uintptr(t.keysize))
        }
    // 更新元素目标桶对应的tophash
    // 采用与而非取模,应该是出于性能目的
        dst.b.tophash[dst.i&(bucketCnt-1)] = top
    // 复制key到目标桶
        if t.indirectkey() {
            *(*unsafe.Pointer)(dst.k) = k2 // copy pointer
        } else {
            typedmemmove(t.key, dst.k, k) // copy elem
        }
    // 复制value到目标桶
        if t.indirectelem() {
            *(*unsafe.Pointer)(dst.e) = *(*unsafe.Pointer)(e)
        } else {
            typedmemmove(t.elem, dst.e, e)
        }
    
    // 更新目标桶元素数量、key、value位置
        dst.i++
        // These updates might push these pointers past the end of the key or elem arrays.   
    // That's ok, as we have the overflow pointer at the end of the bucket to protect against pointing past the end of the bucket.
        dst.k = add(dst.k, uintptr(t.keysize))
        dst.e = add(dst.e, uintptr(t.elemsize))
    }
}

事后事(整理)也别忘记了

如果发现没有在使用旧buckets的就把原buckets中的key-value清理掉吧(tophash还是保留用来搬迁时判断状态)

        // Unlink the overflow buckets & clear key/elem to help GC.
        if h.flags&oldIterator == 0 && t.bucket.ptrdata != 0 {
      // 计算当前原bucket所在的开始位置b
            b := add(h.oldbuckets, oldbucket*uintptr(t.bucketsize))
            // Preserve b.tophash because the evacuation
            // state is maintained there.
      // 从开始位置加上key-value的偏移量,那么ptr所在的位置就是实际key-value的开始位置
            ptr := add(b, dataOffset)
      // n是总bucket大小减去key-value的偏移量,就key-value占用空间的大小
            n := uintptr(t.bucketsize) - dataOffset
      // 清理从ptr开始的n个字节
            memclrHasPointers(ptr, n)
        }

最后,要不要看下是不是全搬迁完了呢?

在本次搬迁进度是最新进度的情况下(不是最新就让最新的去干吧)

  • 更新进度
  • 尝试往后看1024个bucket,如果发现有搬迁完的,但是没更新进度就顺便帮别人更新了
  • 若是全部bucket都完成搬迁了,那就直接将原buckets释放掉
func advanceEvacuationMark(h *hmap, t *maptype, newbit uintptr) {
  // 更新进度
    h.nevacuate++
    // Experiments suggest that 1024 is overkill by at least an order of magnitude.
    // Put it in there as a safeguard anyway, to ensure O(1) behavior.
  // 向后看,更新已经完成的进度
    stop := h.nevacuate + 1024
    if stop > newbit {
        stop = newbit
    }
    for h.nevacuate != stop && bucketEvacuated(t, h, h.nevacuate) {
        h.nevacuate++
    }
  // 若是完成搬迁,就释放掉old buckets、重置搬迁状态、释放原bucket挂载到extra的overflow指针
    if h.nevacuate == newbit { // newbit == # of oldbuckets
        // Growing is all done. Free old main bucket array.
        h.oldbuckets = nil
        // Can discard old overflow buckets as well.
        // If they are still referenced by an iterator,
        // then the iterator holds a pointers to the slice.
        if h.extra != nil {
            h.extra.oldoverflow = nil
        }
        h.flags &^= sameSizeGrow
    }
}

到此这篇关于go map搬迁的实现的文章就介绍到这了,更多相关go map搬迁内容请搜索编程网以前的文章或继续浏览下面的相关文章希望大家以后多多支持编程网!

您可能感兴趣的文档:

--结束END--

本文标题: go map搬迁的实现

本文链接: https://www.lsjlt.com/news/210125.html(转载时请注明来源链接)

有问题或投稿请发送至: 邮箱/279061341@qq.com    QQ/279061341

本篇文章演示代码以及资料文档资料下载

下载Word文档到电脑,方便收藏和打印~

下载Word文档
猜你喜欢
  • go map搬迁的实现
    目录能具体解释一下这两种情况吗?下面开始准备搬迁了别着急,正式搬迁才刚刚开始先找找可能的目标桶位置吧遍历bucket链表,一个个迁移事后事(整理)也别忘记了最后,要不要看下是不是全搬...
    99+
    2023-05-16
    go map搬迁 Go map迁移
  • go语言yaml转map、map遍历的实现
    yaml文件内容 apiVersion: policy/v1beta1 kind: PodSecurityPolicy metadata: name: mysql-snaps...
    99+
    2024-04-02
  • Go遍历struct,map,slice的实现
    目录遍历结构体 遍历切片 遍历MapGolang json序列化(struct,int,map,slice)遍历结构体 如何实现遍历结构体字段? 好吧,言归正传!举个例子:...
    99+
    2024-04-02
  • GO中对map排序的实现
    目录前言按Key顺序输出map按Value顺序输出map前言 GO语言中,map是哈希表,能够将特定类型的key映射到特定类型的Value上。在查询Map里面的内容时,其时间复杂度为...
    99+
    2023-03-06
    GO map排序 GO map key排序 GO map value排序
  • 实现并发安全的Go语言map
    实现并发安全的Go语言map 随着并发编程的普及,Go语言成为了许多程序员的首选语言之一。在并发编程中,map是一个常用的数据结构。但是在多个goroutine同时对map进行读写操作...
    99+
    2024-04-02
  • mysql数据库搬迁的步骤有哪些
    MySQL数据库迁移的方法包括以下几种,其中还附有具体代码示例: 数据库备份和恢复数据库备份和恢复是最常见的迁移方法之一。首先,需要将原数据库备份到一个文件,然后将备份文件导入到新的数...
    99+
    2024-02-22
  • go map初始化赋值怎么实现
    在Go语言中,可以使用字面量的方式对map进行初始化赋值。以下是几种常见的map初始化赋值方法: 使用make函数创建一个空的m...
    99+
    2023-10-23
    go
  • go语言中的json与map相互转换实现
    主要是引入 "encoding/json" 包;用到的也就是其中的两个函数json.Marshal和json.Unmarshal。 1、json.Marshal ...
    99+
    2024-04-02
  • 阿里云服务器搬迁的可能性与影响分析
    随着云计算技术的发展,越来越多的企业和个人开始使用阿里云服务器进行数据存储和处理。然而,对于阿里云服务器是否会搬迁的问题,许多人表示担忧。本文将对此进行详细分析。 首先,我们需要明确什么是阿里云服务器搬迁。阿里云服务器搬迁指的是阿里云服务器...
    99+
    2023-11-15
    阿里 可能性 服务器
  • Vite3迁移Webpack5的实现
    目录为什么要做迁移现有问题性能提升webpack5为什么快安装依赖webpack5配置webpack.base.conf.jswebpack.dev.jswebpack.prod.j...
    99+
    2023-05-18
    Vite3迁移Webpack5 Vite3 Webpack5迁移
  • golang map的实现讲解
    Golang是一门新兴的编程语言,它的map是基于哈希表实现的。在这篇文章中,我们将讨论Golang中map的实现方式。具体来说,我们将介绍哈希表的概念、Golang map的结构和性能优化。哈希表的概念哈希表是一种以键值对存储数据的数据结...
    99+
    2023-05-14
    Golang
  • java实体类转成map的实现
    目录java实体类转成map1.第一种2.第二种java实体类与map集合互转java实体类转成map 1.第一种  <!-- 配置gson -->         &l...
    99+
    2024-04-02
  • golang map 实现原理
    在学习 Golang 的过程中,map 是我们经常使用的一种数据结构,可以用来存储 key-value 对。但是,你是否想过 map 的实现原理是什么呢?在本文中,我们将探究 Golang 中 map 的实现原理。map 实现原理简介map...
    99+
    2023-05-15
  • Java怎么实现实体类转Map、Map转实体类
    这篇文章给大家分享的是有关Java怎么实现实体类转Map、Map转实体类的内容。小编觉得挺实用的,因此分享给大家做个参考,一起跟随小编过来看看吧。实体类转Map、Map转实体类1、创建entity(User.java)package&nbs...
    99+
    2023-06-20
  • 搬迁GitLab环境中碰见的问题和解决方法是什么
    搬迁GitLab环境中碰见的问题和解决方法是什么,相信很多没有经验的人对此束手无策,为此本文总结了问题出现的原因和解决方法,通过这篇文章希望你能解决这个问题。....而在新服务器上/opt路径下空间很小,让用户使用的是/DATA路径。 查看...
    99+
    2023-06-04
  • golang如何实现map
    本文小编为大家详细介绍“golang如何实现map”,内容详细,步骤清晰,细节处理妥当,希望这篇“golang如何实现map”文章能帮助大家解决疑惑,下面跟着小编的思路慢慢深入,一起来学习新知识吧。随着大数据时代的到来和云计算技术的普及,数...
    99+
    2023-07-05
  • golang map实现接口
    在Golang中,map是一种非常常用的数据结构。它可以存储一个无序的键值对集合,并且可以通过键快速地检索到对应的值,因此在开发的过程中经常会使用它来存储和管理数据。在某些情况下,我们可能会需要将map类型与接口结合使用,来实现一些特定的功...
    99+
    2023-05-14
  • Java如何实现实体类转Map、Map转实体类
    实体类转Map、Map转实体类 1、创建entity(User.java) package com.jeff.entity; public class User { priva...
    99+
    2024-04-02
  • golang map如何实现
    本文小编为大家详细介绍“golang map如何实现”,内容详细,步骤清晰,细节处理妥当,希望这篇“golang map如何实现”文章能帮助大家解决疑惑,下面跟着小编的思路慢慢深入,一起来学习新知识吧。哈希表的概念哈希表是一种以键值对存储数...
    99+
    2023-07-05
  • go map value 和 nil 的区别
    你在学习Golang相关的知识吗?本文《go map value 和 nil 的区别》,主要介绍的内容就涉及到,如果你想提升自己的开发能力,就不要错过这篇文章,大家要知道编程理论基础和实战操作都是不...
    99+
    2024-04-04
软考高级职称资格查询
编程网,编程工程师的家园,是目前国内优秀的开源技术社区之一,形成了由开源软件库、代码分享、资讯、协作翻译、讨论区和博客等几大频道内容,为IT开发者提供了一个发现、使用、并交流开源技术的平台。
  • 官方手机版

  • 微信公众号

  • 商务合作