C# 循环 —— 从 `for` 和 `foreach` 到 CPU 流水线和 LLM‑就绪代码

发布: (2025年12月18日 GMT+8 08:11)
6 min read
原文: Dev.to

Source: Dev.to

请提供您希望翻译的文章正文内容,我将为您翻译成简体中文并保留原有的格式、代码块和链接。

介绍

大多数开发者每天使用循环。
很少有人真正理解语法之下发生了什么。

  • 为什么一个 for 循环飞快,而另一个却爬行?
  • 为什么 foreach 可以是免费…或者暗藏高开销?
  • 为什么相同的循环在运行一段时间后会变快*?
  • 理解循环如何帮助你编写对 LLM 友好、性能可预测的代码

这篇文章是一次思维模型升级——从初学者语法到处理器层面的现实,现代 .NET JIT 行为,以及如何像科学家一样推理循环

如果你能写出 for (int i = 0; i 内存胜过语法 每一次。

3. Roslyn vs JIT — 谁在工作?

阶段它的作用
Roslyn(C# 编译器)生成 IL,降低 foreach,插入分支
RyuJIT(运行时)生成机器代码,移除边界检查,提升不变式,专门化热点循环,使用分层编译 + PGO

The 相同 loop may be re‑compiled after warming up, which is why micro‑benchmarks need a warm‑up phase.

4. forwhiledo/while — 实际差异

循环类型关键区别
while条件 进行评估
do/while循环体至少执行 一次
forwhile 的机器结构相同,但意图更明确

性能差异通常是 噪声。应基于 正确性和可读性 进行选择。

5. foreach 的内部实现

数组

foreach (var x in array)
{
    // …
}
  • 降级为 for 循环
  • 通常会消除边界检查
  • 非常快

List

  • 使用 结构体枚举器
  • 没有分配
  • 仍然非常快

IEnumerable

⚠️ 潜在的性能瓶颈:

  • 接口调度
  • 可能产生分配
  • 无法消除边界检查

在热点循环中避免使用 IEnumerable

6. Bounds‑Check Elimination (BCE)

for (int i = 0; i < arr.Length; i++)
{
    // …
}

经验法则

  • 线性访问
  • 单一索引变量
  • 缓存长度 (int len = arr.Length;)

奇怪的索引模式可能会破坏 BCE。

7. 分支预测:数据胜过代码

两个循环代码相同但数据不同:

数据模式可预测性效果
99 % 可预测快速分支预测器学习模式
50/50 随机较慢频繁误预测

有时 对数据进行排序 能带来比重写循环更大的收益。

8. Span: 零分配迭代

Span<int> slice = array.AsSpan(1, 3);
foreach (ref var x in slice)
    x++;
  • 无分配(仅栈)
  • 缓存友好
  • 安全

Span 是现代 .NET 中最重要的性能工具之一。

9. 向量化循环(SIMD 风格)

Vector<float> v1, v2;
acc += v1 * v2;
  • 在可用时使用 SIMD
  • 每条指令处理多个元素
  • 非常适合数值工作负载

对于 SIMD,数据布局比循环形状更重要

10. yield return:隐藏的状态机

IEnumerable<int> Evens()
{
    yield return 2;
}

编译器会生成一个 状态机

  • 通常是堆分配
  • 额外的间接引用

有助于代码清晰,但在超高频路径中应 避免

11. 世界级循环启发式

  • ✅ 优先使用连续内存
  • ✅ 避免在循环内部分配内存
  • ✅ 在热点路径中避免接口调度
  • ✅ 让 JIT 消除边界检查
  • ✅ 使用 BenchmarkDotNet 进行测量
  • ✅ 在分支之前优化内存

大多数性能错误都是内存错误。

12. 为什么这对 LLM‑辅助代码很重要

LLMs:

  • 生成正确的语法
  • 了解缓存行、分支错误预测或 GC 压力

当你(或 LLM)编写循环时,牢记 硬件现实。编写代码时要:

  1. 内存友好 – 连续的、缓存感知的
  2. 可预测 – 避免随机访问模式,防止分支预测器受损
  3. 轻量分配 – 尤其在热点路径中

这样,你将得到不仅能编译而且运行高效的代码——这是任何 LLM 单独无法推断的。

All model is the safety net.

如果你在这个层面上理解循环,你可以:

  • 引导 LLM
  • 智能地审查生成的代码
  • 在性能分析之前预测性能
  • 编写在真实负载下可扩展的代码

最后思考

循环不是一种结构。
它是 你的数据、JIT(即时编译器)和处理器之间的契约

一旦你明白了这一点,你就不再猜测,而是开始进行工程化。

祝循环愉快。

Back to Blog

相关文章

阅读更多 »

掌握在 .NET 中使用 NuGet 包

NuGet到底是什么?想象一下,NuGet是 .NET 版的 Amazon 或 Mercado Libre。你不会自己制造家具的每一颗螺丝,而是向商店购买它们。- Package...