深度探索 Go 对象模型(一)

简介: 深度探索 Go 对象模型

了解一门语言的高级特性,仅仅从浮于表面,是无法把握住语言的精髓的。学习过 C++ 的高阶开发者,一定读过神书《Inside The C++ Object Model》,本文的目标是一样的:通过对象模型,掌握 Go 语言的底层机制,从更深层次解释语言特性。

编译与执行

众所周知,Go 源码并不能直接运行,所有代码必须一行行,通过“编译”——“汇编”——“链接” 阶段 转化为低级的机器语言指令,即可执行程序。

“汇编”和“链接”阶段各种语言并无区别,所以一般通过“编译”和“执行”阶段来支持各种语言特性。对于 Go 语言,执行过程并无法直接修改执行指令,因此所有语言特性都是“编译”相关的。理解这一点很重要,因为下面依赖“编译”的产物 汇编代码 来解读对象模型。

什么是对象模型?

何为 Go 对象模型? Go 对象模型可以概括为以下两部分:

  1. 支持面向对象程序设计的部分
  • 封装
  • 继承
  • 多态
  1. 各种特性的底层实现机制
  • 反射

下面分别从 struct 和 interface 来解释模型如何支持以上两部分。

Struct 语意学

面向对象编程,把对象作为程序的基本单元,一个对象包含了数据和操作数据的函数,前者为成员变量,后者为成员函数。所以研究对象需要分别从成员变量和成员函数入手。

成员变量

以下有三段程序:

// First: global varible
var (
   X,Y,Z float32
)
// Second: simple type
type point3d struct {
  X, Y, Z float32
}
// Third: inherit type
type point struct {
  X float32
}
type point2d struct {
  point
  Y float32
}
type point3d struct {
  point2d
  Z float32
}

从风格来看,三段程序截然不同。有许多令人信服的讨论告诉我们,为什么“数据封装”(Second & Third)要比使用“全局变量”好。但,从程序员的角度看,会有几个疑问:

  1. “数据封装” 之后,内存成本增加了多少?
  2. “数据封装” 之后,在执行过程中,变量的存储效率是否降低了?
内存布局

先看内存变化。了解内存变化最好的办法就是通过代码打印对象的内存大小,先看全局变量大小

var (
  X, Y, Z float32
)
func main() {
  fmt.Printf("X size:%v, Y size:%v, Z size:%v\n", unsafe.Sizeof(X), unsafe.Sizeof(Y), unsafe.Sizeof(Z))
  fmt.Printf("X addr:%v, Y addr:%v, Z addr:%v\n", &X, &Y, &Z)
}

执行程序输出为:

$ go run variable.go
X size:4, Y size:4, Z size:4
X addr:0x118ee88, Y addr:0x118ee8c, Z addr:0x118ee90

可以看到,X、Y、Z三个字段大小均为4字节,且三个字段内存地址顺序排列。

再看第二段代码的输出

func TestLayout(t *testing.T) {
  p := point3d{X: 1, Y: 2, Z: 3}
  fmt.Printf("point3d size:%v, align:%v\n", unsafe.Sizeof(p), unsafe.Alignof(p))
  typ := reflect.TypeOf(p)
  fmt.Printf("Struct:%v is %d bytes long\n", typ.Name(), typ.Size())
  fmt.Printf("X at offset %v, size=%d\n", unsafe.Offsetof(p.X), unsafe.Sizeof(p.X))
  fmt.Printf("Y at offset %v, size=%d\n", unsafe.Offsetof(p.Y), unsafe.Sizeof(p.Y))
  fmt.Printf("Z at offset %v, size=%d\n", unsafe.Offsetof(p.Z), unsafe.Sizeof(p.Z))
}

执行程序输出为:

$ go test -v -run TestLayout
=== RUN   TestLayout
point3d size:12, align:4
Struct:point3d is 12 bytes long
X at offset 0, size=4
Y at offset 4, size=4
Z at offset 8, size=4

可以看到,X、Y、Z三个字段大小一样为4字节,内存排列也与上一个版本一样。

继续,第三段代码

func TestLayout(t *testing.T) {
  p := point3d{point2d: point2d{point: point{X: 1}, Y: 2}, Z: 3}
  fmt.Printf("point3d size:%v, align:%v\n", unsafe.Sizeof(p), unsafe.Alignof(p))
  typ := reflect.TypeOf(p)
  fmt.Printf("Struct:%v is %d bytes long\n", typ.Name(), typ.Size())
  fmt.Printf("X at offset %v, size=%d\n", unsafe.Offsetof(p.X), unsafe.Sizeof(p.X))
  fmt.Printf("Y at offset %v, size=%d\n", unsafe.Offsetof(p.Y), unsafe.Sizeof(p.Y))
  fmt.Printf("Z at offset %v, size=%d\n", unsafe.Offsetof(p.Z), unsafe.Sizeof(p.Z))
}

执行程序输出为:

$ go test -v -run TestLayout
=== RUN   TestLayout
point3d size:12, align:4
Struct:point3d is 12 bytes long
X at offset 0, size=4
Y at offset 4, size=4
Z at offset 8, size=4

可以看到,X、Y、Z三个字段大小一样为4字节,内存排列也与之前两个版本一样。

综上所述,我们可以看到,无论是否封装,还是多深的继承层次,对成员变量的内存布局都并无影响,均按照字段定义的顺序排列(不考虑内存对齐的情况)。即内存布局类似如下:


目录
相关文章
|
3月前
|
数据采集 编解码 监控
Go语言实战案例:使用channel实现生产者消费者模型
本文是「Go语言100个实战案例 · 网络与并发篇」第4篇,通过实战案例详解使用 Channel 实现生产者-消费者模型,涵盖并发控制、任务调度及Go语言并发哲学,助你掌握优雅的并发编程技巧。
|
11月前
|
Go 开发工具
百炼-千问模型通过openai接口构建assistant 等 go语言
由于阿里百炼平台通义千问大模型没有完善的go语言兼容openapi示例,并且官方答复assistant是不兼容openapi sdk的。 实际使用中发现是能够支持的,所以自己写了一个demo test示例,给大家做一个参考。
|
9月前
|
机器学习/深度学习 人工智能 测试技术
扩散模型版CS: GO!世界模型+强化学习:2小时训练登顶Atari 100K
《Diffusion for World Modeling: Visual Details Matter in Atari》提出了一种名为DIAMOND的方法,将扩散模型应用于世界模型构建。该方法在Atari 100K基准测试中仅用2小时训练时间就达到了前所未有的性能水平,平均人类归一化分数达1.46,超过人类水平。DIAMOND通过条件生成、网络预条件和高效采样等设计,提升了视觉细节捕捉、模型稳定性和计算效率。未来研究方向包括连续控制领域应用和更长记忆机制的整合。
237 10
|
存储 安全 Java
go语言重用对象
【10月更文挑战第19天】
131 1
|
Go 调度 开发者
Go语言的并发编程模型
【10月更文挑战第26天】Go语言的并发编程模型
144 1
|
安全 测试技术 Go
Go语言中的并发编程模型解析####
在当今的软件开发领域,高效的并发处理能力是提升系统性能的关键。本文深入探讨了Go语言独特的并发编程模型——goroutines和channels,通过实例解析其工作原理、优势及最佳实践,旨在为开发者提供实用的Go语言并发编程指南。 ####
|
负载均衡 安全 物联网
探索Go语言的并发编程模型及其在现代应用中的优势
【10月更文挑战第10天】探索Go语言的并发编程模型及其在现代应用中的优势
|
缓存 编译器 Go
开发与运维线程问题之Go语言的goroutine基于线程模型实现如何解决
开发与运维线程问题之Go语言的goroutine基于线程模型实现如何解决
142 3
|
Go 开发者
探索Go语言的并发编程模型
通过实例详细介绍了Go语言中的并发编程模型,包括goroutine、channel的基本使用和最佳实践。深入剖析如何利用Go的并发特性提高程序性能和效率,适用于初学者和有一定经验的开发者。
|
安全 Go 开发者
Golang深入浅出之-Go语言中的CSP模型:深入理解并发哲学
【5月更文挑战第2天】Go语言的并发编程基于CSP模型,强调通过通信共享内存。核心概念是goroutines(轻量级线程)和channels(用于goroutines间安全数据传输)。常见问题包括数据竞争、死锁和goroutine管理。避免策略包括使用同步原语、复用channel和控制并发。示例展示了如何使用channel和`sync.WaitGroup`避免死锁。理解并发原则和正确应用CSP模型是编写高效安全并发程序的关键。
340 7
下一篇
oss云网关配置