Gin路由算法模拟

2023-05-16

概述

Gin的路由算法是采用压缩字典树实现的,基数树(Radix Tree)又称为PAT位树(Patricia Trie or crit bit tree),是一种更节省空间的前缀树(Trie Tree)。对于基数树的每个节点,如果该节点是唯一的子树的话,就和父节点合并。下图为一个基数树示例:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-iuCbn1kc-1650874481342)(https://www.hengyumo.cn/momoclouddisk/file/download?code=202203231440228_image.png)]

https://www.cnblogs.com/randysun/p/15841366.html

算法模拟

结构体组成:

// TrieNode 压缩字典树节点
type TrieNode struct {
	// 存储子节点的索引
	indices string
	// 存储当前节点的值
	value string
	// 子节点
	children []*TrieNode
}

// NewTrieTree 创建一颗新Trie树
func NewTrieTree() *TrieNode {
	return &TrieNode{}
}

新增节点,主要考虑的难点是存在部分共同前缀时需要进行分裂:

// InsertNode 向TireTree中新增节点
func (n *TrieNode) InsertNode(str string) (inserted bool) {
	// 如果节点值为空,当前节点为根节点
	// 如果当前节点的值和str的前缀完全一致
	lenn := len(n.value)
	if lenn < len(str) && n.value == str[0:lenn] || n.value == "" {
		// 如果前缀不在索引中,加到索引
		// 并且由于子节点不存在共同前缀,因此,新增节点
		c := str[lenn]
		if strings.IndexByte(n.indices, c) == -1 {
			// 子节点都不存在共同前缀=>新增节点到当前节点下
			n.indices += string(c)
			newNode := TrieNode{
				indices:  "",
				value:    str[lenn:],
				children: nil,
			}
			if n.children == nil {
				n.children = []*TrieNode{
					&newNode,
				}
			} else {
				n.children = append(n.children, &newNode)
			}
			return true
		} else if n.children != nil {
			// 子节点存在共同前缀:向下寻找子节点进行对比
			for _, child := range n.children {
				// 如果前缀无法匹配,快速结束
				strSub := str[lenn:]
				if strSub[0] != child.value[0] {
					continue
				}
				// 向下时裁切掉前缀部分
				inserted = child.InsertNode(strSub)
				// 快速结束
				if inserted {
					return
				}
			}
		}
	}
	// 不存在共同前缀
	// 如果存在部分匹配前缀,需要进行分裂,增加新节点
	index := FindPrefix(n.value, str)
	if index != -1 {
		// 当前节点变为共同前缀,向下分裂一个子节点
		i := index + 1
		p := n.value[0:i]
		s := n.value[i:]
		s2 := str[i:]
		n.value = p
		newNode := TrieNode{
			// 继承node的索引
			indices:  n.indices,
			value:    s,
			children: n.children,
		}
		// 第二个节点
		newNode2 := TrieNode{
			indices:  "",
			value:    s2,
			children: nil,
		}
		// 更新node的索引
		n.indices = s[0:1]
		s2Idx := s2[0:1]
		if n.indices != s2Idx {
			n.indices += s2Idx
		}
		// 分裂之后,node拥有新的两个节点作为孩子
		n.children = []*TrieNode{
			&newNode, &newNode2,
		}
		return true
	}

	return false
}

节点查找:递归版本

// FindNode 查找字符串在树是否存在
func (n *TrieNode) FindNode(str string) (found bool) {
	if str == "" {
		return false
	}
	// 如果当前节点匹配字符串,则匹配,否则匹配前缀,查找索引,向下查找子节点
	if str == n.value {
		return true
	}
	lenn := len(n.value)
	if len(str) > lenn && str[0:lenn] == n.value && n.children != nil {
		// 匹配前缀,查找索引
		// 切割去除前缀
		str = str[lenn:]
		// str的索引
		idx := str[0]
		// 如果当前node的索引包含 idx
		if strings.IndexByte(n.indices, idx) != -1 {
			// 存在索引
			for _, child := range n.children {
				if child.value[0] == idx {
					// 加入快速匹配
					if child.value == str {
						return true
					}
					// 匹配该子节点,继续向下匹配
					found = child.FindNode(str)
					// 快速结束
					if found {
						return
					}
				} else {
					// 该子节点不匹配索引
					continue
				}
			}
		}
	}

	return
}

格式化打印树:


// FormatTree 格式化树结构
// --
//   |__ a
//     |__ bd
//     |__ d
func (n *TrieNode) FormatTree() string {
	var buf bytes.Buffer
	if n.value == "" {
		buf.WriteString("\n-- \n")
	} else {
		buf.WriteString("|__ " + n.value + "\n")
	}
	if n.children != nil {
		for _, child := range n.children {
			childStr := child.FormatTree()
			// 增加前缀
			// 分割行
			sps := strings.Split(childStr, "\n")
			for _, sp := range sps {
				if sp != "" {
					buf.WriteString("  ")
					buf.WriteString(sp)
					buf.WriteString("\n")
				}
			}
		}
	}
	return buf.String()
}

非递归版本查找算法:


// FindNode2 查找字符串在树是否存在-非递归版本实现
func (n *TrieNode) FindNode2(str string) (found bool) {
	nowNode := n
walk:
	if str == "" {
		return false
	}
	// 如果当前节点匹配字符串,则匹配,否则匹配前缀,查找索引,向下查找子节点
	if str == nowNode.value {
		return true
	}
	lenn := len(nowNode.value)
	if len(str) > lenn && str[0:lenn] == nowNode.value && nowNode.children != nil {
		// 匹配前缀,查找索引
		// 切割去除前缀
		str = str[lenn:]
		// str的索引
		idx := str[0]
		// 如果当前node的索引包含 idx
		if strings.IndexByte(nowNode.indices, idx) != -1 {
			// 存在索引
			for _, child := range nowNode.children {
				if child.value[0] == idx {
					// 匹配该子节点,继续向下匹配
					nowNode = child
					goto walk
				} else {
					// 该子节点不匹配索引
					continue
				}
			}
		}
	}

	return
}

算法性能测试和对比

新增节点的耗时和内存消耗对比,因为gin的节点包含更多信息,所以gin的内存占用是最多的,占用内存最少的是List存储,其次是hashmap,然后是压缩字典树,为什么压缩字典树内存消耗这么多呢,很大程度是因为索引。索引使得每个节点需要存储的信息大大增加。

// 内存占用分析,数量级 1000_0000
// go test -run '^.*InsertNodeMem.*$' -v
// === RUN   Test_InsertNodeMem_TrieTree
//    testgin2_test.go:165: startMem usage:0 MB
//    testgin2_test.go:174: memory usage:1359 MB
//--- PASS: Test_InsertNodeMem_TrieTree (16.03s)
//=== RUN   Test_InsertNodeMem_HashMap
//    testgin2_test.go:181: startMem usage:1359 MB
//    testgin2_test.go:189: memory usage:552 MB
//--- PASS: Test_InsertNodeMem_HashMap (2.29s)
//=== RUN   Test_InsertNodeMem_List
//    testgin2_test.go:196: startMem usage:1911 MB
//    testgin2_test.go:204: memory usage:381 MB
//--- PASS: Test_InsertNodeMem_List (0.84s)
//PASS
//ok      mytest/testgin/testgin2 19.585s

//=== RUN   Test_InsertNode_GinTree
//    testgin2_test.go:369: startMem usage:1 MB
//    testgin2_test.go:378: memory usage:2192 MB
//--- PASS: Test_InsertNode_GinTree (16.64s)
var size = 1000_0000
func Test_InsertNodeMem_TrieTree(t *testing.T) {
	mem := runtime.MemStats{}
	runtime.ReadMemStats(&mem)
	startMem := mem.TotalAlloc/MiB
	t.Logf("startMem usage:%d MB", startMem)
	// 初始化
	tree := NewTrieTree()
	for i := 0; i < size; i++ {
		str := strconv.Itoa(rand.Int())
		tree.InsertNode(str)
	}
	runtime.ReadMemStats(&mem)
	curMem := mem.TotalAlloc/MiB - startMem
	t.Logf("memory usage:%d MB", curMem)
}
func Test_InsertNodeMem_HashMap(t *testing.T) {
	// 初始化
	mem := runtime.MemStats{}
	runtime.ReadMemStats(&mem)
	startMem := mem.TotalAlloc/MiB
	t.Logf("startMem usage:%d MB", startMem)
	m := make(map[string]bool, size)
	for i := 0; i < size; i++ {
		str := strconv.Itoa(rand.Int())
		m[str] = true
	}
	runtime.ReadMemStats(&mem)
	curMem := mem.TotalAlloc/MiB - startMem
	t.Logf("memory usage:%d MB", curMem)
}
func Test_InsertNodeMem_List(t *testing.T) {
	// 初始化
	mem := runtime.MemStats{}
	runtime.ReadMemStats(&mem)
	startMem := mem.TotalAlloc/MiB
	t.Logf("startMem usage:%d MB", startMem)
	l := make([]string, size)
	for i := 0; i < size; i++ {
		str := strconv.Itoa(rand.Int())
		l[i] = str
	}
	runtime.ReadMemStats(&mem)
	curMem := mem.TotalAlloc/MiB - startMem
	t.Logf("memory usage:%d MB", curMem)
}

// 复制了一份gin树的算法,测试其性能,gin耗费内存比自己写的TrieTree更多(考虑到其使用了更多属性用于存储数据)
//
//=== RUN   Test_InsertNode_GinTree
//    testgin2_test.go:369: startMem usage:1 MB
//    testgin2_test.go:378: memory usage:2192 MB
//--- PASS: Test_InsertNode_GinTree (16.64s)
func Test_InsertNode_GinTree(t *testing.T)  {
	mem := runtime.MemStats{}
	runtime.ReadMemStats(&mem)
	startMem := mem.TotalAlloc/MiB
	t.Logf("startMem usage:%d MB", startMem)
	// 初始化
	tree := &node{}
	for i := 0; i < size; i++ {
		str := strconv.Itoa(rand.Int())
		tree.addRoute(str, []HandlerFunc{func(*gin.Context) {}})
	}
	runtime.ReadMemStats(&mem)
	curMem := mem.TotalAlloc/MiB - startMem
	t.Logf("memory usage:%d MB", curMem)
}

对比查找性能:性能不如hashmap也情有可原,毕竟字典树的优点最主要是支持通配符路由,gin的性能不错(因为实现了优先级,查找的平均用时会更短)。但是距离hashmap还有五倍的落后。

每个树级别上的子节点都按Priority(优先级)排序,其中优先级(最左列)就是在子节点(子节点、子子节点等等)中注册的句柄的数量。这样做有两个好处:

首先优先匹配被大多数路由路径包含的节点。这样可以让尽可能多的路由快速被定位。
类似于成本补偿。最长的路径可以被优先匹配,补偿体现在最长的路径需要花费更长的时间来定位,如果最长路径的节点能被优先匹配(即每次拿子节点都命中),那么路由匹配所花的时间不一定比短路径的路由长。下面展示了节点(每个-可以看做一个节点)匹配的路径:从左到右,从上到下。


// 性能测试,查找性能,测试 1000 w数量随机整数,查找任意一个整数
// go test -run '^$' -bench '.*FindNode$' -benchmem
// 可以看到,性能和hashmap相比并无优势,差距达到10倍
// Benchmark_FindNode/TrieTree-24            520750              2454 ns/op              23 B/op          1 allocs/op
// Benchmark_FindNode/HashMap-24            5526843               222.6 ns/op            23 B/op          1 allocs/op
// 优化后:
// Benchmark_FindNode/TrieTree-24            679867              1758 ns/op              23 B/op          1 allocs/op
// Benchmark_FindNode/HashMap-24            5332783               240.9 ns/op            23 B/op          1 allocs/op
// 对比遍历查找,性能无疑好了非常多:
// Benchmark_FindNode/TrieTree-24            686958              1689 ns/op              23 B/op          1 allocs/op
// Benchmark_FindNode/HashMap-24            5461135               221.3 ns/op            23 B/op          1 allocs/op
// Benchmark_FindNode/List-24                    25          43738816 ns/op              24 B/op          1 allocs/op
// 对比gin,gin的性能确实不错
//Benchmark_FindNode/TrieTree-24            682962              1794 ns/op              23 B/op          1 allocs/op
//Benchmark_FindNode/HashMap-24            5606622               207.5 ns/op            23 B/op          1 allocs/op
//Benchmark_FindNode/List-24                    24          48739212 ns/op              24 B/op          1 allocs/op
//Benchmark_FindNode/Gin-24                1000000              1106 ns/op              23 B/op          1 allocs/op
// 增加了findNode的非递归版本,对比性能:
// 性能相比递归版本略有提升
//Benchmark_FindNode/TrieTree-24            599498              1819 ns/op              23 B/op          1 allocs/op
//Benchmark_FindNode/TrieTree2-24           667114              1547 ns/op              23 B/op          1 allocs/op
//Benchmark_FindNode/HashMap-24            5671921               213.0 ns/op            23 B/op          1 allocs/op
//Benchmark_FindNode/List-24                    21          51678314 ns/op              24 B/op          1 allocs/op
//Benchmark_FindNode/Gin-24                 956608              1123 ns/op              23 B/op          1 allocs/op
func Benchmark_FindNode(b *testing.B) {
   // 初始化
   b.StopTimer()
   tree := NewTrieTree()
   ginTree := &node{}
   size := 1000_0000
   m := make(map[string]bool, size)
   l := make([]string, size)
   for i := 0; i < size; i++ {
   	str := strconv.Itoa(rand.Int())
   	tree.InsertNode(str)
   	ginTree.addRoute(str, []HandlerFunc{func(*gin.Context) {}})
   	l[i] = str
   	m[str] = true
   }
   b.StartTimer()
   // 开始
   b.Run("TrieTree", func(b *testing.B) {
   	for i := 0; i < b.N; i++ {
   		str := strconv.Itoa(rand.Int())
   		tree.FindNode(str)
   	}
   })
   b.Run("TrieTree2", func(b *testing.B) {
   	for i := 0; i < b.N; i++ {
   		str := strconv.Itoa(rand.Int())
   		tree.FindNode2(str)
   	}
   })
   b.Run("HashMap", func(b *testing.B) {
   	for i := 0; i < b.N; i++ {
   		str := strconv.Itoa(rand.Int())
   		_ = m[str] == true
   	}
   })
   b.Run("List", func(b *testing.B) {
   	for i := 0; i < b.N; i++ {
   		str := strconv.Itoa(rand.Int())
   		for j, length := 0, len(l); j < length; j++ {
   			if l[j] == str {
   				break
   			}
   		}
   	}
   })
   b.Run("Gin", func(b *testing.B) {
   	for i := 0; i < b.N; i++ {
   		str := strconv.Itoa(rand.Int())
   		ginTree.getValue(str, nil, &[]skippedNode{}, false)
   	}
   })
}
本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

Gin路由算法模拟 的相关文章

  • Gin框架 ShouldBindJSON详解

    为什么第二次使用ShouldBindJSON就失效了呢 今天debug看了下 xff0c 主要是 http Request的io buffer第一次取完之后 xff0c http body 结构体中的sawEOF 61 true 第二次去读
  • Gin路由算法模拟

    概述 Gin的路由算法是采用压缩字典树实现的 xff0c 基数树 xff08 Radix Tree xff09 又称为PAT位树 xff08 Patricia Trie or crit bit tree xff09 xff0c 是一种更节省
  • 9-gin使用websocket

    toc gin使用websocket Gin 框架默认不支持 websocket xff0c 可以使用 github com gorilla websocket 实现 Talk is cheap Show me the code xff0c
  • go集成gin处理error

    1 gin的使用 gin在go开发web的占比是挺大的 很好用的web框架 xff0c 简单快速高效 但是呢 xff0c 在使用gin的过程中 xff0c 如何去统一去处理error和数据返回给客户端呢 xff1f 原始的做法如下 xff1
  • Golang

    欢迎关注 全栈工程师修炼指南 公众号 点击 下方卡片 即可关注我哟 设为 星标 每天带你 基础入门 到 进阶实践 再到 放弃学习 专注 企业运维实践 网络安全 系统运维 应用开发 物联网实战 全栈文章 等知识分享 花开堪折直须折 莫待无花空
  • go语言使用gin框架

    gin框架基础用法 package main import github com gin gonic gin net http func main router gin Default router LoadHTMLGlob templat
  • golang六个常用的web 框架

    框架一直是敏捷开发中的利器 能让开发者很快的上手并做出应用 甚至有的时候 脱离了框架 一些开发者都不会写程序了 成长总不会一蹴而就 从写出程序获取成就感 再到精通框架 快速构造应用 当这些方面都得心应手的时候 可以尝试改造一些框架 或是自己
  • gin 十. gin-contrib之secure 支持https与安全设置

    目录 一 实现https 二 防止XSS CSRF 一 实现https gin默认是http接口 前面也了解到gin中存在一个子包 gin contrib secure 内部提供了一些安全相关的中间件 例如HTTPS重定向 内容安全策略 C
  • Gin路由中间件详解

    什么是中间件 Gin 中的中间件必须是一个 gin HandlerFunc 类型 配置路由的时候可以传递多个 func 回调函 数 最后一个 func 回调函数前面触发的方法 都可以称为中间件 中间件操作演示 方法一 直接写在func 回调
  • Go【gin和gorm框架】实现紧急事件登记的接口

    简单来说 就是接受前端微信小程序发来的数据保存到数据库 这是我写的第二个接口 相比前一个要稍微简单一些 而且因为前端页面也是我写的 参数类型自然是无缝对接 前端页面大概长这个样子 先用apifox模拟发送请求测试 apifox可以直接复制J
  • Gin微服务框架_golang web框架_完整示例Demo

    Gin简介 前些天发现了一个巨牛的人工智能学习网站 通俗易懂 风趣幽默 忍不住分享一下给大家 点击跳转到网站 Gin是一个golang的微框架 封装比较优雅 API友好 源码注释比较明确 具有快速灵活 容错方便等特点 其实对于golang而
  • Gin-swaggo为gin框架提供Swagger 文档

    官方 https github com swaggo gin swagger 开始使用 为API方法增加注释 加在controller api 层 See Declarative Comments Format 运行下面命令下载swgo g
  • 【golang】12、gin 源码解析

    文章目录 快速使用 返回响应 路由匹配 path query Multipart Urlencoded Form 解析请求 MultipartFrom MiddleWare github com gin gonic gin 是 golang
  • gin 六.重定向路由重定向与请求转发

    目录 一 重定向与请求转发基础解释 二 重定向 gin Context Redirect 内部 外部重定向 路由重定向 三 请求转发 一 重定向与请求转发基础解释 重定向和请求转发是两种常见的HTTP请求处理方式 它们都可以实现将请求从一个
  • Gin框架结合gorm使用

    Gin框架结合Gorm使用 目录 Gin框架结合Gorm使用 前言 一 介绍 二 使用步骤 1 创建项目 2 开始main go 3 router的初始化 4 controller的初始化 5 services的初始化 6 models的初
  • Gin中的Cookie和Session的用法

    Gin中的Cookie和Session的用法 文章目录 Gin中的Cookie和Session的用法 介绍 Cookie 代码演示 Session 代码展示 介绍 cookie 和 session 是 Web 开发中常用的两种技术 主要用于
  • gin 三.请求数据的映射

    数据解析绑定 基础解释 ShouldBindWith 请求数据映射示例 ShouldBindHeader 将请求头绑定到一个结构体或接口示例 MustBindWith 方式 基础解释 解释 例如后端获取调用方参数 通常会使用一个结构体 或一
  • Golang gin 框架在中间件中获取请求和响应的各种数据

    为 gin 框架做不同用途的中间件时一般都需要获取到请求体和响应体的一些数据 例如做签名插件需要获取到请求参数 请求内容和 header 做鉴权插件需要获取到 header 的某些值 做日志插件需要的数据就更多了 下面就使用代码演示各种数据
  • 六种黑客入侵手机的常见方式

    六种黑客入侵手机的常见方式 在移动网络科技高速发展的今天 我们每个人的手机都有可能成为黑客攻击的对象 下面为大家介绍6种黑客入侵手机的常见方式 希望能够帮助大家避免手机被不对象攻击 1 网络钓鱼攻击 网络钓鱼攻击非常普遍 那是因为它们非常有
  • go 进阶 gin实战相关: 五. gin_scaffold 企业脚手架

    目录 一 gin scaffold 企业级脚手架 二 gin scaffold 脚手架安装及使用演示 文件分层解释 开始使用 1 配置开启go mod 功能 2 下载 安装 gin scaffold 3 整合 golang common 4

随机推荐

  • 使用sea-orm执行migrate

    源码github地址 seaormdemo 一 下载工具链 sea orm cli 是sea orm 提供的工具链 xff0c 可通过cargo下载 cargo span class token function install span
  • PVE安装更新源错误

    pve系统ping 网络不通且不能进行apt install 描述 root 64 xuyuquan span class token comment apt get update span Err 1 http ftp debian or
  • failed to run command ‘java’: No such file or directory

    failed to run command java No such file or directory 程序里远程执行shell命令 xff08 nohup java jar xff09 的执行 xff0c 后台日志报错如下 xff1a
  • vue3中的setup函数

    原文 xff1a vue3中的setup函数 落雪小轩韩的博客 CSDN博客 vue3setup 一 概念 xff1a setup是vue3中的一个新的配置项 xff0c 值为一个函数 xff0c 我们在组件中用到的数据 方法等等 xff0
  • vue同步请求

    原文地址 xff1a vue 同步请求 Aa duidui的博客 CSDN博客 vue同步请求 同步请求执行的顺序 async await 挂上的才是同步 没挂上的还是异步 async 方法名 await 请求方法 参数 then res
  • Anaconda上设置虚拟环境,并在jupyter notebook中切换。

    个人记录 xff0c 但欢迎阅读和赐教 我之前在Anaconda Navigator中建立虚拟环境 xff0c 然后在jupyter notebook的terminal中增加对应环境的ipykernel xff0c 这样可行 xff0c 但
  • 字符,字节和编码

    级别 xff1a 初级 摘要 xff1a 本文介绍了字符与编码的发展过程 xff0c 相关概念的正确理解 举例说明了一些实际应用中 xff0c 编码的实现方法 然后 xff0c 本文讲述了通常对字符与编码的几种误解 xff0c 由于这些误解
  • http协议原理

    HTTP工作原理 HTTP协议定义Web客户端如何从Web服务器请求Web页面 xff0c 以及服务器如何把Web页面传送给客户端 HTTP协议采用了请求 响应模型 客户端向服务器发送一个请求报文 xff0c 请求报文包含请求的方法 URL
  • TLS协议/SSL协议

    历史背景 SSL Secure Socket Layer 安全套接层 是基于HTTPS下的一个协议加密层 xff0c 最初是由网景公司 xff08 Netscape xff09 研发 xff0c 后被IETF xff08 The Inter
  • TCP协议

    TCP 基础 https www jianshu com p ef892323e68f TCP 使用固定的连接 TCP 用于应用程序之间的通信 当应用程序希望通过 TCP 与另一个应用程序通信时 xff0c 它会发送一个通信请求 这个请求必
  • UDP协议

    UDP 概述 xff08 User Datagram Protocol xff0c 用户数据报协议 xff09 用户数据报协议 UDP 只在 IP 的数据报服务之上增加了很少一点的功能 xff0c 这就是复用和分用的功能以及查错检测的功能
  • TCP和UDP的区别

    TCP协议与UDP协议的区别 首先咱们弄清楚 xff0c TCP协议和UDP协议与TCP IP协议的联系 xff0c 很多人犯糊涂了 xff0c 一直都是说TCP协议与UDP协议的区别 xff0c 我觉得这是没有从本质上弄清楚网络通信 xf
  • 网络协议概述

    互联网协议介绍 互联网的核心是一系列协议 xff0c 总称为 互联网协议 xff08 Internet Protocol Suite xff09 xff0c 正是这一些协议规定了电脑如何连接和组网 我们理解了这些协议 xff0c 就理解了互
  • go 编写tcp和udp服务端和客户端

    TCP协议 TCP IP Transmission Control Protocol Internet Protocol 即传输控制协议 网间协议 xff0c 是一种面向连接 xff08 连接导向 xff09 的 可靠的 基于字节流的传输层
  • tcp黏包问题

    服务端代码如下 xff1a span class token keyword package span main span class token keyword import span span class token punctuati
  • go sync.Pool 深入

    new函数的调用时机和pool的内存释放规则 以下代码调用了四次Get函数 xff0c 但是并不是每次都会new 第一次 xff0c 是a 61 pool Get byte xff0c 首次Get xff0c 在pool的private私有
  • 【AI理论学习】深入理解扩散模型:Diffusion Models(DDPM)(理论篇)

    深入理解扩散模型 xff1a Diffusion Models 引言扩散模型的原理扩散过程反向过程优化目标 模型设计代码实现Stable Diffusion DALL E Imagen背后共同的套路Stable DiffusionDALL
  • gin 框架原理

    Gin的路由原理 Gin的路由基于Trie树和压缩字典树算法 xff0c 什么是Trie树 xff1f 其实很好理解 xff0c 看下图 xff1a 单词at xff0c bee xff0c ben xff0c bt xff0c q组成的T
  • PowerDesigner导入sql脚本

    1 依次点击File gt Reverse Engineer gt Database 2 弹出弹窗对模型进行命名 xff0c 同时在DBMS下拉选择框中需要选择自己对应的数据库类型 xff0c 点击确定 新的弹窗 xff0c 选中Using
  • Gin路由算法模拟

    概述 Gin的路由算法是采用压缩字典树实现的 xff0c 基数树 xff08 Radix Tree xff09 又称为PAT位树 xff08 Patricia Trie or crit bit tree xff09 xff0c 是一种更节省