Go的动态数组简直是灾难!
先温习一些计算机基础知识。
一个程序,按照生命周期,可大致分为(以编译型语言举例,不讨论Python等解释型语言)编译期(Compile Time)与运行期(Runtime)。 编译期编译器负责将使用高级抽象编程语言写的代码编译为计算机可执行的二进制文件。运行时则既可以指程序在运行中的这一状态,也可以指在程序运行时的发挥类似于调度器(Scheduler)功能的组件。 如Rust本不需要运行时,但当你需要使用异步Rust时,因为一个异步任务可简单视为一个任务,自然需要一个调度器,来维护任务队列、任务状态等。
如果将一个运行中的程序视为一个进程,进程会拥有操作系统分配的虚拟内存空间(CS八股文之一:进程是操作系统分配资源的最小单位),在程序运行中,会不可避免的需要申请内存空间与释放内存空间。
以网络微服务举例,收到一个http请求,会申请内存将请求体作为字符串存储到内存(Memory)中,一般而言,在对此Json编码的字符串做了反序列化后,内存中同时存在一个字符串与一个结构体,此时,可以将字符串回收,释放字符串占有的内存。
如上所述,请求体的字符串形式就经历了一个申请内存与释放内存的周期。
对于内存回收,也有着好几种不同的处理方式,在Rust之前,大概分为以C/C++代表的程序员手动管理的方式:在不再需要此字符串对象后,由程序员在代码中写好的free()函数释放掉内存,缺点是很少有程序员能够如此细致的记住每一块内存碎片。
另一种是以Java、Go为代表的Runtime作为内存管理器去实施垃圾回收(Garbage Collection):由Runtime判断哪一块内存不再被任一变量引用,由Runtime去释放不再被引用的内存碎片,此方法的缺点是,执行垃圾回收时,会触发STW(Stop The World),导致在STW期间,程序不可达。
Rust创新性的提出所有权(Ownership)的概念:对于每一块内存,维护一个此内存的所有者/维护一个引用计数(Reference Count),在所有指向此内存的变量皆超出生命周期后,自动释放此块内存。
Discord在2020年发表过一篇博客,讲述了Discord后端从Go迁移到Rust后的性能提升,以及解决了GC时STW导致的large latency spikes。原文链接为:https://discord.com/blog/why-discord-is-switching-from-go-to-rust。
需要在编译期确定好大小的为静态数组,静态数组的大小在运行时不可变。静态数组存储在虚拟内存的栈上,栈空间具有访问快的特点,同时,在函数间传递静态数组,是传递的静态数组的深拷贝(复制一份数据传入函数),函数内的修改不会影响外界。 动态数组由于扩容的关系,需要存储在更灵活的堆上:当分配的内存空间不足以存储新的数据大小时,会重新寻找一块更大的内存存储数据,在函数间传递,是传递的浅拷贝(传递数据指针)。
在Go中,[x]Type为静态数组,[]Type为动态数组,也即是Go官方叫的切片(Slice)。如果在Go中,打印静态数组的Sizeof与动态数组的Sizeof分别会是什么结果呢?
为什么静态数组的大小与元素个数相关,动态数组的大小与元素个数不相关?
静态数组的数据就存在此变量指向的空间,是一块在编译期就已经确定好的地址,动态数组实则是一个胖指针,由指向数据实际所在堆上地址的指针(64位机器,即8字节)、长度(len,8字节)与容量(cap,8字节)组成,自然是与元素个数无关。
接下来看一段代码,请分析此代码是否能通过编译,如果能,是否会在运行时Panic,会在运行时打印什么