Go 还是 TinyGo?
什么是 TinyGo?
TinyGo 不是另一种编程语言,而是 Go 语言的一个编译器。它与 Go 编译器的关键区别在于,TinyGo 生成的二进制文件更小,主要用于嵌入式系统和 WebAssembly(WASM)环境。相比之下,Go 编译器通常用于编译完整的服务器端应用程序和通用程序。
TinyGo 使用 LLVM 作为后端,与 Go 编译器相比,可以创建更小的二进制文件。例如,一个简单的打印“Hello World”的代码用 Go 编译可能会产生约 800 KB 的二进制文件,而 TinyGo 在去除不必要的符号后可以将其减少到约 10 KB。
TinyGo 专为资源有限的环境设计,允许开发人员在微控制器和小型设备(如 Arduino 和 Raspberry Pi)上运行 Go 代码,这些设备通常不受标准 Go 编译器的支持。
TinyGo 的特性和限制
1. Cgo
虽然 TinyGo 集成了 Clang 编译器来解析 import "C"
块,但 Cgo 的一些功能仍不受支持或可能工作方式略有不同。例如,#cgo
语句只得到部分支持。
2. 反射
许多包,尤其是标准库中的包,依赖反射来工作。reflect
包在 TinyGo 中已经重新实现,大部分功能都可以使用,但部分功能尚未完全支持。
3. 映射
映射通常工作正常,但可能比预期的要慢。这可能是因为某些类型(如结构体)在内部使用反射进行比较,而不是使用专用的哈希/比较函数。
4. 标准库
由于上述功能缺失,以及标准库部分依赖于特定的编译器/运行时,许多包尚未编译。请参阅这里的可编译包列表(注意,“可编译”并不意味着完全可用)。
5. 垃圾回收
垃圾回收通常工作正常,但在非常小的芯片(如 AVR)和 WebAssembly 上可能效果不佳。它也比 Go 的垃圾回收器慢得多。通过精心设计,可以避免在主循环中进行内存分配,以避免性能问题。可以使用 -print-allocs
选项编译,以了解内存分配的位置和原因。更多信息请参阅堆分配。
6. 全局变量
TinyGo 在编译期间计算全局变量,这优化了启动时间并减少了内存使用。而 Go 编译器则以不同方式处理全局变量。这种优化的好处包括:
- 启动时间减少。
- 通过将初始数据从闪存复制到 RAM 来初始化全局变量,所需的闪存空间大幅减少。
- 数据通常可以静态分配,而不是在启动时动态分配。
- 无用的全局变量可以被 LLVM 轻松优化掉。
- 常量全局变量可以被 LLVM 识别并存储在闪存中。
- 全局常量有助于常量传播和死代码消除。
附加内容
我对比了使用 Go 和 TinyGo 编译的 WASM 与 JavaScript 的性能,结果令人惊讶。在发现 TinyGo 之前,我对 Go WASM 并不太满意。我将在下一篇博客中分享完整的教程和我的发现。
结论
总而言之,TinyGo 通过使 Go 能够在受限环境中运行,扩展了 Go 的能力,同时在功能和性能优化方面作出了妥协。对于物联网设备和 WebAssembly 应用程序的开发人员而言,TinyGo 特别有用,因为在这些场景中,二进制文件大小和资源效率至关重要。