diff --git a/blog/go/block_chain.md b/blog/go/block_chain.md new file mode 100644 index 0000000..92775fd --- /dev/null +++ b/blog/go/block_chain.md @@ -0,0 +1,121 @@ +@[toc] + +# 前言 +你好,我是醉墨居士,在当今的技术领域,区块链技术无疑是一颗璀璨的明珠,其去中心化、不可篡改和安全可靠的特性吸引了无数开发者的目光,在这篇博客中我将介绍如何使用强大和高效的Go语言开发一个极简的区块链模型 + +# 交易结构体 +```go +type Transaction struct { + Sender string // 交易的发送方的唯一标识符 + Receiver string // 交易的接收方的唯一标识符 + Amount float64 // 交易金额 +} +``` + +# 区块结构体 +```go +type Block struct { + Index int // 区块的索引 + Timestamp string // 区块创建的时间戳 + Data string // 区块携带的数据 + PrevHash string // 前一个区块的哈希值 + Hash string // 当前区块的哈希值 + Transactions []Transaction // 交易列表 + Nonce int // 证明工作量的随机数 +} +``` + +# 区块链结构体 +```go +type Blockchain struct { + Chain []Block // 区块链条 +} +``` + +# 计算区块哈希值 +```go +func (b *Block) CalculateHash() string { + data := strconv.Itoa(block.Index) + block.Timestamp + fmt.Sprintf("%v", block.Transactions) + block.PrevHash + strconv.Itoa(block.Nonce) + hash := sha256.Sum256([]byte(data)) + return fmt.Sprintf("%x", hash) +} +``` + +# 工作量证明(PoW) +通过不断调整 Nonce 值,计算出满足一定难度要求的哈希值,确保新区块的生成需要一定的计算量 +```go +func (b *Block) ProofOfWork(difficulty int) int { + var nonce int + for { + hash := b.CalculateHash() + if strings.HasPrefix(hash, strings.Repeat("0", difficulty)) { + return nonce + } + nonce++ + } +} +``` + +# 生成新区块 +```go +func (b *Block) GenerateBlock(data string) Block { + block := Block{ + Index: oldBlock.Index + 1, + Timestamp: time.Now().String(), + Data: data, + PrevHash: b.Hash, + } + block.Hash = block.CalculateHash() + return newBlock +} +``` + +# 验证区块合法性 +```go +func (b *Block) VaildBlock(prevBlock Block) bool { + // 检查索引是否连续递增 + if b.Index!= prevBlock.Index + 1 { + return false + } + + // 检查时间戳是否合理(简单示例,实际可能更复杂) + newTime, _ := time.Parse("2006-01-02 15:04:05", b.Timestamp) + prevTime, _ := time.Parse("2006-01-02 15:04:05", prevBlock.Timestamp) + if newTime.Before(prevTime) { + return false + } + + // 重新计算哈希值并与存储的哈希值比较 + calculatedHash := b.CalculateHash() + if calculatedHash!= b.Hash { + return false + } + + // 检查前一区块哈希值是否匹配 + if b.PrevHash!= prevBlock.Hash { + return false + } + + return true +} +``` + +# 添加新区块 +```go +func (bc *Blockchain) AddBlock(transactions []Transaction) { + lastBlock := bc.Chain[len(bc.Chain)-1] + block := Block{ + Index: lastBlock.Index + 1, + Timestamp: time.Now().String(), + Transactions: transactions, + PrevHash: lastBlock.Hash, + Nonce: 0, + } + block.Nonce = block.ProofOfWork(2) + block.Hash = block.CalculateHash() + bc.Chain = append(bc.Chain, block) +} +``` + +# 最后 +我是醉墨居士,我们已经完成了区块链的基本模型,希望大家多收藏、点赞、关注,我们下篇博客再见 diff --git a/blog/go/deep_learn.md b/blog/go/deep_learn.md new file mode 100644 index 0000000..c4b8de8 --- /dev/null +++ b/blog/go/deep_learn.md @@ -0,0 +1,690 @@ + +@[toc] + +# 开发前言 +正向传播是指从神经网络的输入层开始,通过逐层计算和传递,将输入数据一直传递到输出层。在每一层中,通过对输入数据进行加权求和并应用激活函数,得到该层的输出。这个过程可以看作是将输入数据在网络中前进(向前传播),直至得到模型的预测结果。 + +反向传播是指根据模型的预测结果和实际标签之间的差异,从输出层向输入层反向计算梯度,并利用梯度来更新网络参数。 + +这篇博客我将使用Go语言实现正向传播和反向传播,帮助你理解其底层的运转规律 + +项目代码使用纯粹的Go语言标准库实现,不借用任何其它第三方库。用轮子是生活,造轮子是信仰。 + +我是醉墨居士,我们现在开始吧🤗 +# 开发理论 +一个数学函数,由一系列数据和一系列运算方式构成,我们将数据对应为数据节点,将运算方式对应为算子节点,这样我们就可以将数学函数转化为由一系列数据节点和一系列算子节点组成的数据流图 + +正向传递数据流图,不断运算数据,就是正向传播的过程 +反向传递数据流图,不断累加梯度,就是反向传播的过程 + +# 图解理论 +我画了两张图来表示函数3 * pow(x, 2) + 2 * x + 1的正向传播和反向传播的过程 + +正向传播图解 +![forward](https://i-blog.csdnimg.cn/blog_migrate/e747bfc6aea5a94f5253d3c2b351c4d0.png) + +反向传播图解 +![backward](https://i-blog.csdnimg.cn/blog_migrate/de471635c67c01c8cdcc83e336e8eade.png) + +# 数据类型 +data/type.go +```go +package data + +type Type interface { + ~int | ~int32 | ~int64 | + ~uint | ~uint32 | ~uint64 | + ~float32 | ~float64 +} +``` + +# 数学函数 +math/math.go +```go +package math + +import ( + "dl/node" + stmath "math" +) + +func Pow[T node.Type](a, b T) T { + return T(stmath.Pow(float64(a), float64(b))) +} + +func Ln[T node.Type](a T) T { + return T(stmath.Log(float64(a))) +} + +func Tan[T node.Type](a T) T { + return T(stmath.Tan(float64(a))) +} + +func Sin[T node.Type](a T) T { + return T(stmath.Sin(float64(a))) +} + +func Cos[T node.Type](a T) T { + return T(stmath.Cos(float64(a))) +} +``` + +# 数据节点统一抽象 +fm/datanode.go +```go +type DataNode[T data.Type] interface { + Data()T + SetData(T) + Grad()T + setGrad(T) + preNode() CalNode[T] + backNodes() *[]CalNode[T] + fm()FlowMap[T] + Add(DataNode[T]) DataNode[T] + Sub(DataNode[T]) DataNode[T] + Mul(DataNode[T]) DataNode[T] + Div(DataNode[T]) DataNode[T] + Pow(DataNode[T]) DataNode[T] + Ln() DataNode[T] + Tan() DataNode[T] + Sin() DataNode[T] + Cos() DataNode[T] +} +``` + +# 变量数据节点 +```go +package fm + +import "dl/data" + +type varDataNode[T data.Type] struct { + data T + grad T + prenode CalNode[T] + backnodes []CalNode[T] + flowmap FlowMap[T] +} + +func (n *varDataNode[T]) Data() T { + return n.data +} + +func (n *varDataNode[T]) SetData(i T) { + n.data = i +} + +func (n *varDataNode[T]) Grad() T { + return n.grad +} + +func (n *varDataNode[T]) setGrad(i T) { + n.grad = i +} + +func (n *varDataNode[T]) preNode() CalNode[T] { + return n.prenode +} + +func (n *varDataNode[T]) backNodes() *[]CalNode[T] { + return &n.backnodes +} + +func (n *varDataNode[T]) fm() FlowMap[T] { + return n.flowmap +} + +func (n *varDataNode[T]) Add(node DataNode[T]) DataNode[T] { + return calTwo(newAdd[T](), n, node) +} + +func (n *varDataNode[T]) Sub(node DataNode[T]) DataNode[T] { + return calTwo(newSub[T](), n, node) +} + +func (n *varDataNode[T]) Mul(node DataNode[T]) DataNode[T] { + return calTwo(newMul[T](), n, node) +} + +func (n *varDataNode[T]) Div(node DataNode[T]) DataNode[T] { + return calTwo(newDiv[T](), n, node) +} + +func (n *varDataNode[T]) Pow(node DataNode[T]) DataNode[T] { + return calTwo(newPow[T](), n, node) +} + +func (n *varDataNode[T]) Ln() DataNode[T] { + return calOne(newLn[T](), n) +} + +func (n *varDataNode[T]) Tan() DataNode[T] { + return calOne(newTan[T](), n) +} + +func (n *varDataNode[T]) Sin() DataNode[T] { + return calOne(newSin[T](), n) +} + +func (n *varDataNode[T]) Cos() DataNode[T] { + return calOne(newCos[T](), n) +} +``` + +# 常量数据节点 +```go +type constDataNode[T data.Type] struct { + data T + prenode CalNode[T] + backnodes []CalNode[T] + flowmap FlowMap[T] +} + +func (n *constDataNode[T]) Data() T { + return n.data +} + +func (n *constDataNode[T]) SetData(i T) { + n.data = i +} +func (n *constDataNode[T]) Grad() T { + return 0 +} + +func (n *constDataNode[T]) setGrad(T) {} + +func (n *constDataNode[T]) preNode() CalNode[T] { + return n.prenode +} + +func (n *constDataNode[T]) backNodes() *[]CalNode[T] { + return &n.backnodes +} + +func (n *constDataNode[T]) fm() FlowMap[T] { + return n.flowmap +} + +func (n *constDataNode[T]) Add(node DataNode[T]) DataNode[T] { + return calTwo(newAdd[T](), n, node) +} + +func (n *constDataNode[T]) Sub(node DataNode[T]) DataNode[T] { + return calTwo(newSub[T](), n, node) +} + +func (n *constDataNode[T]) Mul(node DataNode[T]) DataNode[T] { + return calTwo(newMul[T](), n, node) +} + +func (n *constDataNode[T]) Div(node DataNode[T]) DataNode[T] { + return calTwo(newDiv[T](), n, node) +} + +func (n *constDataNode[T]) Pow(node DataNode[T]) DataNode[T] { + return calTwo(newPow[T](), n, node) +} + +func (n *constDataNode[T]) Ln() DataNode[T] { + return calOne(newLn[T](), n) +} + +func (n *constDataNode[T]) Tan() DataNode[T] { + return calOne(newTan[T](), n) +} + +func (n *constDataNode[T]) Sin() DataNode[T] { + return calOne(newSin[T](), n) +} + +func (n *constDataNode[T]) Cos() DataNode[T] { + return calOne(newCos[T](), n) +} +``` + +# 单目运算封装 +```go +func calOne[T data.Type](operation CalNode[T], a DataNode[T]) DataNode[T] { + *a.fm().calnodes = append(*a.fm().calnodes, operation) + *a.backNodes() = append(*a.backNodes(), operation) + res := &varDataNode[T]{ + prenode: operation, + flowmap: a.fm(), + } + *a.fm().datanodes = append(*a.fm().datanodes, res) + operation.CalNode().PreNodes = []DataNode[T]{a} + operation.CalNode().BackNode = res + return res +} +``` + +# 双目运算封装 +```go +func calTwo[T data.Type] (operation CalNode[T], a, b DataNode[T]) DataNode[T] { + if a.fm() != b.fm() { + return nil + } + *a.fm().calnodes = append(*a.fm().calnodes, operation) + *a.backNodes() = append(*a.backNodes(), operation) + *b.backNodes() = append(*b.backNodes(), operation) + res := &varDataNode[T]{ + prenode: operation, + flowmap: a.fm(), + } + *a.fm().datanodes = append(*a.fm().datanodes, res) + operation.CalNode().PreNodes = []DataNode[T]{a, b} + operation.CalNode().BackNode = res + return res +} +``` + +# 算子节点统一抽象 +fm/calnode.go +```go +type CalNode[T data.Type] interface { + CalNode() *BaseCalNode[T] + Forward() + Backward() +} +``` + +# 基础算子 +```go +type BaseCalNode[T data.Type] struct { + PreNodes []DataNode[T] + BackNode DataNode[T] +} +``` + +# 加法算子 +```go +type AddNode[T data.Type] BaseCalNode[T] + +func newAdd[T data.Type]() CalNode[T] { + basenode := &BaseCalNode[T]{} + return (*AddNode[T])(basenode) +} + +func (n *AddNode[T]) CalNode() *BaseCalNode[T] { + return (*BaseCalNode[T])(n) +} + +func (n *AddNode[T]) Forward() { + n.BackNode.SetData(n.CalNode().PreNodes[0].Data() + n.CalNode().PreNodes[1].Data()) +} + +func (n *AddNode[T]) Backward() { + // selfgrad + backgrad + grad0 := n.PreNodes[0].Grad() + n.BackNode.Grad() + // selfgrad + backgrad + grad1 := n.PreNodes[1].Grad() + n.BackNode.Grad() + + n.PreNodes[0].setGrad(grad0) + n.PreNodes[1].setGrad(grad1) +} +``` + +# 减法算子 +```go +type SubNode[T data.Type] BaseCalNode[T] + +func newSub[T data.Type]() CalNode[T] { + basenode := &BaseCalNode[T]{} + return (*SubNode[T])(basenode) +} + +func (n *SubNode[T]) CalNode() *BaseCalNode[T] { + return (*BaseCalNode[T])(n) +} + +func (n *SubNode[T]) Forward() { + n.BackNode.SetData(n.CalNode().PreNodes[0].Data() - n.CalNode().PreNodes[1].Data()) +} + +func (n *SubNode[T]) Backward() { + // selfgrad + backgrad + grad0 := n.PreNodes[0].Grad() + n.BackNode.Grad() + // selfgrad - backgrad + grad1 := n.PreNodes[1].Grad() - n.BackNode.Grad() + + n.PreNodes[0].setGrad(grad0) + n.PreNodes[1].setGrad(grad1) +} +``` + +# 乘法算子 +```go +type MulNode[T data.Type] BaseCalNode[T] + +func newMul[T data.Type]() CalNode[T] { + basenode := &BaseCalNode[T]{} + return (*MulNode[T])(basenode) +} + +func (n *MulNode[T]) CalNode() *BaseCalNode[T] { + return (*BaseCalNode[T])(n) +} + +func (n *MulNode[T]) Forward() { + n.BackNode.SetData(n.CalNode().PreNodes[0].Data() * n.CalNode().PreNodes[1].Data()) +} + +func (n *MulNode[T]) Backward() { + a := n.PreNodes[0].Data() + b := n.PreNodes[1].Data() + backgrad := n.BackNode.Grad() + + // selfgrad + (backgrad * b) + grad0 := n.PreNodes[0].Grad() + (backgrad * b) + // selfgrad + (backgrad * a) + grad1 := n.PreNodes[1].Grad() + (backgrad * a) + + n.PreNodes[0].setGrad(grad0) + n.PreNodes[1].setGrad(grad1) +} +``` + +# 除法算子 +```go +type DivNode[T data.Type] BaseCalNode[T] + +func newDiv[T data.Type]() CalNode[T] { + basenode := &BaseCalNode[T]{} + return (*DivNode[T])(basenode) +} + +func (n *DivNode[T]) CalNode() *BaseCalNode[T] { + return (*BaseCalNode[T])(n) +} + +func (n *DivNode[T]) Forward() { + n.BackNode.SetData(n.CalNode().PreNodes[0].Data() / n.CalNode().PreNodes[1].Data()) +} + +func (n *DivNode[T]) Backward() { + a := n.PreNodes[0].Data() + b := n.PreNodes[1].Data() + backgrad := n.BackNode.Grad() + + // selfgrad + (backgrad / b) + grad0 := n.PreNodes[0].Grad() + (backgrad / b) + // selfgrad - (backgrad * a / pow(b, 2)) + grad1 := n.PreNodes[1].Grad() - (backgrad * a / math.Pow(b, 2)) + + n.PreNodes[0].setGrad(grad0) + n.PreNodes[1].setGrad(grad1) +} +``` + +# 指数算子 +```go +type PowNode[T data.Type] BaseCalNode[T] + +func newPow[T data.Type]() CalNode[T] { + basenode := &BaseCalNode[T]{} + return (*PowNode[T])(basenode) +} + +func (n *PowNode[T]) CalNode() *BaseCalNode[T] { + return (*BaseCalNode[T])(n) +} + +func (n *PowNode[T]) Forward() { + n.BackNode.SetData(math.Pow(n.CalNode().PreNodes[0].Data(), n.CalNode().PreNodes[1].Data())) +} + +func (n *PowNode[T]) Backward() { + a := n.PreNodes[0].Data() + b := n.PreNodes[1].Data() + backgrad := n.BackNode.Grad() + + // selfgrad + (backgrad * b * pow(a, b-1)) + grad0 := n.PreNodes[0].Grad() + (backgrad * b * math.Pow(a, b-1)) + // selfgrad + (backgrad * pow(a, b) * ln(a)) + grad1 := n.PreNodes[1].Grad() + (backgrad * math.Pow(a, b) * math.Ln(a)) + + n.PreNodes[0].setGrad(grad0) + n.PreNodes[1].setGrad(grad1) +} +``` + +# 对数算子 +```go +type LnNode[T data.Type] BaseCalNode[T] + +func newLn[T data.Type]() CalNode[T] { + basenode := &BaseCalNode[T]{} + return (*LnNode[T])(basenode) +} + +func (n *LnNode[T]) CalNode() *BaseCalNode[T] { + return (*BaseCalNode[T])(n) +} + +func (n *LnNode[T]) Forward() { + n.BackNode.SetData(math.Ln(n.CalNode().PreNodes[0].Data())) +} + +func (n *LnNode[T]) Backward() { + a := n.PreNodes[0].Data() + backgrad := n.BackNode.Grad() + + // selfgrad + (backgrad / a) + grad0 := n.PreNodes[0].Grad() + (backgrad / a) + + n.PreNodes[0].setGrad(grad0) +} +``` + +# 正切算子 +```go +type TanNode[T data.Type] BaseCalNode[T] + +func newTan[T data.Type]() CalNode[T] { + basenode := &BaseCalNode[T]{} + return (*TanNode[T])(basenode) +} + +func (n *TanNode[T]) CalNode() *BaseCalNode[T] { + return (*BaseCalNode[T])(n) +} + +func (n *TanNode[T]) Forward() { + n.BackNode.SetData(math.Tan(n.CalNode().PreNodes[0].Data())) +} + +func (n *TanNode[T]) Backward() { + a := n.PreNodes[0].Data() + backgrad := n.BackNode.Grad() + + // selfgrad + (backgrad / pow(cos(a), 2)) + grad0 := n.PreNodes[0].Grad() + (backgrad / math.Pow(math.Cos(a), 2)) + + n.PreNodes[0].setGrad(grad0) +} +``` + +# 正弦算子 +```go +type SinNode[T data.Type] BaseCalNode[T] + +func newSin[T data.Type]() CalNode[T] { + basenode := &BaseCalNode[T]{} + return (*SinNode[T])(basenode) +} + +func (n *SinNode[T]) CalNode() *BaseCalNode[T] { + return (*BaseCalNode[T])(n) +} + +func (n *SinNode[T]) Forward() { + n.BackNode.SetData(math.Sin(n.CalNode().PreNodes[0].Data())) +} + +func (n *SinNode[T]) Backward() { + a := n.PreNodes[0].Data() + backgrad := n.BackNode.Grad() + + // selfgrad + (backgrad * cos(a)) + grad0 := n.PreNodes[0].Grad() + (backgrad * math.Cos(a)) + + n.PreNodes[0].setGrad(grad0) +} +``` + +# 余弦算子 +```go +type CosNode[T data.Type] BaseCalNode[T] + +func newCos[T data.Type]() CalNode[T] { + basenode := &BaseCalNode[T]{} + return (*CosNode[T])(basenode) +} + +func (n *CosNode[T]) CalNode() *BaseCalNode[T] { + return (*BaseCalNode[T])(n) +} + +func (n *CosNode[T]) Forward() { + n.BackNode.SetData(math.Cos(n.CalNode().PreNodes[0].Data())) +} + +func (n *CosNode[T]) Backward() { + a := n.PreNodes[0].Data() + backgrad := n.BackNode.Grad() + + // selfgrad - (backgrad * sin(a)) + grad0 := n.PreNodes[0].Grad() - (backgrad * math.Sin(a)) + + n.PreNodes[0].setGrad(grad0) +} +``` + +# 数据流图 +fm/flowmap.go +```go +type FlowMap[T data.Type] struct { + calnodes *[]CalNode[T] + datanodes *[]DataNode[T] +} + +func NewFlowMap[T data.Type]() *FlowMap[T] { + calnodes := make([]CalNode[T], 0) + datanods := make([]DataNode[T], 0) + + return &FlowMap[T]{ + calnodes: &calnodes, + datanodes: &datanods, + } +} + +func (m FlowMap[T]) NewData() DataNode[T] { + node := &varDataNode[T]{ + backnodes: make([]CalNode[T], 0), + flowmap: m, + } + *m.datanodes = append(*m.datanodes, node) + return node +} + +func (m FlowMap[T]) NewConstData(i T) DataNode[T] { + return &constDataNode[T]{ + backnodes: make([]CalNode[T], 0), + data: i, + flowmap: m, + } +} +``` + +# 正向传播 +```go +func (m FlowMap[T]) Forward() { + n := len(*m.calnodes) + for i := 0; i < n; i++ { + (*m.calnodes)[i].Forward() + } +} +``` + +# 反向传播 +```go +func (m FlowMap[T]) Backward() { + for i := len(*m.datanodes) - 1; i >= 0; i-- { + (*m.datanodes)[i].setGrad(0) + } + + n := len(*m.calnodes)-1 + (*m.calnodes)[n].CalNode().BackNode.setGrad(1) + + for i := n; i >= 0; i-- { + (*m.calnodes)[i].Backward() + } +} +``` + +# 正向训练 +```go +func (m FlowMap[T]) ForwardWalk(step T) { + for i := len(*m.datanodes) - 1; i >= 0; i-- { + (*m.datanodes)[i].SetData((*m.datanodes)[i].Data() + (*m.datanodes)[i].Grad() * step) + } +} +``` + +# 反向训练 +```go +func (m FlowMap[T]) BackwardWalk(step T) { + for i := len(*m.datanodes) - 1; i >= 0; i-- { + (*m.datanodes)[i].SetData((*m.datanodes)[i].Data() - (*m.datanodes)[i].Grad() * step) + } +} +``` + +# 运行示例 +我们的运行示例使用理论图解的那个例子: 3 * pow(x, 2) + 2 * x + 1 +```go +// 3 * pow(x, 2) + 2 * x + 1 +func main() { + m := fm.NewFlowMap[float64]() + x := m.NewData() + three := m.NewConstData(3) + two := m.NewConstData(2) + one := m.NewConstData(1) + res := three.Mul(x.Pow(two)).Add(two.Mul(x)).Add(one) + + x.SetData(2) + m.Forward() + m.Backward() + // data = 3 * pow(2, 2) + 2 * 2 + 1 = 17 + // grad = 2 + 6 * x = 14 + fmt.Println("x=2 -> ", "res.data =", res.Data(), ",", "x.grad =", x.Grad()) + + x.SetData(3) + m.Forward() + m.Backward() + // data = 3 * pow(3, 2) + 2 * 3 + 1 = 34 + // grad = 2 + 6 * x = 20 + fmt.Println("x=3 -> ", "res.data =", res.Data(), ",", "x.grad =", x.Grad()) + + x.SetData(4) + m.Forward() + m.Backward() + // data = 3 * pow(4, 2) + 2 * 4 + 1 = 57 + // grad = 2 + 6 * x = 26 + fmt.Println("x=4 -> ", "res.data =", res.Data(), ",", "x.grad =", x.Grad()) +} +``` + +运行结果 +![result](https://i-blog.csdnimg.cn/blog_migrate/fcf47e485dbeb71ecc5eb7756a92b2a5.png) + +# 开发总结 +恭喜你,我们一起使用Go语言完成了深度学习的正向传播和反向传播,希望这个项目能让你有所收获😊 + +我的特色就是用最简单的方式帮助你学会最硬核的知识,一起加油吧❤️ + +我是醉墨居士,之前这个账号改了好几次名称,从此之后这个账号的名称大概率不会再变动😜 + +如果有什么错误,请你评论区或者私信我指出,让我们一起进步✌️ + +请你多多关注我,开发这个项目,并且整理总结,花费了很多的精力,博客热度越高,更新速度越快😎 \ No newline at end of file diff --git a/blog/go/download.md b/blog/go/download.md new file mode 100644 index 0000000..b1ed1ca --- /dev/null +++ b/blog/go/download.md @@ -0,0 +1,222 @@ +@[TOC] + +# 前言 +你好,我是醉墨居士,最近在开发文件传输相关的项目,然后顺手写了一个多协程文件下载器,代码非常精简,核心代码只有100行左右,适合分享给大家学习使用 + +# 流程图 +![在这里插入图片描述](https://i-blog.csdnimg.cn/direct/1e67e7fac44a465280e5d23cbdcf5765.png) + +# 主函数 +```go +func main() { + fileURL := flag.String("u", "", "downloade url of the file") + flag.Parse() + + if *fileURL == "" { + log.Println("Please input a download url") + flag.Usage() + return + } + + fileDir, err := os.Getwd() + if err != nil { + log.Println(err) + return + } + + // 下载文件保存路径 + filePath := filepath.Join(fileDir, filepath.Base(*fileURL)) + + err = downloadFile(*fileURL, filePath) + if err != nil { + log.Println(err) + return + } + + log.Println("download file success:", filePath) +} +``` + +# 下载文件 +```go +// 下载文件 +func downloadFile(fileURL string, filePath string) error { + log.Println("downloading file:", fileURL, "to", filePath) + + taskCh := make(chan [2]int64, runtime.NumCPU()) + wg := new(sync.WaitGroup) + + // 创建执行下载任务的 worker + err := initWorker(fileURL, filePath, taskCh, wg) + if err != nil { + return fmt.Errorf("init worker failed: %v", err) + } + + // 分发下载任务 + err = dispatchTask(fileURL, taskCh) + if err != nil { + return fmt.Errorf("dispacth task failed: %v", err) + } + + // 等待所有下载任务完成 + wg.Wait() + + return nil +} +``` + +# 初始化分片下载worker +```go +// 初始化 下载 worker +func initWorker(url string, filePath string, taskCh chan [2]int64, wg *sync.WaitGroup) error { + for i := 0; i < runtime.NumCPU(); i++ { + // 打开文件句柄 + file, err := os.OpenFile(filePath, os.O_CREATE|os.O_RDWR, 0644) + if err != nil { + return err + } + + wg.Add(1) + go func(file *os.File, taskCh chan [2]int64) { + defer wg.Done() + defer file.Close() + + // 循环从 taskCh 中获取下载任务并下载 + for part := range taskCh { + log.Printf("downloading part, start offset: %d, end offset: %d", part[0], part[1]) + + // 重试下载,最大重试次数为 10 次,每次下载失败后等待 1 秒 + err := retryWithWaitTime(10, func() error { + return downloadPart(url, file, part[0], part[1]) + }, time.Second) + if err != nil { + log.Printf("download part %d failed: %v", part, err) + } + } + }(file, taskCh) + } + + return nil +} +``` + +# 分发下载任务 +```go +// 分发下载任务 +func dispatchTask(url string, taskCh chan [2]int64) error { + defer close(taskCh) + + fileSize, err := getFileSize(url) + if err != nil { + return err + } + + + // 分片大小 1MB + const chunkSize = 1024 * 1024 + + parts := fileSize / chunkSize + + log.Println("file size:", fileSize, "parts:", parts, "chunk size:", chunkSize) + + for i := int64(0); i < parts; i++ { + // 计算分片的起始和结束位置 + startOffset := i * chunkSize + endOffset := startOffset + chunkSize - 1 + + // 发送下载任务 + taskCh <- [2]int64{startOffset, endOffset} + } + + // 发送最后一个分片的下载任务 + if fileSize % chunkSize != 0 { + taskCh <- [2]int64{parts * chunkSize, fileSize - 1} + } + + return nil +} +``` + +# 获取下载文件的大小 +```go +// 获取文件大小 +func getFileSize(url string) (int64, error) { + resp, err := http.Head(url) + if err != nil { + return 0, err + } + defer resp.Body.Close() + + return resp.ContentLength, nil +} +``` + +# 下载文件分片 +```go +// 下载文件分片 +func downloadPart(url string, file *os.File, startPos, endPos int64) error { + req, err := http.NewRequest("GET", url, nil) + if err != nil { + return err + } + + // 设置文件分片区间的请求头 + req.Header.Set("Range", fmt.Sprintf("bytes=%d-%d", startPos, endPos)) + resp, err := http.DefaultTransport.RoundTrip(req) + if err != nil { + return err + } + defer resp.Body.Close() + + // 如果服务器返回的状态码不是 206 Partial Content,则说明下载失败 + if resp.StatusCode != http.StatusPartialContent { + data, err := io.ReadAll(resp.Body) + if err != nil { + return err + } + log.Println("unexpected data:", string(data)) + return fmt.Errorf("unexpected status code: %d", resp.StatusCode) + } + + // 文件指针移动到分片的起始位置 + _, err = file.Seek(startPos, 0) + if err != nil { + return err + } + + // 写入分片数据到文件 + _, err = io.Copy(file, resp.Body) + if err != nil { + return err + } + + return nil +} +``` + +# 错误重试 +```go +// 重试函数 +func retryWithWaitTime(retryCount int, fn func() error, waitTime time.Duration) error { + var err error + for i := 0; i < retryCount; i++ { + e := fn() + if e != nil { + errors.Join(err, e) + time.Sleep(waitTime) + continue + } + + return nil + } + + return err +} +``` + +# 项目演示 +![在这里插入图片描述](https://i-blog.csdnimg.cn/direct/57d6a8a74813494295170c4ddb9b1d99.png) +![在这里插入图片描述](https://i-blog.csdnimg.cn/direct/f4c45150d44f4c4ca51d8b6d33468ca2.png) + +# 最后 +我是醉墨居士,如果这个项目对你有所帮助,希望你能多多支持,我们下期再见 \ No newline at end of file diff --git a/blog/go/inject.md b/blog/go/inject.md new file mode 100644 index 0000000..2b33cd5 --- /dev/null +++ b/blog/go/inject.md @@ -0,0 +1,238 @@ +@[toc] + +# 前言 +你好,我是醉墨居士,欢迎来到我的博客,今天带领大伙使用Go语言实现依赖自动注入,我们不会使用其它的第三方库,项目核心代码不到100行,是Go语言初学者难得的精简项目 + +# 依赖注入是什么 + +依赖注入(Dependency Injection,简称DI)是一种设计模式,用于实现控制反转(Inversion of Control,简称IoC)原则。它的核心思想是将对象的依赖关系从内部管理转移到外部管理,从而降低对象之间的耦合度,提高代码的灵活性和可测试性 + +# 依赖注入的好处是什么 +降低耦合度:通过将依赖关系从对象内部转移到外部,可以降低对象之间的耦合度。这样,对象只需要知道它需要什么,而不需要知道如何获取这些依赖 + +提高可测试性:依赖注入使得单元测试变得更加容易。在测试时,可以轻松地替换掉真实的依赖对象,使用模拟对象(Mock Object)或存根(Stub)来进行测试,从而隔离被测试代码 + +增强可维护性:由于依赖关系被明确地定义和管理,代码的可读性和可维护性得到了提高。当需要修改依赖关系时,只需要在配置或注入点进行修改,而不需要修改对象内部的代码 + +促进代码重用:依赖注入使得组件可以更容易地在不同的上下文中重用。因为组件不直接创建和管理自己的依赖,所以它们可以在不同的环境中被配置和使用 + +支持面向接口编程:依赖注入鼓励使用接口来定义依赖关系,而不是具体的实现类。这使得代码更加灵活,因为可以在运行时替换不同的实现,而不需要修改调用代码 + +简化对象创建:依赖注入容器(如Spring的ApplicationContext)可以自动管理对象的创建和生命周期,开发者不需要手动创建和管理这些对象,从而简化了代码 + +# 结构图 +现在介绍一下我们依赖注入这个小项目的结构图 +![在这里插入图片描述](https://i-blog.csdnimg.cn/direct/863dc6e3a5b84a1989b50264d3d2bd0e.png) + +# 应用程序上下文接口 +```go +type BeanProvider func() reflect.Value + +type ApplicationContext interface { + Inject(provider BeanProvider, name string) error + Autowise(obj any, name string) error +} +``` + +# 上下文管理器 +```go +type context struct { + namedConatiner map[string]BeanProvider + typedContainer map[reflect.Type]BeanProvider +} + +// 实现依赖注入 +func (c *context) Inject(provider BeanProvider, name string) error { + if provider == nil { + return fmt.Errorf("inject: provider can not be nil") + } + + if name == "" { + // type inject + ty := provider().Type() + + if _, ok := c.typedContainer[ty];ok { + return fmt.Errorf("inject: %v is ambiguous", ty) + } + + c.typedContainer[ty] = provider + } else { + // name inject + if _, ok := c.namedConatiner[name];ok { + return fmt.Errorf("inject: %v is ambiguous", name) + } + + c.namedConatiner[name] = provider + } + + return nil +} + +// 实现自动装配 +func (c *context) Autowise(val any, name string) error { + if val == nil { + return fmt.Errorf("inject: nil value") + } + rv := reflect.ValueOf(val) + if rv.Kind() != reflect.Ptr { + return fmt.Errorf("inject: %v is not a pointer", rv) + } + ri := reflect.Indirect(rv) + rt := ri.Type() + var provider BeanProvider + if name == "" { + // type autowise + provider = c.typedContainer[rt] + } else { + // name autowise + provider = c.namedConatiner[name] + } + + if provider == nil { + return fmt.Errorf("inject: %v is not found", name) + } + + obj := provider() + if obj.CanConvert(rt) { + ri.Set(obj.Convert(rt)) + return nil + } + + return fmt.Errorf("inject: value can not convert to %s", ri.Type()) +} +``` + +# 暴露的功能 +```go +func defaultBeanProvider(v any) BeanProvider { + return func() reflect.Value { + return reflect.ValueOf(v) + } +} + +// 对外暴露依赖注入的能力,name为空字符串时表示默认使用类型注入 +func Inject(obj any, name string) error { + return instance.Inject(defaultBeanProvider(obj), name) +} + +// 对外暴露依赖注入的能力,name为空字符串时表示默认使用类型注入 +func DeepInject(provider BeanProvider, name string) error { + return instance.Inject(provider, name) +} + +// 对外暴露自动装配的能力,name为空字符串时表示默认使用类型自动装配 +func Autowise[T any](obj *T, name string) error { + return instance.Autowise(obj, name) +} +``` + +# 使用示例 + +示例代码 +```go +package main + +import ( + "fmt" + "github.com/zm50/injector" + "reflect" +) + +type TwoString struct { + s1 *string + s2 *string +} + +func main() { + // 通过类型注入变量,注入一个string类型的变量 + var injectString string = "醉墨居士" + err := injector.Inject(injectString, "") + if err != nil { + panic(err) + } + // 通过类型装配变量,通过string类型自动装配变量 + var autowiseString string + err = injector.Autowise(&autowiseString, "") + if err != nil { + panic(err) + } + + fmt.Println("类型注入和装配的演示结果") + fmt.Println("注入的变量:", injectString, "装配的变量:", autowiseString) + + // 通过名称注入变量 + var injectName string = "醉墨" + var injectString2 string = "居士" + err = injector.Inject(injectString2, injectName) + if err != nil { + panic(err) + } + // 通过名称装配变量 + var autowiseString2 string + err = injector.Autowise(&autowiseString2, "醉墨") + if err != nil { + panic(err) + } + + fmt.Println("名称注入和装配的演示结果") + fmt.Println("注入的变量:", injectString2, "装配的变量:", autowiseString2) + + // 通过类型注入结构体指针 + injectStruct := &TwoString{} + injectStruct.s1 = new(string) + injectStruct.s2 = new(string) + *injectStruct.s1 = "醉墨" + *injectStruct.s2 = "居士" + err = injector.Inject(injectStruct, "") + if err != nil { + panic(err) + } + // 通过类型装配结构体指针 + autowiseStruct := &TwoString{} + err = injector.Autowise(&autowiseStruct, "") + if err != nil { + panic(err) + } + + fmt.Println("结构体指针注入和装配的演示结果") + fmt.Println("注入的变量:", injectStruct, *(injectStruct.s1), *(injectStruct.s2)) + fmt.Println("装配的变量:", autowiseStruct, *(autowiseStruct.s1), *(autowiseStruct.s2)) + fmt.Println("是否相等:", injectStruct == autowiseStruct, injectStruct.s1 == autowiseStruct.s1, injectStruct.s2 == autowiseStruct.s2) + + // 自定义依赖注入和装配的能力,演示自定义依赖注入和装配的能力实现深拷贝,大家可以也根据自己的需求自定义依赖注入和装配的能力 + injectStruct2 := &TwoString{ + s1: new(string), s2: new(string), + } + *(injectStruct2.s1) = "醉墨" + *(injectStruct2.s2) = "居士" + provider := func() reflect.Value { + twoString := TwoString{} + twoString.s1 = new(string) + twoString.s2 = new(string) + *twoString.s1 = *injectStruct.s1 + *twoString.s2 = *injectStruct.s2 + return reflect.ValueOf(twoString) + } + err = injector.DeepInject(provider, "") + if err != nil { + panic(err) + } + autowiseStruct2 := &TwoString{} + err = injector.Autowise(autowiseStruct2, "") + if err != nil { + panic(err) + } + + fmt.Println("自定义规则实现结构体深拷贝注入和装配的演示结果") + fmt.Println("注入的变量:", injectStruct2, *(injectStruct2.s1), *(injectStruct2.s2)) + fmt.Println("装配的变量:", autowiseStruct2, *(autowiseStruct2.s1), *(autowiseStruct2.s2)) + fmt.Println("是否相等:", injectStruct2 == autowiseStruct2, injectStruct2.s1 == autowiseStruct2.s1, injectStruct2.s2 == autowiseStruct2.s2) +} +``` + +示例结果 +![在这里插入图片描述](https://i-blog.csdnimg.cn/direct/ef9df15d4edc4ad5972f834388d98c00.png) + +# 最后 +至此,各位我们已经一起完成了这个依赖注入的小项目 +我是醉墨居士,我们下篇博客见 diff --git a/blog/go/tokenizer.md b/blog/go/tokenizer.md new file mode 100644 index 0000000..740e1c6 --- /dev/null +++ b/blog/go/tokenizer.md @@ -0,0 +1,274 @@ +@[toc] + +# 前言 +大模型的tokenizer用于将原始文本输入转化为模型可处理的输入形式。tokenizer将文本分割成单词、子词或字符,并将其编码为数字表示。大模型的tokenizer通常基于词表进行编码,使用词嵌入将单词映射为向量表示。tokenizer还可以将输入文本进行填充和截断,以确保所有输入序列的长度一致,以便于模型的批量处理。 + +这篇博客的tokenizer分析器使用纯粹的Go语言标准库实现,不借用任何其它第三方库。用轮子是生活,造轮子是信仰。 + +# 核心结构体定义 +```go +type BytePairEncoder struct { + wsToken string + unkToken string + // k: word, v: tokens + wordToken map[string]*[]string + // k: word, v: count + wordCount map[string]int + // k: token, v: count + tokenCount map[string]int + // k: id, v: token + idToken map[int]string + // k: token, v: id + tokenId map[string]int +} +``` + +# 构造函数 +```go +func DefaultBytePairEncoder() *BytePairEncoder { + return NewBytePairEncoder("_", " ") +} + +func NewBytePairEncoder(wsToken, unkToken string) *BytePairEncoder { + return &BytePairEncoder{ + wsToken: wsToken, + unkToken: unkToken, + wordToken: make(map[string]*[]string), + wordCount: make(map[string]int), + tokenCount: make(map[string]int), + idToken: make(map[int]string), + tokenId: make(map[string]int), + } +} +``` + +# 文本初始处理 +```go +func (e *BytePairEncoder) wordToTokens(word string) *[]string { + parts := []rune(word) + n := len(parts) + res := make([]string, n) + for i := 0; i < n; i++ { + token := string(parts[i]) + e.tokenCount[token]++ + res[i] = token + } + return &res +} + +func (e *BytePairEncoder) preprocess(text string) []string { + text = strings.TrimSpace(text) + return strings.Fields(text) +} + +func (e *BytePairEncoder) processWord(word string) { + e.wordToken[word] = e.wordToTokens(word) + e.wordCount[word]++ +} + +func (e *BytePairEncoder) initState(text string) { + words := e.preprocess(text) + for _, word := range words { + e.processWord(e.wsToken + word) + } +} +``` + +# 组词 +```go +func (e *BytePairEncoder) mergePair() { + // k: token, v: count + m := make(map[string]int) + for word, tokens := range e.wordToken { + n := len(*tokens) - 1 + for i := 0; i < n; i++ { + m[(*tokens)[i]+(*tokens)[i+1]] += e.wordCount[word] + } + } + + maxToken := "" + maxCount := 0 + for k, v := range m { + if v > maxCount { + maxToken = k + maxCount = v + } + } + + if maxCount < 2 { + return + } + + e.tokenCount[maxToken] = maxCount + + for _, tokens := range e.wordToken { + n := len(*tokens) - 1 + for i := 0; i < n; i++ { + if (*tokens)[i]+(*tokens)[i+1] == maxToken { + e.tokenCount[(*tokens)[i]]-- + e.tokenCount[(*tokens)[i+1]]-- + post := (*tokens)[i+1:] + post[0] = maxToken + *tokens = (*tokens)[:i] + *tokens = append((*tokens), post...) + *tokens = (*tokens)[:len(*tokens)] + + i-- + n -= 2 + } + } + } +} + +func (e *BytePairEncoder) merge(steps int) { + for i := 0; i < steps; i++ { + e.mergePair() + } +} +``` + +# 构建词组索引 +```go +func (e *BytePairEncoder) buildIndex() { + e.tokenId[e.unkToken] = 0 + e.idToken[0] = e.unkToken + i := 1 + for token := range e.tokenCount { + e.tokenId[token] = i + e.idToken[i] = token + i++ + } +} +``` + +# 训练数据 +```go +func (e *BytePairEncoder) Train(text string, steps int) { + e.initState(text) + e.merge(steps) + e.buildIndex() +} +``` + +# 编码 +```go +func (e *BytePairEncoder) segment(words []string) []int { + res := make([]int, 0) + for _, word := range words { + parts := []rune(word) + NEXT: + for i := len(parts); i >= 1; i-- { + if code, ok := e.tokenId[string(parts[:i])]; ok { + parts = parts[i:] + res = append(res, code) + goto NEXT + } + } + if len(parts) == 0 { + continue + } + code := e.tokenId[string(parts[0])] + res = append(res, code) + parts = parts[1:] + if len(parts) != 0 { + goto NEXT + } + } + + return res +} + +func (e *BytePairEncoder) Encode(text string) []int { + words := e.preprocess(text) + return e.segment(words) +} +``` + +# 解码 +```go +func (e *BytePairEncoder) Decode(codes []int) []string { + res := make([]string, 0) + for _, code := range codes { + res = append(res, e.idToken[code]) + } + + return res +} +``` + +# 打印状态信息 +```go +func (e *BytePairEncoder) Dump() { + fmt.Println("===== dump state ======") + fmt.Println("===> dump wordToken <===") + for word, tokens := range e.wordToken { + fmt.Println(word, "=>", *tokens) + } + fmt.Println() + fmt.Println("===> dump wordcnt <===") + for word, count := range e.wordCount { + fmt.Println(word, "=>", count) + } + fmt.Println() + fmt.Println("===> dump tokenCount <===") + for token, count := range e.tokenCount { + fmt.Println(token, "=>", count) + } + fmt.Println() + fmt.Println("===> dump idTokens <===") + for code, token := range e.idToken { + fmt.Println(code, "=>", token) + } + fmt.Println() + fmt.Println("===> dump tokenIds <===") + for token, code := range e.tokenId { + fmt.Println(token, "=>", code) + } + fmt.Println() +} +``` + +# 运行效果 +我们的tokenizer已经开发完毕,现在可以运行我们的tokenizer,看看是否能达到我们想要的效果 +```go +package main + +import ( + "fmt" + "os" + "tokenizer" +) + +func main() { + trainData, err := os.ReadFile("./data.txt") + if err != nil { + panic(err) + } + steps := 50 + enc := tokenizer.DefaultBytePairEncoder() + enc.Train(string(trainData), steps) + input := "提取数据特征进行预测" + codes := enc.Encode(input) + tokens := enc.Decode(codes) + fmt.Println(codes) + fmt.Println(tokens) +} +``` + +输入数据集 +data.txt +```txt +机器学习、深度学习和强化学习是人工智能领域中的三个重要技术方向。以下是它们的区别: +机器学习:机器学习是一种通过从数据中自动学习模式和规律来进行预测和决策的方法。它涉及到使用算法和统计模型,从数据中提取特征并进行模型训练,进而对未知数据进行预测或分类。机器学习的重点在于自动学习和泛化能力,它不需要明确的指令或规则来执行任务,而是通过数据和经验来改善性能。 +深度学习:深度学习是机器学习的一个分支,它使用包含多个神经网络层的深度神经网络进行学习和预测。深度学习模型通过层层堆叠的方式,从原始数据中学习到多个抽象层次的特征表示。深度学习的优势在于可以自动提取复杂的特征,并通过大规模数据的训练来优化模型性能。它被广泛应用于计算机视觉、自然语言处理和语音识别等领域。 +强化学习:强化学习是一种机器学习的方法,旨在让机器学习从环境中的交互中通过试错来改善性能。它通过不断与环境进行交互,观察环境状态并执行动作,然后从环境的反馈中学习如何在给定环境中做出最优的决策。强化学习的目标是通过学习最优的策略来最大化累积奖励。强化学习在游戏、机器人控制和优化问题等领域有着广泛应用。 +总的来说,机器学习是从数据中学习模式和规律,深度学习是机器学习的一种方法,使用深度神经网络来提取复杂的特征表示,强化学习是通过试错学习从环境中改善性能。 +``` + +运行效果 +![在这里插入图片描述](https://i-blog.csdnimg.cn/blog_migrate/a340d3df14f8e62069a7f03cd56d2871.png) + +可以根据情况使用Dump函数打印状态信息查看更多细节 + +# 总结 +恭喜你已经制作了一个属于自己的tokenizer分词器,我们实现的相对粗糙一点,但是对于初学者是难得的实战项目,麻雀虽小,五脏俱全。 \ No newline at end of file diff --git a/blog/middleware/redis_dict_code.md b/blog/middleware/redis_dict_code.md new file mode 100644 index 0000000..87aaf6b --- /dev/null +++ b/blog/middleware/redis_dict_code.md @@ -0,0 +1,356 @@ + +@[toc] + +# 前言 + +哈希表是Redis中非常重要的数据结构,这篇博客我们就一起来探索一下Redis中哈希表的奥秘😁 + +# 代码位置 + +src/dict.h +src/dict.c + +# 哈希表 + +- 原理 + +哈希表用于键值对的存储和查找,通过哈希函数将键映射到一个的索引上,来保存相应的值 + +- 优势 + +哈希表的优势是增删改查的时间复杂度都是O(1),哈希表在大量的数据中也能保持良好的性能,因为哈希函数会将键均匀地分散在整个数组中 + +- 问题 + +多个键通过哈希函数映射到同一个索引时,就会产生哈希冲突 + +- 解决问题 + +常见的解决哈希冲突的方法有链式哈希法或开放寻址法 + +在链式哈希法中,每个索引位置上都存储一个桶,每个桶是个链表,用来链接冲突的键值对 + +在开放寻址法中,当发生冲突时,会继续向后探测数组,直到找到一个空闲的位置来存储冲突的键值对 + +- redis选型 + +redis使用了链式哈希法来实现hash表,使用渐进式 rehash 方法来减少哈希冲突 +- rehash + +就是创建一个更大的hash表,然后将原本的hash表迁移过去,因为新hash表更大,造成哈希冲突的几率也就更小 + +- 渐进式rehash + +因为rehash操作需要迁移整个hash表,代价很大,我们可以在不影响redis对外正常服务的情况下逐步的进行迁移 +在迁移过程中新的数据写入只会发生在新哈希表中,旧哈希表仅用于读取操作。这样可以避免写入操作复杂度的增加 +当所有数据都完成迁移后,Redis会将新哈希表替换旧哈希表,完成rehash过程 +# 核心代码 + +```c +// 哈希表的行为 +typedef struct dictType { + // 计算哈希值的函数 + uint64_t (*hashFunction)(const void *key); + // 复制键的函数 + void *(*keyDup)(dict *d, const void *key); + // 复制值的函数 + void *(*valDup)(dict *d, const void *obj); + // 比较键的函数 + int (*keyCompare)(dict *d, const void *key1, const void *key2); + // 销毁键的函数 + void (*keyDestructor)(dict *d, void *key); + // 销毁值的函数 + void (*valDestructor)(dict *d, void *obj); + // hash表扩展 + int (*expandAllowed)(size_t moreMem, double usedRatio); + // 开启rehash,此时新旧hash表已经创建过了 + void (*rehashingStarted)(dict *d); + // rehash完成后的钩子函数,通常是一些清理工作,比如释放临时分配的内存或者更新哈希表的状态信息 + void (*rehashingCompleted)(dict *d); + // 获取hash表中元数据所占用的字节数 + size_t (*dictMetadataBytes)(dict *d); + // 标识是否使用值 + unsigned int no_value:1; + /* If no_value = 1 and all keys are odd (LSB=1), setting keys_are_odd = 1 + * enables one more optimization: to store a key without an allocated + * dictEntry. */ + // 如果 no_value = 1,且所有键都是奇数,则设置keys_are_odd = 1可以启用优化:存储未分配dictEntry的键 + unsigned int keys_are_odd:1; + /* TODO: Add a 'keys_are_even' flag and use a similar optimization if that + * flag is set. */ +} dictType; +``` + +```c +// 哈希表中的元素 +struct dictEntry { + // 键 + void *key; + // 值,小技巧:如果值是uint64_t、int64_t、double中的,就直接存储对应内容,无需使用指针,减少内存开销 + union { + void *val; + uint64_t u64; + int64_t s64; + double d; + } v; + // 下一个元素 + struct dictEntry *next; +}; +``` + +```c +// 哈希表 +struct dict { + // 指定hash表的行为 + dictType *type; + + // 两张hash表,在rehash时交替使用,每张hash表里面是二维的dictEntry + dictEntry **ht_table[2]; + + // 两张哈希表中键值对的使用数量 + unsigned long ht_used[2]; + + // 标识是否正在进行rehash, -1表示没有进行rehash + long rehashidx; + + // 标识是否暂停rehash,>0表示暂停rehash,<0表示编码错误 + int16_t pauserehash; + + // 大小的指数,size = 1 << exp + signed char ht_size_exp[2]; + + // 元数据 + void *metadata[]; +}; +``` + +以上实现我们不难看出,如果哈希冲突过多会使dictEntry链表变长,导致操作该位置的hash表在性能上减弱 + + + +# rehash + +rehash是扩充hash表的一个操作,它可以减少哈希冲突的概率,Redis中rehash操作是渐进式的,当触发rehash操作时,逐渐地将旧hash表的数据放入新hash表中,最终当数据转移完成之后旧hash表的空间会被释放 + +```c +// 如果需要进行扩容 +static int _dictExpandIfNeeded(dict *d) +{ + // 如果已经在进行rehash操作就直接退出 + if (dictIsRehashing(d)) return DICT_OK; + + // 若hash表为空,就扩容成初始大小 + if (DICTHT_SIZE(d->ht_size_exp[0]) == 0) return dictExpand(d, DICT_HT_INITIAL_SIZE); + + // 启用了rehash且哈希表的大小达到或超过当前容量 或者 未禁止rehash且当前hash表的使用率大于比率阈值 + if ((dict_can_resize == DICT_RESIZE_ENABLE && + d->ht_used[0] >= DICTHT_SIZE(d->ht_size_exp[0])) || + (dict_can_resize != DICT_RESIZE_FORBID && + d->ht_used[0] / DICTHT_SIZE(d->ht_size_exp[0]) > dict_force_resize_ratio)) + { + /// 如果hash表中表示已经扩容过,就退出 + if (!dictTypeExpandAllowed(d)) + return DICT_OK; + + // 触发扩容 + return dictExpand(d, d->ht_used[0] + 1); + } + return DICT_OK; +} +``` +```c +// 扩容 +int _dictExpand(dict *d, unsigned long size, int* malloc_failed) +{ + if (malloc_failed) *malloc_failed = 0; + + // 如果正在进行rehash 或者 当前使用的hash表的大小大于将要分配的容量,直接退出 + if (dictIsRehashing(d) || d->ht_used[0] > size) + return DICT_ERR; + + // 新hash表 + dictEntry **new_ht_table; + // 新hash表中元素的使用数量 + unsigned long new_ht_used; + // 新hash表的大小指数 + signed char new_ht_size_exp = _dictNextExp(size); + + // 计算新hash表的大小 + size_t newsize = DICTHT_SIZE(new_ht_size_exp); + // 如果新大小不够,则直接返回 + if (newsize < size || newsize * sizeof(dictEntry*) < newsize) + return DICT_ERR; + + // 若大小指数未变化,则返回 + if (new_ht_size_exp == d->ht_size_exp[0]) return DICT_ERR; + + if (malloc_failed) { + // 检查分配是否会失败 + // 尝试进行分配新的hash表 + new_ht_table = ztrycalloc(newsize*sizeof(dictEntry*)); + // 标识分配是否失败 + *malloc_failed = new_ht_table == NULL; + if (*malloc_failed) + // 分配失败,直接返回 + return DICT_ERR; + } else + // 分配新的hash表 + new_ht_table = zcalloc(newsize*sizeof(dictEntry*)); + + // 新的hash表使用量为0 + new_ht_used = 0; + + // 新hash表初始化 + d->ht_size_exp[1] = new_ht_size_exp; + d->ht_used[1] = new_ht_used; + d->ht_table[1] = new_ht_table; + d->rehashidx = 0; + // 执行rehash启动的钩子函数 + if (d->type->rehashingStarted) d->type->rehashingStarted(d); + + // 如果hash表为空 + if (d->ht_table[0] == NULL || d->ht_used[0] == 0) { + // 执行rehash结束后的钩子函数 + if (d->type->rehashingCompleted) d->type->rehashingCompleted(d); + // 如果旧hash表未释放,则释放掉 + if (d->ht_table[0]) zfree(d->ht_table[0]); + // 旧hash表重新指向分配并迁移完成的新hash表 + d->ht_size_exp[0] = new_ht_size_exp; + d->ht_used[0] = new_ht_used; + d->ht_table[0] = new_ht_table; + // 重置hash表,将旧表中的所有元素都会被释放,确保新的哈希表不会包含旧的元素,保证哈希表的效率和一致性 + _dictReset(d, 1); + // 标志rehash结束 + d->rehashidx = -1; + return DICT_OK; + } + + return DICT_OK; +} + +// rehash,不考虑内存分配是否成功 +int dictExpand(dict *d, unsigned long size) { + return _dictExpand(d, size, NULL); +} + +// rehash,考虑内存分配是否成功 +int dictTryExpand(dict *d, unsigned long size) { + int malloc_failed; + _dictExpand(d, size, &malloc_failed); + return malloc_failed? DICT_ERR : DICT_OK; +} +``` +```c +int dictRehash(dict *d, int n) { + // 最多访问n*10个空桶 + int empty_visits = n*10; + // hash表0的大小 + unsigned long s0 = DICTHT_SIZE(d->ht_size_exp[0]); + // hash表1的大小 + unsigned long s1 = DICTHT_SIZE(d->ht_size_exp[1]); + // 若禁止resize或者未进行rehash,则返回 + if (dict_can_resize == DICT_RESIZE_FORBID || !dictIsRehashing(d)) return 0; + // 如果避免resize,且s1大于s0且s1 / s0的比率小于resize的阈值 或者 s1小于s0且s0 / s1的比率小于resize的阈值,则返回 + if (dict_can_resize == DICT_RESIZE_AVOID && + ((s1 > s0 && s1 / s0 < dict_force_resize_ratio) || + (s1 < s0 && s0 / s1 < dict_force_resize_ratio))) + { + return 0; + } + + // 主循环,根据要拷贝的bucket数量n,循环n次后停止或ht[0]中的数据迁移完停止 + while(n-- && d->ht_used[0] != 0) { + dictEntry *de, *nextde; + + /* Note that rehashidx can't overflow as we are sure there are more + * elements because ht[0].used != 0 */ + assert(DICTHT_SIZE(d->ht_size_exp[0]) > (unsigned long)d->rehashidx); + // 遍历旧hash表,找到第一个不为空的桶 + while(d->ht_table[0][d->rehashidx] == NULL) { + d->rehashidx++; + // 如果访问空桶数量达到阈值,则返回 + if (--empty_visits == 0) return 1; + } + // 当前不为空的桶 + de = d->ht_table[0][d->rehashidx]; + /* Move all the keys in this bucket from the old to the new hash HT */ + // 遍历该桶 + while(de) { + // 新节点在新hash表中的索引位置 + uint64_t h; + + // 保存下一个哈希节点的指针,因为重新散列过程中当前哈希节点可能会被释放或者重新分配位置 + nextde = dictGetNext(de); + + // 获取该节点的key + void *key = dictGetKey(de); + + // 计算新哈希节点在新哈希表中的索引位置 + if (d->ht_size_exp[1] > d->ht_size_exp[0]) { + h = dictHashKey(d, key) & DICTHT_SIZE_MASK(d->ht_size_exp[1]); + } else { + h = d->rehashidx & DICTHT_SIZE_MASK(d->ht_size_exp[1]); + } + + // 判断当前节点是否存有value + if (d->type->no_value) { + // 若所有键都是奇数且新hash表在h位置的桶不存在,则存储未分配dictEntry的键 + if (d->type->keys_are_odd && !d->ht_table[1][h]) { + assert(entryIsKey(key)); + // 若当前节点是dictEntry,则进行优化,只存储未分配dictEntry的键 + if (!entryIsKey(de)) zfree(decodeMaskedPtr(de)); + de = key; + } else if (entryIsKey(de)) { // 判断当前节点是否只存在键 + /* We don't have an allocated entry but we need one. */ + // 只存储未分配dictEntry的键 + de = createEntryNoValue(key, d->ht_table[1][h]); + } else { + /* Just move the existing entry to the destination table and + * update the 'next' field. */ + assert(entryIsNoValue(de)); + // 将当前节点的下一个指针指向新hash表中h槽位的头指针 + dictSetNext(de, d->ht_table[1][h]); + } + } else { + // 将当前节点的下一个指针指向新hash表中h槽位的头指针 + dictSetNext(de, d->ht_table[1][h]); + } + // 设置新hash表中h槽位的元素为当前的桶 + d->ht_table[1][h] = de; + // 更新两个哈希表的节点数量 + d->ht_used[0]--; + d->ht_used[1]++; + // de指向下一个节点,用于下次循环 + de = nextde; + } + // 从旧hash表中移除当前元素 + d->ht_table[0][d->rehashidx] = NULL; + + // 遍历下一个元素 + d->rehashidx++; + } + + // 检查是否已经完成整个hash表的rehash了 + if (d->ht_used[0] == 0) { + if (d->type->rehashingCompleted) d->type->rehashingCompleted(d); + // 释放旧hash表 + zfree(d->ht_table[0]); + // 让旧hash表指向新hash表 + d->ht_table[0] = d->ht_table[1]; + d->ht_used[0] = d->ht_used[1]; + d->ht_size_exp[0] = d->ht_size_exp[1]; + // 重置新哈希表的状态 + _dictReset(d, 1); + // 关闭hash表的渐进式 rehash 标志 + d->rehashidx = -1; + // 返回0,表示rehash完成 + return 0; + } + + // 返回1,表示rehash未完成 + return 1; +} +``` + +# 最后 +恭喜我们一起看完了redis中哈希表的核心源码,渐进式rehash的源码,希望你能有所收获😉 \ No newline at end of file diff --git a/blog/system/linux_network.md b/blog/system/linux_network.md new file mode 100644 index 0000000..3ba605c --- /dev/null +++ b/blog/system/linux_network.md @@ -0,0 +1,42 @@ +@[toc] + +# 前言 +你好,我是醉墨居士,因为Linux内核涉及的内容极多,我们初学者如果一上来就开始深挖细节,很有可能会在Linux内核代码的茫茫大海之中迷失自我。本篇博客希望可以帮助大家先有个全局的视野,剩余的细节内容,根据沿着全局的脉络去学习,可能会有意想不到的效果🫠 + +# 网络协议栈 +#### 图解 +![!\[在这里插入图片描述\](https://img-blog.csdnimg.cn/direct/00c1ab74129f491bb39a893c339fe2fc.png](https://i-blog.csdnimg.cn/blog_migrate/b9c67f3f8cadf6d52d2242e6c1fe21fc.png) +#### 功能 +>应用层负责为上层应用程序提供用户接口 +传输层负责端到端通信,数据的分割与重组,多路复用与解复用 +网络层负责路由选择,数据的分组与重组,转发分组报文 +链路层负责错误检测与纠正,帧同步,处理物理地址 +物理层负责在网络中传输原始的比特流 + +# 发送Linux内核网络数据包 +#### 图解 +![在这里插入图片描述](https://i-blog.csdnimg.cn/blog_migrate/05a8bdb7ed01b8fd55ff01fcf02ee119.png) +#### 流程 +>1.应用协议栈处理:对用户数据进行封装成应用数据 +2.CPU Copy:CPU参与将用户空间的数据拷贝到内核的套接字缓冲区中 +3.协议栈处理:将套接字缓冲区中的应用数据送给Linux内核网络协议栈进行封包后放入sk_buff +4.建立DMA映射:表示sk_buff中的数据已经装载完毕,设置sk_buff的地址和长度等 +5.DMA Copy:通过DMA方式将sk_buff中的数据通过Rx Ring buffer拷贝到网卡的Rx FIFO +6.发送数据:通过有线或者无线的方式将数据发送到网络中 + +# 接收Linux内核网络数据包 +#### 图解 +![在这里插入图片描述](https://i-blog.csdnimg.cn/blog_migrate/3ea5138fa91a927bb0e66c3f8af9f771.png) + +#### 流程 +>1.接收数据:通过有线或者无线的方式接收网络中的数据 +2.DMA Copy:通过DMA方式将网卡的Tx FIFO中的数据通过Tx Ring Buffer拷贝到sk_buff +3.硬件中断IRQ:跳转到对应IRQ的中断服务例程(ISR)来处理该事件 +4.软中断:驱动程序通过软中断执行对应的软中断处理程序 +5.协议栈处理:将sk_buff中的数据通过Linux内核网络协议栈进行拆包放入套接字缓冲区 +6.CPU Copy:CPU参与将内核的套接字缓冲区拷贝到用户空间 +7.应用协议栈处理:对应用数据解封装成用户数据 + +# 最后 +我是醉墨居士,这篇博客我们大概梳理了一下Linux网络传输的总体流程😎 +如果有问题可以及时通知我,私聊,评论都行哈🤗 diff --git a/code/index.html b/code/index.html index 43da3d6..5095225 100644 --- a/code/index.html +++ b/code/index.html @@ -21,8 +21,27 @@

编程空间

+

让编程成为你的艺术

+
+

Go语言专栏

- +

中间件专栏

+ +

系统架构专栏

+ +

计算机网络专栏

+

Docker专栏

+

Kubernetes专栏

+

牛客刷题专栏