热路径应避免堆分配、确保方法可内联、优先无锁并发、慎用边界检查:用span/stackalloc替代字符串拼接,arraypool预分配集合,避免async捕获大对象;抽离私有小函数并标记aggressiveinlining;用concurrent集合和interlocked替代lock;必要时手动消除边界检查。

避免在热路径中分配堆内存
JIT 编译器对频繁分配的 new 表达式会失去内联机会,且 GC 压力会直接拖慢高并发吞吐。尤其注意 string 拼接、new List<t>()</t>、匿名对象和闭包捕获的局部变量。
- 用
Span<char></char>或stackalloc char[256]替代string.Format或$"..."(尤其在日志/序列化热路径) - 预分配集合:用
ArrayPool<t>.Shared.Rent(1024)</t>替代new T[1024],用完必须Return() - 避免在
async方法里捕获大对象——这会把栈帧提升为堆上的状态机,触发额外分配
public void ProcessBatch(Span<byte> input)
{
Span<char> buffer = stackalloc char[512];
if (Utf8Parser.TryParse(input, out int value, out int bytesConsumed))
{
// 避免 ToString() → 新 string → 堆分配
var written = value.TryFormat(buffer, out int charsWritten);
// 后续直接用 buffer.Slice(0, charsWritten)
}
}让方法满足 JIT 内联条件
JIT(特别是 .NET 6+ 的 Tiered Compilation)只对满足严格条件的方法做内联,否则调用开销在每微秒级竞争中会被放大。内联失败常见于含 try/catch、yield return、过大 IL(默认阈值约 32 字节)、或含虚方法调用的方法。
- 把热路径逻辑抽成
private或internal实例方法(非虚),避免virtual/interface调用 - 用
[MethodImpl(MethodImplOptions.AggressiveInlining)]标记小函数(如位运算包装、长度检查),但别滥用——JIT 会忽略明显不合适的标记 - 避免在热方法里调用
DateTime.Now、Guid.NewGuid()等已知非内联方法;改用Stopwatch.GetTimestamp()或预生成 ID 池
慎用 lock 和 Monitor —— 优先无锁结构
lock 编译为 Monitor.Enter/Exit,在争用激烈时触发内核态切换,JIT 无法优化其同步语义。即使无争用,.NET 运行时仍需维护同步块索引(SyncBlock),带来间接开销。
用eclipse开发android程序的时,跟VS一样是可以断点单步调试的。 Eclipse Java编辑器不但能够为开发者提供代码编写、语法纠错和实时编译等常用功能,而且还能够对Java源代码进行快速修改、重构等高级操作。感兴趣的朋友可以过来看看
- 优先用
ConcurrentQueue<t></t>、ConcurrentDictionary<tkey tvalue></tkey>(它们内部用分段锁 + CAS) - 计数类场景用
Interlocked.Increment(ref counter)替代lock包裹counter++ - 需要复杂状态更新?考虑
SpinWait+Interlocked.CompareExchange实现自旋CAS,但要设最大重试次数防饿死
private long _sequence = 0; public long NextId() => Interlocked.Increment(ref _sequence); // 零分配、无锁、JIT 可内联为单条 x86 inc 指令
数组访问与边界检查的取舍
默认情况下,C# 数组访问带运行时边界检查(ldelem 指令附带 check)。JIT 在部分场景能消除它(如 for 循环中 i ),但一旦引入复杂索引表达式或跨方法传递索引,消除失败概率陡增。
- 确认索引绝对安全时,用
array.AsSpan().DangerousGetReferenceAt(index)(.NET 7+)绕过检查 - 更推荐:用
Span<t></t>/ReadOnlySpan<t></t>替代T[]参数,JIT 对Span索引的优化更激进 - 避免在循环内重复调用
list.Count或array.Length——虽是属性,但 JIT 不总将其提升为循环外变量(尤其当数组被其他线程修改时)
真正难的不是写出“看起来快”的代码,而是识别哪些操作在 JIT 眼里是“不可信”或“不可优化”的——比如一个看似简单的 ToString() 调用,背后可能是堆分配 + 字符串驻留 + GC 压力,而这些在 profiler 里往往藏得很深。









