深入研究高级类型优化技术,从值类型到JIT编译,显著提升全球应用软件性能和效率。最大化速度,降低资源消耗。
高级类型优化:释放跨全球架构的峰值性能
在广阔且不断发展的软件开发领域,性能仍然是最受关注的问题。从高频交易系统到可扩展的云服务,再到资源受限的边缘设备,对应用程序的需求不仅要具有功能性,而且要具有卓越的速度和效率,这种需求在全球范围内持续增长。虽然算法改进和架构决策常常抢尽风头,但在我们代码的根基中,更深层次、更细粒度的优化在于高级类型优化。 这篇博文深入研究了复杂的技术,这些技术利用对类型系统的精确理解来释放显著的性能提升,减少资源消耗,并构建更强大、更具全球竞争力的软件。
对于全球范围内的开发人员来说,理解和应用这些高级策略可能意味着应用程序仅仅能够运行,还是能够脱颖而出,从而在不同的硬件和软件生态系统中提供卓越的用户体验和运营成本节约之间的区别。
理解类型系统的基础:全球视角
在深入研究高级技术之前,巩固我们对类型系统及其固有性能特征的理解至关重要。 在各个地区和行业中流行的不同语言提供了不同的类型方法,每种方法都有其权衡取舍。
重新审视静态与动态类型:性能影响
静态和动态类型之间的二分法对性能产生了深远的影响。静态类型语言(例如,C++、Java、C#、Rust、Go)在编译时执行类型检查。这种早期的验证允许编译器生成高度优化的机器代码,通常对数据形状和操作做出假设,而这些假设在动态类型环境中是不可能实现的。消除了运行时类型检查的开销,并且内存布局可以更具可预测性,从而提高缓存利用率。
相反,动态类型语言(例如,Python、JavaScript、Ruby)将类型检查推迟到运行时。 虽然提供了更大的灵活性和更快的初始开发周期,但这通常会带来性能成本。运行时类型推断、装箱/拆箱和多态调度引入了开销,这些开销会严重影响执行速度,尤其是在性能关键部分。现代 JIT 编译器减轻了部分成本,但基本差异仍然存在。
抽象和多态的成本
抽象是可维护和可扩展软件的基石。面向对象编程 (OOP) 严重依赖多态性,允许不同类型的对象通过公共接口或基类被统一处理。然而,这种功能通常伴随着性能损失。虚拟函数调用(vtable 查找)、接口调度和动态方法解析引入了间接内存访问,并阻止了编译器积极内联。
在全球范围内,使用 C++、Java 或 C# 的开发人员经常需要应对这种权衡。虽然对设计模式和可扩展性至关重要,但在热代码路径中过度使用运行时多态性会导致性能瓶颈。高级类型优化通常涉及降低或优化这些成本的策略。
核心高级类型优化技术
现在,让我们探讨利用类型系统进行性能增强的特定技术。
利用值类型和结构体
最具影响力的类型优化之一涉及明智地使用值类型(结构体)而不是引用类型(类)。当一个对象是引用类型时,它的数据通常分配在堆上,并且变量持有对该内存的引用(指针)。但是,值类型直接存储它们的数据,它们在声明的地方,通常在堆栈上或在其他对象内部内联。
- 减少堆分配:堆分配的成本很高。 它们涉及搜索空闲的内存块、更新内部数据结构以及潜在地触发垃圾回收。值类型,尤其是在集合中使用或作为局部变量使用时,会大大降低堆压力。 这对于垃圾回收语言(如 C#(使用
struct)和 Java(尽管 Java 的基元本质上是值类型,并且 Project Valhalla 旨在引入更通用的值类型)尤为有利。 - 改进缓存局部性:当值类型的数组或集合在内存中连续存储时,顺序访问元素会产生出色的缓存局部性。 CPU 可以更有效地预取数据,从而更快地处理数据。这是性能敏感型应用程序中的一个关键因素,从科学模拟到游戏开发,跨所有硬件架构。
- 无垃圾回收开销:对于具有自动内存管理的语言,值类型可以显着减少垃圾收集器的负载,因为它们通常在超出范围时(堆栈分配)或在收集包含对象时(内联存储)自动释放。
全球示例:在 C# 中,用于数学运算的 Vector3 结构体或用于图形坐标的 Point 结构体,由于堆栈分配和缓存优势,其性能将优于其类对应的结构体,尤其是在性能关键的循环中。 同样,在 Rust 中,默认情况下所有类型都是值类型,开发人员在需要堆分配时会显式使用引用类型(Box、Arc、Rc),这使得围绕值语义的性能考虑成为语言设计固有的内容。
优化泛型和模板
泛型(Java、C#、Go)和模板(C++)提供了强大的机制,用于编写与类型无关的代码,而不会牺牲类型安全。 然而,它们的性能影响可能因语言实现而异。
- 单态化与多态:C++ 模板通常是单态化的:编译器为与模板一起使用的每个不同类型生成一个单独的专用版本的代码。 这会导致高度优化、直接的调用,从而消除运行时调度开销。 Rust 的泛型也主要使用单态化。
- 共享代码泛型:像 Java 和 C# 这样的语言通常使用“共享代码”方法,其中单个已编译的泛型实现处理所有引用类型(在 Java 中进行类型擦除后,或通过在 C# 中内部使用
object用于没有特定约束的值类型)。 虽然可以减小代码大小,但这可能会导致值类型的装箱/拆箱以及运行时类型检查的轻微开销。 但是,C#struct泛型通常受益于专用代码生成。 - 特化和约束:利用泛型中的类型约束(例如,C# 中的
where T : struct)或 C++ 中的模板元编程,允许编译器通过对泛型类型做出更强的假设来生成更有效的代码。 针对常见类型的显式特化可以进一步优化性能。
可操作的见解:了解您选择的语言如何实现泛型。 在性能至关重要时,首选单态化泛型,并注意共享代码泛型实现中的装箱开销,尤其是在处理值类型的集合时。
有效使用不可变类型
不可变类型是在创建后其状态无法更改的对象。 虽然乍一看对于性能来说似乎有悖常理(因为修改需要创建新对象),但不可变性提供了深刻的性能优势,尤其是在并发和分布式系统中,这在全球计算环境中越来越普遍。
- 无锁的线程安全:不可变对象本质上是线程安全的。 多个线程可以并发读取不可变对象,而无需锁或同步原语,锁或同步原语是多线程编程中众所周知的性能瓶颈和复杂性的根源。 这简化了并发编程模型,可以在多核处理器上更轻松地进行扩展。
- 安全共享和缓存:不可变对象可以在应用程序的不同部分之间甚至跨网络边界(使用序列化)安全地共享,而无需担心意外的副作用。 它们是缓存的绝佳选择,因为它们的状态永远不会改变。
- 可预测性和调试:不可变对象的可预测性降低了与共享可变状态相关的错误,从而实现了更强大的系统。
- 函数式编程中的性能:具有强大函数式编程范例(例如,Haskell、F#、Scala,以及越来越多地使用库的 JavaScript 和 Python)的语言大量利用了不可变性。虽然为“修改”创建新对象似乎代价高昂,但编译器和运行时通常会优化这些操作(例如,持久数据结构中的结构共享)以最大程度地减少开销。
全球示例:将配置设置、财务交易或用户配置文件表示为不可变对象可确保一致性,并简化跨全球分布式微服务的并发性。 像 Java 这样的语言提供了 final 字段和方法来鼓励不可变性,而像 Guava 这样的库提供了不可变集合。 在 JavaScript 中,Object.freeze() 和 Immer 或 Immutable.js 等库促进了不可变数据结构。
类型擦除和接口调度优化
类型擦除,通常与 Java 的泛型相关,或者更广泛地说,使用接口/特征来实现多态行为,由于动态调度,可能会引入性能成本。 当在接口引用上调用一个方法时,运行时必须确定对象的实际具体类型,然后调用正确的方法实现 – vtable 查找或类似机制。
- 最小化虚拟调用:在 C++ 或 C# 等语言中,减少性能关键循环中虚拟方法调用的数量可以产生显着的收益。 有时,明智地使用模板 (C++) 或带有接口的结构 (C#) 可以在最初看似需要多态的情况下实现静态调度。
- 专用实现:对于常见的接口,为特定类型提供高度优化的、非多态的实现可以规避虚拟调度成本。
- 特征对象 (Rust):Rust 的特征对象 (
Box<dyn MyTrait>) 提供了类似于虚拟函数的动态调度。 然而,Rust 鼓励“零成本抽象”,其中首选静态调度。 通过接受泛型参数T: MyTrait而不是Box<dyn MyTrait>,编译器通常可以对代码进行单态化,从而启用静态调度和广泛的优化,如内联。 - Go 接口:Go 的接口是动态的,但具有更简单的底层表示(包含类型指针和数据指针的两个字结构)。 虽然它们仍然涉及动态调度,但它们的轻量级特性和语言对组合的关注使它们具有相当的性能。 但是,避免在热路径中不必要的接口转换仍然是一个好习惯。
可操作的见解:分析您的代码以识别热点。 如果动态调度是瓶颈,请调查是否可以通过泛型、模板或这些特定场景的专用实现来实现静态调度。
指针/引用优化和内存布局
数据在内存中的布局方式以及指针/引用的管理方式,对缓存性能和整体速度有深远的影响。 这在系统编程和数据密集型应用程序中尤其相关。
- 面向数据的设计 (DOD):与面向对象设计 (OOD) 中对象封装数据和行为不同,DOD 侧重于组织数据以实现最佳处理。 这通常意味着在内存中连续排列相关数据(例如,结构体数组而不是指向结构体的指针数组),这大大提高了缓存命中率。 这一原则广泛应用于全球高性能计算、游戏引擎和财务建模。
- 填充和对齐:当数据与特定的内存边界对齐时,CPU 通常会表现得更好。 编译器通常会处理这个问题,但显式控制(例如,C/C++ 中的
__attribute__((aligned))、Rust 中的#[repr(align(N))])有时可能需要优化结构体大小和布局,尤其是在与硬件或网络协议交互时。 - 减少间接性:每次指针解引用都是一个间接寻址,如果目标内存尚未在缓存中,则可能导致缓存未命中。 通过直接存储数据或使用紧凑的数据结构来最小化间接寻址(尤其是在紧循环中)可以带来显着的加速。
- 连续内存分配:在频繁的元素访问和缓存局部性至关重要时,首选 C++ 中的
std::vector而不是std::list,或 Java 中的ArrayList而不是LinkedList。 这些结构连续存储元素,从而提高缓存性能。
全球示例:在物理引擎中,将所有粒子位置存储在一个数组中,将速度存储在另一个数组中,将加速度存储在第三个数组中(“数组结构”或 SoA)通常比 Particle 对象的数组(“结构体数组”或 AoS)表现更好,因为 CPU 更有效地处理同构数据,并减少了缓存未命中,尤其是在迭代特定组件时。
编译器和运行时辅助优化
除了显式的代码更改之外,现代编译器和运行时还提供了用于自动优化类型使用的复杂机制。
即时 (JIT) 编译和类型反馈
JIT 编译器(用于 Java、C#、JavaScript V8、Python with PyPy)是强大的性能引擎。 它们在运行时将字节码或中间表示编译为本机机器代码。 关键的是,JIT 可以利用在程序执行期间收集的“类型反馈”。
- 动态去优化和重新优化:JIT 最初可能会对多态调用站点中遇到的类型做出乐观假设(例如,假设始终传递特定的具体类型)。 如果此假设保持很长时间,则可以生成高度优化、专用的代码。 如果稍后证明该假设是错误的,JIT 可以“去优化”回一个不太优化的路径,然后使用新的类型信息“重新优化”。
- 内联缓存:JIT 使用内联缓存来记住方法调用的接收器的类型,从而加速对同一类型的后续调用。
- 逃逸分析:这种优化在 Java 和 C# 中很常见,它确定一个对象是否“逃逸”其局部范围(即,对其他线程可见或存储在字段中)。 如果一个对象没有逃逸,则可以潜在地在堆栈上而不是堆上分配它,从而减少 GC 压力并提高局部性。 此分析很大程度上依赖于编译器对对象类型及其生命周期的理解。
可操作的见解:虽然 JIT 很智能,但编写提供更清晰类型信号的代码(例如,避免在 C# 中过度使用 object 或在 Java/Kotlin 中过度使用 Any)可以帮助 JIT 更快地生成更优化的代码。
提前 (AOT) 编译以进行类型特化
AOT 编译涉及在执行之前将代码编译为本机机器代码,通常在开发时进行。 与 JIT 不同,AOT 编译器没有运行时类型反馈,但它们可以执行 JIT 由于运行时约束而无法执行的广泛的、耗时的优化。
- 积极的内联和单态化:AOT 编译器可以完全内联函数,并在整个应用程序中对泛型代码进行单态化,从而生成更小、更快的二进制文件。 这是 C++、Rust 和 Go 编译器的标志。
- 链接时优化 (LTO):LTO 允许编译器跨编译单元进行优化,从而提供程序的全局视图。 这使得可以更积极地消除死代码、函数内联和数据布局优化,所有这些都受到整个代码库中类型使用方式的影响。
- 减少启动时间:对于云原生应用程序和无服务器函数,AOT 编译语言通常提供更快的启动时间,因为没有 JIT 预热阶段。 这可以降低突发工作负载的运营成本。
全球背景:对于嵌入式系统、移动应用程序(iOS、Android 原生)和启动时间或二进制文件大小至关重要的云函数,AOT 编译(例如,C++、Rust、Go 或 Java 的 GraalVM 原生映像)通常通过基于编译时已知的具体类型使用情况对代码进行特化来提供性能优势。
配置文件引导优化 (PGO)
PGO 弥合了 AOT 和 JIT 之间的差距。 它涉及编译应用程序、使用代表性工作负载运行它以收集配置文件数据(例如,热代码路径、经常采用的分支、实际类型使用频率),然后使用此配置文件数据重新编译应用程序以做出高度知情的优化决策。
- 真实世界的类型使用:PGO 使编译器能够深入了解在多态调用站点中哪些类型最常用,从而允许它为这些常见类型生成优化的代码路径,并为罕见类型生成不太优化的路径。
- 改进的分支预测和数据布局:配置文件数据引导编译器排列代码和数据,以最大限度地减少缓存未命中和分支预测错误,从而直接影响性能。
可操作的见解:PGO 可以为 C++、Rust 和 Go 等语言的生产版本带来实质性的性能提升(通常为 5-15%),尤其是在具有复杂运行时行为或不同类型交互的应用程序中。 它是一种经常被忽视的高级优化技术。
特定于语言的深入研究和最佳实践
高级类型优化技术的应用因编程语言而异。 在这里,我们深入研究了特定于语言的策略。
C++:constexpr、模板、移动语义、小对象优化
constexpr:如果输入已知,则允许在编译时执行计算。 这可以显着减少与类型相关的复杂计算或常量数据生成的运行时开销。- 模板和元编程:C++ 模板对于静态多态性(单态化)和编译时计算非常强大。 利用模板元编程可以将与类型相关的复杂逻辑从运行时转移到编译时。
- 移动语义 (C++11+):引入
rvalue引用和移动构造函数/赋值运算符。 对于复杂类型,“移动”资源(例如,内存、文件句柄)而不是深度复制它们可以通过避免不必要的分配和释放来显着提高性能。 - 小对象优化 (SOO):对于较小的类型(例如,
std::string、std::vector),某些标准库实现采用 SOO,其中少量数据直接存储在对象本身中,从而避免了常见小情况的堆分配。 开发人员可以为其自定义类型实现类似的优化。 - Placement New:高级内存管理技术,允许在预分配的内存中构造对象,适用于内存池和高性能场景。
Java/C#:基元类型、结构体 (C#)、Final/Sealed、逃逸分析
- 优先考虑基元类型:始终在性能关键部分使用基元类型(
int、float、double、bool),而不是它们的包装类(Integer、Float、Double、Boolean),以避免装箱/拆箱开销和堆分配。 - C#
struct:拥抱struct用于小的、类似值的数据类型(例如,点、颜色、小向量),以受益于堆栈分配和改进的缓存局部性。 注意它们的值复制语义,尤其是在将它们作为方法参数传递时。 在传递较大的结构体时,使用ref或in关键字来提高性能。 final(Java) /sealed(C#):将类标记为final或sealed允许 JIT 编译器做出更积极的优化决策,例如内联方法调用,因为它知道该方法无法被覆盖。- 逃逸分析 (JVM/CLR):依赖于 JVM 和 CLR 执行的复杂逃逸分析。 虽然不由开发人员显式控制,但理解其原理鼓励编写代码,其中对象的范围有限,从而实现堆栈分配。
record struct(C# 9+):结合了值类型的优点和记录的简洁性,使定义具有良好性能特征的不可变值类型更容易。
Rust:零成本抽象、所有权、借用、Box、Arc、Rc
- 零成本抽象:Rust 的核心理念。 像迭代器或
Result/Option类型这样的抽象会编译成与手写 C 代码一样快(或更快)的代码,抽象本身没有运行时开销。 这高度依赖于其强大的类型系统和编译器。 - 所有权和借用:在编译时强制执行的所有权系统消除了整个运行时错误类别(数据竞争、悬空指针),同时实现了高效的内存管理,而无需垃圾收集器。 这种编译时保证允许无所畏惧的并发性和可预测的性能。
- 智能指针 (
Box,Arc,Rc):Box<T>:单个所有者,堆分配的智能指针。 当您需要单个所有者的堆分配时使用,例如,用于递归数据结构或非常大的局部变量。Rc<T>(引用计数):用于单线程上下文中的多个所有者。 共享所有权,在最后一个所有者删除时清理。Arc<T>(原子引用计数):用于多线程上下文的线程安全的Rc,但具有原子操作,与Rc相比,会产生轻微的性能开销。
#[inline]/#[no_mangle]/#[repr(C)]:用于指导编译器进行特定优化策略的属性(内联、外部 ABI 兼容性、内存布局)。
Python/JavaScript:类型提示、JIT 考虑事项、小心的数据结构选择
虽然是动态类型的,但这些语言受益于仔细的类型考虑。
- 类型提示 (Python):虽然是可选的,主要用于静态分析和开发人员清晰度,但类型提示有时可以帮助高级 JIT(如 PyPy)做出更好的优化决策。 更重要的是,它们提高了全球团队的代码可读性和可维护性。
- JIT 意识:了解 Python(例如,CPython)是被解释的,而 JavaScript 经常运行在高度优化的 JIT 引擎(V8、SpiderMonkey)上。 避免在 JavaScript 中混淆 JIT 的“去优化”模式,例如频繁更改变量的类型或在热代码中动态添加/删除对象的属性。
- 数据结构选择:对于这两种语言,内置数据结构的选择(Python 中的
listvs.tuplevs.setvs.dict;JavaScript 中的Arrayvs.Objectvs.Mapvs.Set)至关重要。 了解它们的底层实现和性能特征(例如,哈希表查找与数组索引)。 - 原生模块/WebAssembly:对于真正对性能有要求的代码段,可以考虑将计算卸载到原生模块(Python C 扩展、Node.js N-API)或 WebAssembly(用于基于浏览器的 JavaScript),以利用静态类型、AOT 编译的语言。
Go:接口满足、结构体嵌入、避免不必要的分配
- 显式接口满足:Go 的接口被隐式满足,这很强大。 但是,在没有严格需要接口的情况下直接传递具体类型可以避免接口转换和动态调度的少量开销。
- 结构体嵌入:Go 提倡组合而非继承。 结构体嵌入(将一个结构体嵌入另一个结构体中)允许“has-a”关系,这种关系通常比深度继承层次结构更有效,从而避免了虚拟方法调用成本。
- 最小化堆分配:Go 的垃圾收集器经过高度优化,但仍然会产生不必要的堆分配开销。 在适当的情况下优先使用值类型(结构体),重复使用缓冲区,并注意循环中的字符串连接。
make和new函数有不同的用途; 了解何时适合使用哪一个。 - 指针语义:虽然 Go 进行了垃圾收集,但理解何时对结构体使用指针与值副本会影响性能,尤其是在将大型结构体作为参数传递时。
类型驱动性能的工具和方法
有效的类型优化不仅仅是了解技术; 还在于系统地应用它们并衡量其影响。
分析工具(CPU、内存、分配分析器)
您无法衡量自己无法优化的内容。 分析器对于识别性能瓶颈是必不可少的。
- CPU 分析器:(例如,Linux 上的
perf、Visual Studio Profiler、Java Flight Recorder、Go pprof、JavaScript 的 Chrome DevTools)有助于查明“热点” – 消耗最多 CPU 时间的函数或代码段。 它们可以揭示多态调用经常发生的位置、装箱/拆箱开销高的位置,或者由于数据布局不佳而缓存未命中的位置。 - 内存分析器:(例如,Valgrind Massif、Java VisualVM、.NET 的 dotMemory、Chrome DevTools 中的堆快照)对于识别过多的堆分配、内存泄漏和理解对象生命周期至关重要。 这与垃圾收集器的压力以及值类型与引用类型的影响直接相关。
- 分配分析器:专注于分配站点的专用内存分析器可以准确地显示对象在堆上的分配位置,指导通过值类型或对象池来减少分配的努力。
全球可用性:许多这些工具是开源的或内置于广泛使用的 IDE 中,这使得它们对开发人员来说是可访问的,而与他们的地理位置或预算无关。 学习解读它们的输出是一项关键技能。
基准测试框架
一旦确定了潜在的优化,就需要基准测试来可靠地量化它们的影响。
- 微基准测试:(例如,Java 的 JMH、C++ 的 Google Benchmark、C# 的 Benchmark.NET、Go 中的
testing包)允许精确测量隔离的小代码单元。 这对于比较不同的与类型相关的实现(例如,结构体与类、不同的泛型方法)的性能非常有用。 - 宏观基准测试:衡量大型系统组件或整个应用程序在现实负载下的端到端性能。
可操作的见解:在应用优化之前和之后,始终进行基准测试。 对没有明确了解其整体系统影响的微优化持谨慎态度。 确保基准测试在稳定、隔离的环境中运行,以为全球分布式团队生成可重现的结果。
静态分析和 Linter
静态分析工具(例如,Clang-Tidy、SonarQube、ESLint、Pylint、GoVet)可以在运行时之前识别与类型使用相关的潜在性能陷阱。
- 它们可以标记效率低下的集合使用、不必要的对象分配或可能导致 JIT 编译语言中去优化的模式。
- Linter 可以执行促进性能友好类型使用的编码标准(例如,不鼓励在已知具体类型的情况下使用 C# 中的
var object)。
性能测试驱动开发 (TDD)
从一开始就将性能考虑因素整合到您的开发工作流程中是一种强大的做法。 这意味着不仅要编写正确性测试,还要编写性能测试。
- 性能预算:为关键函数或组件定义性能预算。 然后,自动化基准测试可以充当回归测试,如果性能下降超过可接受的阈值,则会失败。
- 早期检测:通过尽早关注类型及其性能特征,并通过性能测试进行验证,开发人员可以防止重要的瓶颈累积。
全球影响和未来趋势
高级类型优化不仅仅是一项学术练习; 它具有切实的全球影响,并且是未来创新的重要领域。
云计算和边缘设备的性能
在云环境中,节省的每一毫秒都直接转化为降低的运营成本和提高的可扩展性。 高效的类型使用最大限度地减少了 CPU 周期、内存占用和网络带宽,这对于具有成本效益的全球部署至关重要。 对于资源受限的边缘设备(物联网、移动设备、嵌入式系统),高效的类型优化通常是可接受功能的先决条件。
绿色软件工程和能源效率
随着数字碳足迹的增长,为能源效率优化软件成为一项全球性当务之急。 速度更快、效率更高的代码,通过更少的 CPU 周期、更少的内存和更少的 I/O 操作来处理数据,直接有助于降低能源消耗。 高级类型优化是“绿色编码”实践的基本组成部分。
新兴语言和类型系统
编程语言的格局不断发展。 新的语言(例如,Zig、Nim)和现有语言的进步(例如,C++ 模块、Java Project Valhalla、C# ref 字段)不断引入用于类型驱动性能的新范式和工具。 紧跟这些发展对于寻求构建最具性能的应用程序的开发人员至关重要。
结论:掌握您的类型,掌握您的性能
高级类型优化对于任何致力于构建高性能、资源高效且具有全球竞争力的软件的开发人员来说,都是一个复杂但必不可少的领域。 它超越了单纯的语法,深入研究了我们程序中数据表示和操作的语义。 从值类型的仔细选择到对编译器优化的细微理解,以及对特定于语言的特性的战略性应用,深入研究类型系统使我们能够编写不仅有效而且出色的代码。
采用这些技术可以使应用程序运行速度更快,消耗更少的资源,并在各种硬件和操作环境中更有效地扩展,从最小的嵌入式设备到最大的云基础设施。 随着世界对更灵敏和可持续的软件的需求越来越大,掌握高级类型优化不再是一项可选技能,而是对卓越工程的基本要求。 立即开始分析、试验和改进您的类型使用 – 您的应用程序、用户和地球将感谢您。