Files
flykhan 2536c937e3 feat: 完整中文翻译 maths-cs-ai-compendium(数学·计算机科学·AI 知识大全)
翻译自英文原版 maths-cs-ai-compendium,共 20 章全部完成。

第01章 向量 | 第02章 矩阵 | 第03章 微积分
第04章 统计学 | 第05章 概率论 | 第06章 机器学习
第07章 计算语言学 | 第08章 计算机视觉 | 第09章 音频与语音
第10章 多模态学习 | 第11章 自主系统 | 第12章 图神经网络
第13章 计算与操作系统 | 第14章 数据结构与算法
第15章 生产级软件工程 | 第16章 SIMD与GPU编程
第17章 AI推理 | 第18章 ML系统设计
第19章 应用人工智能 | 第20章 前沿人工智能

翻译说明:
- 所有数学公式 $...$ / $$...$$、代码块、图片引用完整保留
- mkdocs.yml 配置中文导航 + language: zh
- README.md 已翻译为中文(兼 docs/index.md)
- docs/ 目录包含指向各章文件的 symlink
- 约 29,000 行中文内容,排除 .cache/ 构建缓存
2026-05-03 10:23:20 +08:00

274 lines
17 KiB
Markdown
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# 编程语言
*编程语言是人类意图与机器执行之间的接口。本文涵盖语言范式、类型系统、内存管理策略、编译流水线、解释与JIT编译、关键语言特性、领域特定语言以及设计权衡。*
- 每一份软件、每一个ML模型、每一个操作系统都是用编程语言编写的。但存在数百种语言,每种都有不同的优势。为什么?因为语言设计涉及基本的权衡:性能 vs 安全、表现力 vs 简洁性、控制 vs 抽象。理解这些权衡有助于你为工作选择合适的工具,并理解你所处的约束。
## 语言范式
- **范式**是一种编程风格:一套指导你如何组织代码和思考问题的原则。
- **命令式**编程将计算描述为一系列改变状态的命令。"设x为5。将3加到x。如果x > 7,打印它。"C、Python和Java本质上是命令式的。心智模型是一个带有内存的机器,你逐步修改它。
- **面向对象(OOP)**编程围绕**对象**组织代码:数据(属性)和行为(方法)的捆绑。对象通过相互发送消息来交互。关键思想是**封装**(将内部状态隐藏在公共接口之后)、**继承**(通过扩展现有类创建新类)和**多态**(通过共享接口统一处理不同类型)。Java、C++和Python支持OOP。
- **函数式编程(FP)**将计算视为数学函数的求值。核心原则:**不可变性**(数据一旦创建就不改变)、**纯函数**(输出仅取决于输入,无副作用)和**一等函数**(函数是可以作为参数传递、从其他函数返回和存储在变量中的值)。Haskell是纯函数式的。Python、JavaScript和Scala支持函数式风格。
- 纯函数易于推理、测试和并行化(没有共享的可变状态意味着没有竞态条件)。这就是为什么函数式思想越来越多地用于分布式系统和数据管道。JAX(本书中一直在使用)是函数式的:`jax.grad` 之所以有效,是因为JAX函数是纯函数。
- **逻辑编程**描述*什么*应该为真,而不是*如何*计算它。你陈述事实和规则,运行时找到解。Prolog是经典例子:给定"苏格拉底是人"和"所有人都是必死的",引擎推导出"苏格拉底是必死的。"逻辑编程用于AI知识库和类型检查。
- 大多数现代语言是**多范式**的:Python支持命令式、OOP和函数式风格。Rust支持命令式和函数式。范式是一种工具,不是信仰。
## 类型系统
- **类型**对值进行分类,并确定哪些操作是有效的。整数3和字符串"3"是不同的类型:你可以对整数进行加法,但不能对字符串(好吧,你可以拼接字符串,但那是不同的操作)。
- **静态类型**:类型在**编译时**检查,在程序运行之前。类型错误及早被发现。C、Java、Rust和Go是静态类型的。你必须声明类型(或者编译器推断它们):
```rust
let x: i32 = 5; // Rustx是一个32位整数
let y: f64 = 3.14; // y是一个64位浮点数
// let z = x + y; // 编译错误:不能加 i32 和 f64
```
- **动态类型**:类型在**运行时**检查,当操作实际执行时。更灵活,但类型错误只有在代码运行时才暴露。Python、JavaScript和Ruby是动态类型的:
```python
x = 5 # x是一个int(目前)
x = "hello" # 现在x是一个字符串——没有错误
```
- **强类型**:语言阻止隐式类型转换。Python是强类型的:`"3" + 5` 引发TypeError。**弱类型**:语言静默地转换类型。JavaScript是弱类型的:`"3" + 5` 得到 `"35"`(数字被强制转换为字符串)。C是弱类型的:你可以将指针强制转换为整数。
- **类型推断**让编译器推导类型而无需显式注解:
```rust
let x = 5; // 编译器推断:i32
let y = x + 3.0; // 编译错误:混合类型,即使有推断
```
- **泛型**(参数化多态)让你编写适用于任何类型的代码:
```rust
fn largest<T: PartialOrd>(list: &[T]) -> &T {
let mut max = &list[0];
for item in &list[1..] {
if item > max { max = item; }
}
max
}
// 适用于整数、浮点数、字符串——任何支持比较的类型
```
- 对于ML:Python的动态类型使实验快速,但隐藏了错误。生产ML系统越来越多地使用类型提示(`def train(model: nn.Module, lr: float) -> float`)和静态分析工具(mypy)以在部署前捕获错误。PyTorch和JAX使用Python以获得灵活性;TensorRT和ONNX Runtime使用C++以获得性能。
## 内存管理
- 每个程序分配和释放内存。如何管理这是最具影响力的语言设计决策之一。
![内存布局:堆栈从高地址向下增长,堆向上增长,代码和数据在底部](../images/stack_vs_heap.svg)
- **堆栈**存储局部变量和函数调用帧。分配很简单(移动栈指针),释放是自动的(函数返回时弹出帧)。堆栈访问很快,因为它总在缓存中。但堆栈有固定大小(通常1-8 MB),且仅支持LIFO(后进先出)分配。
- **堆**存储动态分配的数据(编译时大小未知的对象、数组、字符串)。堆分配较慢(需要找到一个空闲块),需要显式或自动释放。堆可以增长到填满可用内存。
- **手动内存管理**(C、C++):程序员显式分配(`malloc`)和释放(`free`)堆内存。最大控制和性能,但极易出错:
- **释放后使用**:访问已被释放的内存。导致崩溃或安全漏洞。
- **双重释放**:释放同一内存两次。破坏分配器的内部数据结构。
- **内存泄漏**:分配了内存但从未释放。程序慢慢消耗所有可用RAM。
- **垃圾回收(GC)**:运行时自动检测并释放不再可达的内存。程序员从不调用 `free`
- **跟踪GC**Java、Go、Python的循环收集器):定期从"根"(堆栈变量、全局变量)遍历所有可达对象,释放不可达对象。简单但导致**GC暂停**:收集器运行时程序停止。现代收集器(Go的并发GC、Java的ZGC)将暂停时间最小化到亚毫秒级。
- **引用计数**Python的主要机制、Swift、Objective-C):每个对象跟踪有多少引用指向它。当计数降到0时,对象被立即释放。无暂停,但无法处理**循环**(A引用B,B引用A,两者计数都 > 0 但都不可达)。Python使用单独的循环检测器来处理此问题。
- **所有权**(Rust):编译器在编译时强制实施内存安全规则,零运行时开销。
- 每个值有且仅有一个**所有者**。当所有者超出作用域时,该值被丢弃(释放)。
- 值可以被**借用**(引用),但编译器强制:要么一个可变引用,要么任意数量的不可变引用,永远不能同时存在。
- 这阻止了释放后使用、双重释放、数据竞争和悬垂指针,全部在编译时完成。无需GC,无运行时开销。
- **借用检查器**是Rust的杀手级特性,也是其最陡峭的学习曲线。它保证了内存安全和线程安全,且没有垃圾回收,这就是Rust越来越多地用于性能关键系统(OS内核、游戏引擎、ML推理运行时如Candle和Burn)的原因。
## 编译流水线
- **编译器**在程序运行之前将源代码转换为机器码(或其他目标语言)。该流水线有几个阶段:
![编译流水线:源代码→词法分析器→解析器→语义分析→优化器→代码生成→机器码](../images/compilation_pipeline.svg)
1. **词法分析**(分词):将源文本转换为令牌流。`x = 3 + y` 变为 `[IDENT("x"), EQUALS, INT(3), PLUS, IDENT("y")]`。词法分析器去除空白和注释。
2. **语法分析**:从令牌流构建**抽象语法树(AST)**。AST表示程序的层次结构。`3 + y * 2` 解析为 `Add(3, Mul(y, 2))`(乘法优先级更高)。解析器检查语法:括号不匹配和缺少分号在此被捕获。
3. **语义分析**:检查类型、解析变量名、验证函数调用参数是否正确。静态类型检查在此发生。输出是带类型注解的AST。
4. **优化**:在不改变行为的情况下转换程序以使其运行更快。常见优化:
- **常量折叠**:在编译时计算 `3 + 5`,替换为 `8`
- **死代码消除**:移除永远无法执行的代码。
- **循环展开**:用重复的内联代码替换循环以减少分支开销。
- **内联**:用函数体替换函数调用,消除调用开销。
5. **代码生成**:将优化后的表示转换为目标机器码(x86、ARM)或中间表示。
- **LLVM**是主流的编译器基础设施。它提供了一个通用中间表示(LLVM IR),许多语言可以编译到该表示上。LLVM的优化器在这个IR上工作,其后端为许多目标生成机器码。Clang(C/C++)、Rust、Swift、Julia和许多其他语言使用LLVM。这意味着LLVM优化器的改进同时惠及所有这些语言。
## 解释与JIT编译
- **解释器**逐行(或逐语句)执行程序而不产生机器码。这使得启动快速且开发交互式,但执行较慢(每行每次运行时都要重新分析)。
- 大多数解释型语言实际上编译为**字节码**:一种比源代码更简单但不特定于机器的中间表示。字节码在**虚拟机(VM)**上运行。
- **CPython**(标准Python实现)将Python源代码编译为字节码(`.pyc` 文件),由CPython VM执行。VM逐条指令解释字节码。这就是为什么Python在计算密集型代码上比C慢约~100倍。
- **JVM**Java虚拟机):Java编译为JVM字节码(`.class` 文件)。JVM最初解释字节码,然后**JIT编译**频繁执行的代码路径("热点")为本机机器码。这就是为什么Java启动比C慢(解释开销),但对于长时间运行的程序(JIT优化的热路径)接近C的速度。
- **JIT(即时)编译**在运行时将代码编译为机器码,使用仅在执行期间可用的信息。JIT可以根据实际运行时数据进行优化:如果一个函数总是用整数参数调用,JIT生成专门化的仅整数机器码,跳过类型检查。
- **PyPy**是另一个带有JIT编译器的Python实现。它通过将热点循环JIT编译为机器码,使大多数Python代码运行速度比CPython快5-10倍。然而,它与C扩展模块(NumPy、PyTorch)的兼容性有限,这限制了它在ML中的使用。
- 从解释到编译的范围不是二元的:
- 纯解释:Bash shell脚本。
- 字节码解释:CPython。
- 字节码 + JITJVM、.NET CLR、LuaJIT、PyPy。
- 提前(AOT)编译:C、C++、Rust、Go。
- AOT + 运行时代码生成:JAX的 `jax.jit` 在首次调用时编译Python函数为优化的XLA代码,然后缓存编译后的版本。
## 关键语言特性
- **闭包**:捕获其包围作用域中变量的函数。该函数"闭合"其定义时的环境:
```python
def make_adder(n):
def add(x):
return x + n # n 从包围作用域捕获
return add
add5 = make_adder(5)
print(add5(3)) # 8
```
- 闭包是回调、装饰器和部分应用背后的机制。它们对函数式编程至关重要。
- **模式匹配**:一种强大的控制流机制,解构数据并根据其形状进行分支:
```rust
match value {
Some(x) if x > 0 => println!("Positive: {}", x),
Some(0) => println!("Zero"),
Some(x) => println!("Negative: {}", x),
None => println!("Nothing"),
}
```
- 模式匹配比if-else链更具表现力:它检查数据的结构(是Some还是None?它包含的值是否符合某个条件?),而不仅仅是相等性。Python在3.10中增加了结构模式匹配(`match`/`case`)。
- **代数数据类型(ADT)**:可以是多个变体之一的类型,每个变体携带不同的数据。`Result` 类型要么是 `Ok(value)` 要么是 `Err(error)``Tree` 要么是 `Leaf(value)` 要么是 `Node(left, right)`。ADT结合模式匹配可以穷尽处理所有情况,消除整类bug(空指针异常、未处理的错误码)。
- **特质与接口**:定义一个类型必须实现的一组方法,而不指定如何实现。这实现了多态:一个接受"任何实现了Display特质的类型"的函数可以处理整数、字符串和自定义类型。Rust使用特质,Java使用接口,Go使用隐式接口,Python使用鸭子类型("如果它走路像鸭子……")。
## 领域特定语言
- **领域特定语言(DSL)**是为特定问题域设计的语言,在该领域内用通用性换取表现力。
- **SQL**:关系数据库的语言。`SELECT name FROM users WHERE age > 30` 比等价的命令式循环可读性强得多且更易优化。数据库引擎优化查询执行计划,自动选择连接策略和索引使用。
- **正则表达式**:用于文本模式匹配的微型语言。`\d{3}-\d{4}` 匹配像"555-1234"这样的电话号码。正则引擎将模式编译为有限自动机以实现高效匹配。
- **着色器语言**GLSL、HLSL、Metal Shading Language):在GPU核心上运行的程序,用于计算像素颜色、顶点位置或计算操作。着色器是海量并行的:每次调用独立处理一个像素或一个元素。这与CUDA用于ML计算的执行模型相同。
- 在ML中,像PyTorch和JAX这样的框架本质上是嵌入在Python中的张量计算DSL。它们提供领域特定的抽象(张量、自动微分、设备放置),同时利用Python的生态系统。
## 语言设计权衡
- 没有一种语言在所有方面都是最好的。设计是关于选择哪些权衡:
- **性能 vs 安全**:C提供了原始速度和硬件控制,但会让你破坏内存。Rust以编译时内存安全提供相当的速度。Java提供内存安全但有垃圾回收开销。Python提供最大的安全性和表现力,但执行速度慢100倍。
- **表现力 vs 简洁性**:Haskell的类型系统可以表达非常精确的约束,但有陡峭的学习曲线。Go故意省略了泛型(直到最近)、继承和异常以追求简洁性。Python的"应该有一种——最好只有一种——显而易见的做法"哲学保持了语言的可学习性。
- **控制 vs 抽象**:C/C++让你控制内存布局、缓存行为和硬件交互。Python隐藏了所有这些。对于ML训练(GPU计算占主导),Python的开销可以忽略不计。对于ML推理(每微秒都很关键),C++或Rust可能是必要的。
- **编译速度 vs 运行时速度**:Go在几秒内编译完成(简单的类型系统,最小优化)。Rust需要几分钟编译(复杂的类型系统,激进优化)。权衡的是开发者迭代速度与部署后的性能。
- ML生态系统反映了这些权衡:Python用于实验和训练(表现力取胜),C++/CUDA用于内核和推理(性能取胜),Rust用于基础设施和安全关键系统(安全取胜)。
## 编程任务(使用CoLab或笔记本)
1. 探索闭包和高阶函数。实现一个简单的函数工厂,验证闭包捕获其环境。
```python
def make_multiplier(factor):
"""返回一个将输入乘以 factor 的函数。"""
def multiply(x):
return x * factor
return multiply
double = make_multiplier(2)
triple = make_multiplier(3)
print(f"double(5) = {double(5)}") # 10
print(f"triple(5) = {triple(5)}") # 15
# 闭包通过引用捕获,而不是通过值
def make_counter():
count = [0] # 可变的容器以允许修改
def increment():
count[0] += 1
return count[0]
return increment
counter = make_counter()
print(f"counter() = {counter()}") # 1
print(f"counter() = {counter()}") # 2
print(f"counter() = {counter()}") # 3
```
2. 比较动态与静态类型行为。展示Python的动态类型如何提供灵活性但可能隐藏bug。
```python
def add(a, b):
return a + b
# 适用于不同类型——灵活!
print(add(3, 5)) # 8 (int + int)
print(add("hello ", "world")) # "hello world" (str + str)
print(add([1, 2], [3, 4])) # [1, 2, 3, 4] (list + list)
# 但类型错误仅在运行时暴露:
try:
print(add("hello", 5)) # TypeErrorstr + int
except TypeError as e:
print(f"运行时错误:{e}")
print("静态类型检查器会在运行前捕获此问题")
```
3. 测量解释型Python与编译/JIT方法在计算密集型任务上的性能差异。
```python
import time
import jax
import jax.numpy as jnp
n = 1_000_000
# 纯Python循环(解释型)
start = time.time()
total = 0.0
for i in range(n):
total += i * i
python_time = time.time() - start
# JAX(通过XLA编译)
@jax.jit
def sum_squares_jax(n):
return jnp.sum(jnp.arange(n, dtype=jnp.float32) ** 2)
_ = sum_squares_jax(10) # 预热JIT
start = time.time()
result = sum_squares_jax(n)
jax_time = time.time() - start
print(f"Python loop: {python_time:.4f}s")
print(f"JAX (JIT): {jax_time:.6f}s")
print(f"Speedup: {python_time / jax_time:.0f}x")
```