深入Go的底层,带你走近一群有追求的人

【从零到一】Jetson Nano上YOLOv5部署完全攻略:零基础小白也能玩转边缘AI推理 文章摘要: 本文详细介绍了在Jetson Nano边缘设备上部署YOLOv5目标检测模型的完整流程。从硬件准备、系统镜像烧录、基础环境配置,到CUDA/cuDNN验证、Python虚拟环境搭建及PyTorch安装,逐步指导开发者完成部署准备。教程兼顾理论原理与实践操作,提供Python和C++两种实现方案,并涵盖TensorRT优化、实时检测等生产级应用场景,帮助零基础开发者快速掌握边缘AI推理技术,实现高性能目标检测应用。 阅读详情

上周六晚上,我参加了“Go夜读”活动,这期主要讲Go汇编语言,由滴滴曹春晖大神主讲。活动结束后,我感觉打通了任督二脉。活动从晚上9点到深夜11点多,全程深度参与,大呼过瘾,以至于活动结束之后,久久不能平静。

可以说理解了Go汇编语言,就可以让我们对Go的理解上一个台阶,很多以前模棱的东西,在汇编语言面前都无所遁形了。我在活动上收获了很多,今天我来作一个总结,希望给大家带来启发!

为了更好的阅读体验,手动贴上文章目录:

640?wx_fmt=png


缘起

几周前我写了一篇关于defer的文章:《Golang之如何轻松化解defer的温柔陷阱》。这篇文章发出后不久就被GoCN的每日新闻收录了,然后就被Go夜读群的大佬杨文看到了,之后被邀请去夜读活动分享。

正式分享前,我又主题阅读了很多文章,以求把defer讲清楚。阅读过程中,我发现但凡深入一点的文章,都会抛出Go汇编语言。于是就去搜索资料,无奈相关的资料太少,看得云里雾里,最后到了真正要分享的时候也没有完全弄清楚。

夜读活动结束之后,杨大发布了由春晖大神带来的夜读分享预告:《plan9 汇编入门,带你打通应用和底层》。我得知这个消息后,非常激动!终于有牛人可以讲讲Go汇编语言了,听完之后估计会有很大提升,也能搞懂defer的底层原理了!

接着,我发现,春晖大神竟然和我在同一个公司!我在公司内网上搜到了他写的plan9汇编相关文章,发布到Go夜读的github上。我提前花时间预习完了文章,整理出了遇到的问题。

周六晚上9点准时开讲,曹大的准备很充分!原来1个小时的时间被拉长到了2个多小时,而曹大精力和反应一直很迅速,问的问题很快就能得到回答。我全程和曹大直接对话,感觉简直不要太爽!

这篇文章既是对这次夜读的总结,也是为了宣传一下Go夜读活动。那里是一群有追求的人,他们每周都会聚在一起,通过网络,探讨Go语言的方方面面。我相信,参与的人都会有很多不同的收获。

我直接参与的Go夜读活动有三期,一期分享,两期听讲,每次都有很多的收获。

自我介绍的技巧

很多人都不知道怎么做好一个自我介绍,要么含糊其辞,介绍完大家都不知道你讲了什么;要么说了半天无效的信息,大家并不关心的事情,搞得很尴尬。 其实自我介绍没那么难,掌握套路后,是可以做得很好的!

我在上上期Go夜读分享的时候,用一张PPT完成了自我介绍。包含了四个方面:个人基本信息出现在此时此地的原因我能带来的帮助我希望得到的帮助

个人基本信息包括你叫什么名字,是哪里人,在什么地方工作,毕业于哪个学校,有什么兴趣爱好……这些基本的属性。这些信息可以让大家快速形成对你的直观认识。

出现在此时此地的原因,可以讲解你的故事。你在什么地方通过什么人知道了这个活动,然后因为什么打动你来参加……通过故事可以迅速拉近与现场其他参与者的距离。

我能带来的帮助,参加活动的人都是想获取一些东西的:知识、经验、见闻等等。但是,我们不能只索取,不付出。因此,可以讲讲你可以提供的帮助。比如我可以联系场地,我会写宣传文章等等,你可以讲出你独特的价值。

我希望得到的帮助。每个参与的人都希望从活动中获得自己想要的东西,正是因为此,这个活动对于参与者才有意义,也才会持续下去的动力。

这四个方面,可以组成一个非常精彩的自我介绍。它最早是我在听罗胖的《罗辑思维》听到的,我把它写进了我的人生算法里,今天推荐给大家。希望大家以后在需要自我介绍的场合有话可说,而且能说的精彩。

640?wx_fmt=png


硬核知识点

什么是plan9汇编

我们知道,CPU是只认二进制指令的,也就是一串的0101;人类无法记住这些二进制码,于是发明了汇编语言。汇编语言实际上是二进制指令的文本形式,它与指令可以一一对应。

每一种CPU指令都是不一样的,因此对应的汇编语言也就不一样。人类写完汇编语言后,把它转换成二进制码,就可以被机器执行了。转换的动作由编译器完成。

Go语言的编译器和汇编器都带了一个-S参数,可以查看生成的最终目标代码。通过对比目标代码和原始的Go语言或Go汇编语言代码的差异可以加深对底层实现的理解。

Go汇编语言实际上来源于plan9汇编语言,而plan9汇编语言最初来源于Go语言作者之一的Ken Thompson为plan9系统所写的C语言编译器输出的汇编伪代码。这里强烈推荐一下春晖大神的新书《Go语言高级编程》,即将上市,电子版的点击阅读原文可以看到地址,书中有一整个章节讲Go的汇编语言,非常精彩!

理解Go的汇编语言,哪怕只是一点点,都能对Go的运行机制有更深入的理解。比如我们以前讲的defer,如果从Go源码编译后的汇编代码来看,就能深刻地掌握它的底层原理。再比如,很多文章都会分析Go的函数参数传递都是值传递,如果把汇编代码秀出来,很容易就能得出结论。

汇编角度看函数调用及返回过程

假设我们有一个这样年幼无知的例子,求两个int的和,Go源码如下:

package main	

	
func main() {	
  _ = add(3,5)	
}	

	
func add(a, b int) int {	
  return a+b	
}

使用如下命令得到汇编代码:

go tool compile -S main.go

go tool compile命令用于调用Go语言提供的底层命令工具,其中-S参数表示输出汇编格式。

我们现在只关心add函数的汇编代码:

"".add STEXT nosplit size=19 args=0x18 locals=0x0	
        0x0000 00000 (main.go:7)        TEXT    "".add(SB), NOSPLIT, $0-24	
        0x0000 00000 (main.go:7)        FUNCDATA        $0, gclocals·54241e171da8af6ae173d69da0236748(SB)	
        0x0000 00000 (main.go:7)        FUNCDATA        $1, gclocals·33cdeccccebe80329f1fdbee7f5874cb(SB)	
        0x0000 00000 (main.go:7)        MOVQ    "".b+16(SP), AX	
        0x0005 00005 (main.go:7)        MOVQ    "".a+8(SP), CX	
        0x000a 00010 (main.go:8)        ADDQ    CX, AX	
        0x000d 00013 (main.go:8)        MOVQ    AX, "".~r2+24(SP)	
        0x0012 00018 (main.go:8)        RET

看不懂没关系,我目前也不是全部都懂,但是对于理解一个函数调用的整体过程而言,足够了。

0x0000 00000 (main.go:7)        TEXT    "".add(SB), NOSPLIT, $0-24

这一行表示定义add这个函数,最后的数字$0-24,其中0表示函数栈帧大小为0;24表示参数及返回值的大小:参数是2个int型变量,返回值是1个int型变量,共24字节。

再看中间这四行:

0x0000 00000 (main.go:7)        MOVQ    "".b+16(SP), AX	
0x0005 00005 (main.go:7)        MOVQ    "".a+8(SP), CX	
0x000a 00010 (main.go:8)        ADDQ    CX, AX	
0x000d 00013 (main.go:8)        MOVQ    AX, "".~r2+24(SP)

代码片段中的第1行,将第2个参数b搬到AX寄存器;第2行将1个参数a搬到寄存器CX;第3行将ab相加,相加的结果搬到AX;最后一行,将结果搬到返回参数的地址,这段汇编代码非常简单,来看一下函数调用者和被调者的栈帧图:

(SP)指栈顶,b+16(SP)表示裸骑1的位置,从SP往上增加16个字节,注意,前面的b仅表示一个标号;同样,a+8(SP)表示实参0;~r2+24(SP)则表示返回值的位置。

具体可以看下面的图:

640?wx_fmt=png

上面add函数的栈帧大小为0,其实更一般的调用者与被调用者的栈帧示意图如下:

640?wx_fmt=png

最后,执行RET指令。这一步把被调用函数add栈帧清零,接着,弹出栈顶的返回地址,把它赋给指令寄存器rip,而返回地址就是main函数里调用add函数的下一行。

于是,又回到了main函数的执行环境,add函数的栈帧也被销毁了。但是注意,这块内存是没有被清零的,清零动作是之后再次申请这块内存的时候要做的事。比如,声明了一个int型变量,它的默认值是0,清零的动作是在这里完成的。

这样,main函数完成了函数调用,也拿到了返回值,完美。

汇编角度看slice

再来看一个例子,我们来看看slice的底层到底是什么。

package main	

	
func main() {	
  s := make([]int, 3, 10)	
  _ = f(s)	
}	

	
func f(s []int) int {	
  return s[1]	
}

用上面同样的命令得到汇编代码,我们只关注f函数的汇编代码:

"".f STEXT nosplit size=53 args=0x20 locals=0x8	
        // 栈帧大小为8字节,参数和返回值为32字节	
        0x0000 00000 (main.go:8)        TEXT    "".f(SB), NOSPLIT, $8-32	
        // SP栈顶指针下移8字节	
        0x0000 00000 (main.go:8)        SUBQ    $8, SP	
        // 将BP寄存器的值入栈	
        0x0004 00004 (main.go:8)        MOVQ    BP, (SP)	
        // 将新的栈顶地址保存到BP寄存器	
        0x0008 00008 (main.go:8)        LEAQ    (SP), BP	
        0x000c 00012 (main.go:8)        FUNCDATA        $0, gclocals·4032f753396f2012ad1784f398b170f4(SB)	
        0x000c 00012 (main.go:8)        FUNCDATA        $1, gclocals·69c1753bd5f81501d95132d08af04464(SB)	
        // 取出slice的长度len	
        0x000c 00012 (main.go:8)        MOVQ    "".s+24(SP), AX	
        // 比较索引1是否超过len	
        0x0011 00017 (main.go:9)        CMPQ    AX, $1	
        // 如果超过len,越界了。跳转到46	
        0x0015 00021 (main.go:9)        JLS     46	
        // 将slice的数据首地址加载到AX寄存器	
        0x0017 00023 (main.go:9)        MOVQ    "".s+16(SP), AX	
        // 将第8byte地址的元素保存到AX寄存器,也就是salaries[1]	
        0x001c 00028 (main.go:9)        MOVQ    8(AX), AX	
        // 将结果拷贝到返回参数的位置(y)	
        0x0020 00032 (main.go:9)        MOVQ    AX, "".~r1+40(SP)	
        // 恢复BP的值	
        0x0025 00037 (main.go:9)        MOVQ    (SP), BP	
        // SP向上移动8个字节	
        0x0029 00041 (main.go:9)        ADDQ    $8, SP	
        // 返回	
        0x002d 00045 (main.go:9)        RET	
        0x002e 00046 (main.go:9)        PCDATA  $0, $1	
        // 越界,panic	
        0x002e 00046 (main.go:9)        CALL    runtime.panicindex(SB)	
        0x0033 00051 (main.go:9)        UNDEF	
        0x0000 48 83 ec 08 48 89 2c 24 48 8d 2c 24 48 8b 44 24  H...H.,$H.,$H.D$	
        0x0010 18 48 83 f8 01 76 17 48 8b 44 24 10 48 8b 40 08  .H...v.H.D$.H.@.	
        0x0020 48 89 44 24 28 48 8b 2c 24 48 83 c4 08 c3 e8 00  H.D$(H.,$H......	
        0x0030 00 00 00 0f 0b                                   .....	
        rel 47+4 t=8 runtime.panicindex+0

通过上面的汇编代码,我们画出函数调用的栈帧图:

640?wx_fmt=png

我们可以清晰地看到,一个slice本质上是用一个数据首地址,一个长度Len,一个容量Cap。所以在参数是slice的函数里,对slice的操作会影响到实参的slice。

正确参与Go夜读活动的方式

最后再说一下Go夜读活动的方式和目标。引自Go夜读的github说明文件:

由一个主讲人带着大家一起去阅读 Go 源代码,一起去啃那些难啃的算法、学习代码里面的奇淫技巧,遇到问题或者有疑惑了,我们可以一起去检索,解答这些问题。我们可以一起学习,共同成长。

我们希望可以推进大家深入了解 Go ,快速成长为资深的 Gopher 。我们希望每次来了的人和没来的人都能够有收获,成长。

前面我说Go夜读活动的小伙伴是一群有追求的人,这里我也指出一些问题吧。就我参与的三期来看,虽然zoom接入人数很多,高峰期50+人,但是全过程大家交流比较少,基本上是主讲人一个人在那自嗨。春晖大神讲的那期,只有我全程提问。感觉像是我们两个人在对话,我的问题弄清楚了,只是不知道其他的参与同学如何?

我再给分享者和参与者提一些建议吧:

对于分享者,事先做好充足的准备,可以在文章里列出主要的点,放在github里,参考春晖大神的plan9汇编讲义;最重要的一点,分享前给大家提供一份预习资料。

对于参与者,能获得最多收获的方式就是会前预习,会中积极提问,会后复习总结发散。另外,强烈建议参与者会前要准备至少一个问题,有针对性地听,才会有收获。会中也要积极提问,这也是对主讲者的反馈,不至于主讲者觉得只有自己在对着电脑讲。

最后,欢迎每一个学习Go语言的同学都能来Go夜读看看!点击阅读原文可以看到文章里提到的所有资料,包括上期曹大plan9汇编的视频回放,不容错过!

YOLOv11改进 | 添加注意力篇 | 结合2024最新Mamba注意力机制MLLA助力yolov11有效涨点含二次创新C2PSA(全网独家首发改进) 本文给大家来的改进机制是结合号称超越Transformer架构的Mamba架构的最新注意力机制MLLA,本文将其和我们YOLOv11进行结合,的原理是通过将Mamba模型的一些核心设计融入线性注意力机制,从而提升模型的性能。具体来说,MLLA主要整合了Mamba中的“忘记门”(forget gate)和模块设计(block design)这两个关键因素,同时MLLA通过使用位置编码(RoPE)来替代忘记门,从而在保持并行计算和快速推理速度的同时,提供必要的位置信息。 阅读详情

相关推荐

Go-Go夜读-每周四晚上Go源码阅读以及线下技术讨论。

Go 夜读 - 每周四晚上 Go 源码阅读以及线下技术讨论。

终极指南:如何用Boss Show Time插件让招聘信息时间一目了然

在竞争激烈的求职市场中,时间就是机会。许多招聘平台为了保持职位列表的"新鲜感",故意隐藏或模糊职位的实际发布时间,导致求职者花费大量时间浏览早已过期的岗位。Boss Show Time招聘时间展示插件正是为了解决这一痛点而生,它通过智能解析技术,让你在各大招聘平台上一眼就能看到职位的真实发布时间,大幅提升求职效率。 ## 为什么你需要这个招聘时间展示插件? ### 招聘市场的痛点 1. **

gitblog_00051的博客 493

深度学习数据集之图像分类数据集:小型ImageNet图像分类数据集(200分类)

数据集包含小型ImageNet图像分类数据集(Tiny ImageNet)(200分类),数据按照文件夹储存,不需要处理可直接用作深度学习训练数据。本数据集分为以下200类:altar、bell pepper、confectionery、maypole等等共200类别(每个类别均有500张图片)数据集总大小:185MB下载解压后的图像目录:训练集(80000张图片)、和测试集(20000张图片)data-train 训练集-每个子文件夹放同类别的图像,文件夹名为分类类别data-test 测试集-每个子文件夹放同类别的图像,文件夹名为分类类别除此之外,提供了classes的json字典类别文件,以及可视化的脚本py文件

夜读源码,你探究 Go 语言的iota

Go 语言的 iota 怎么说呢,感觉像枚举,又有点不像枚举,它的底层是什么样的,用哪个姿势使用才算正规,今天转载一篇「Go夜读」社区上分享的文章,咱们一起学习下。Go 夜读,你每页读源...

kevin_tech的博客,微信搜「网管叨bi叨」 1067

Go 夜读

Go 夜读 Go 源码阅读

wccms的博客 393

机不可失,时不再来

目前,我们「Go 夜读」知识星球的成员数已经 446 ,马上就满 500 ,到达 500 后将涨价至 219 元,机会难得,优惠券送完即止。如何免费加入「Go夜读」知识星球?报名参...

梦醒人间 274

ZigbeeTLc与ZHA集成教程:智能家居系统完美对接步骤

ZigbeeTLc是一款针对TLSR825x芯片的Zigbee 3.0物联网设备定制固件,通过与ZHA(Zigbee Home Automation)集成,能帮助用户打造稳定高效的智能家居系统。本教程将详细介绍如何实现ZigbeeTLc与ZHA的无缝对接,让你的智能设备发挥最大效能。 ## 一、准备工作:了解支持的设备型号 在开始集成前,首先确认你的设备是否在ZigbeeTLc支持列表中。Zi

gitblog_00199的博客 1003

从0到10000星:Go夜读7年成长之路,开发者不可错过的完整指南

Go夜读是一个专注于Go语言学习的开源社区,经过7年发展已成长为拥有超过10000星标的知名项目。这个社区通过43个阶段的持续努力,为全球Gopher提供了宝贵的学习资源和交流平台。如果你正在学习Go语言或者想要提升Go技能,这篇文章将为你详细解析Go夜读的成长历程和成功经验。 ## 📈 项目成长历程:从零到万的蜕变 Go夜读项目始于2018年3月21日,最初只是一个简单的线下技术交流活动。

gitblog_01176的博客 1058

Goldberg Steam模拟器终极指南:从零开始构建离线游戏环境

Goldberg Steam模拟器是一个强大的开源项目,专门用于绕过Steam DRM保护,实现Steam游戏的局域网联机和完全离线运行。这款Steam游戏模拟器为技术爱好者和高级用户提供了完整的解决方案,让你在没有Steam客户端的情况下也能享受Steam游戏的乐趣。 ## 为什么选择Goldberg Steam模拟器? ### 核心优势与适用场景 Goldberg Steam模拟器解决了

gitblog_00930的博客 731

告别"乱码噩梦":Go夜读项目的i18n最佳实践与避坑指南

在全球化协作的开源项目中,你是否遇到过中文注释在英文环境下显示乱码?国际贡献者因界面语言障碍放弃参与?Go夜读(night-reading-go)项目通过系统化的国际化(i18n)与本地化(l10n)改造,成功将贡献者地域分布从3个扩展到12个国家。本文将拆解这一过程中的实战方案,帮你避开90%的国际化陷阱。 ## 为什么Go项目需要国际化? Go夜读项目最初仅支持中文文档,导致: - 非中文贡...

gitblog_00595的博客 1031

Go语言高级编程:深度挖掘

你可以创建自定义错误类型,以提供更多的上下文信息和可读性。这对于构建库或大型应用程序特别有用。Code intGo语言允许你创建自定义的数据结构和类型。你可以为基本类型创建自定义类型,这可以提高代码的可读性和可维护性。本篇博客提供了一些深入Go语言高级编程领域的技巧和技术。

我滴小博客 1110

曾经的 PHP 程序员,现在的 Gopher,我经历了哪些?

想和本文作者——前蚂蚁金服技术专家/Go语言 Contributor曹大直接交流吗?请查看文末福利哦全文共 1771 字,阅读大约需要 6 分钟作者 | 曹春晖(Xargin)编辑 | ...

Go中国 363
上一篇: 布隆过滤器实战【防止缓存击穿】
下一篇: 一天彻底搞懂 Git 《Git 原理详解及实用指南》
codehole_
博客等级 码龄7年 33粉丝 132原创
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值