1.过早的优化是万恶之源1.1.著名的计算机科学家高德纳(Donald Knuth)的一句名言1.2.原话是:“对于约97%的微小优化点,我们应该忽略它们:过早的优化是万恶之源。而对于剩下的关键的3%,我们则不能放弃优化的机会。”2.过早优化是提升自己的根源2.1.优化就是解决问题,过早优化创造了暂时没有发现的、假想的问题来解决,就像国际象棋选手设置棋局来挑战自己2.2.探索性编程是提高技能的合法途径3.不要过早优化的原因3.1.优化会增加代码的耦合性,使其更难维护3.2.优化也是一项投资,其回报在很大程度上取决于你能将优化结果保持多久3.3.如果规范发生变化,你所进行的优化可能会让你陷入一个难以摆脱的困境3.4.你可能试图为一个本来就不存在的问题进行优化,而使你的代码变得不那么可靠4.解决该解决的问题4.1.你需要真正理解你在优化时到底做了什么权衡,这意味着你必须把需要解决的问题了解透彻4.2.根据问题的性质,解决方式可以发挥的效用和实现它需要花费的时间可能有很大的不同4.3.基准测试(benchmarking)4.3.1.比较性能指标的行为4.3.1.1.只能给你一堆用于比较的数字4.3.1.2.不能告诉你代码的运行速度是快还是慢4.3.1.3.可以告诉你它们比其他一些代码运行得慢还是快4.3.2.无法帮助你确定造成性能问题的根本原因4.3.3.可以帮助你确定是否存在性能问题4.3.4.你应该常常对你的那些代码优化进行基准测试,来看看你的优化是否还有更进一步的余地4.3.5.BenchmarkDotNet库4.3.5.1.可以消除因测量误差或者调用开销产生的波动4.3.5.2.适用于微观基准测试4.3.5.2.1.适用于微观基准测试4.3.5.3.基准测试并没有试图消除函数调用的开销或for循环本身的开销4.3.6.Math.DivRem()函数比普通的除法和求余操作能快多少4.3.6.1.C#

public class SampleBenchmarkSuite {  [Params(1000)]  ⇽--- 避免编译器优化  public int A;  [Params(35)]  ⇽---   public int B;  [Benchmark]  ⇽--- 用属性标记要进行基准测试的操作  public int Manual() {    int division = A / B;    int remainder = A % B;    return division + remainder;  ⇽--- 我们将值返回,这样编译器就不会丢掉计算步骤  }  [Benchmark]  ⇽---   public int DivRem() {    int division = Math.DivRem(A, B, out int remainder);    return division + remainder;  ⇽---   }}using System;using System.Diagnostics;using BenchmarkDotNet.Running;namespace SimpleBenchmarkRunner {  public class Program {    public static void Main(string[] args) {      BenchmarkRunner.Run<SampleBenchmarkSuite>();    }  }}

4.3.6.2.Math.DivRem()的速度是分别进行除法和求余操作的两倍4.3.6.3.使用Stopwatch编写自己的基准测试程序4.3.6.4.C#

private const int iterations = 1_000_000_000;private static void runBenchmarks() {  var suite = new SampleBenchmarkSuite {    A = 1000,    B = 35  };  long manualTime = runBenchmark(() => suite.Manual());  long divRemTime = runBenchmark(() => suite.DivRem());    reportResult("Manual", manualTime);    reportResult("DivRem", divRemTime);  }private static long runBenchmark(Func<int> action) {  var watch = Stopwatch.StartNew();  for (int n = 0; n < iterations; n++) {    action();  ⇽--- 我们在这里调用基准测试代码  }  watch.Stop();  return watch.ElapsedMilliseconds;}private static void reportResult(string name, long milliseconds) {  double nanoseconds = milliseconds * 1_000_000;  Console.WriteLine("{0} = {1}ns / operation",    name,    nanoseconds / iterations);}

4.3.6.5.DivRem函数的运行速度比除法和求余操作快,因为它被转换为需要更少周期的指令4.4.性能与响应性4.4.1.关于缓慢的一般原则4.4.1.1.任何需要超过100毫秒的动作都会让人感觉到延迟,而任何需要超过300毫秒的动作都被认为是缓慢的,更不要说花整整1秒的动作4.4.2.性能并不总是与响应性(responsiveness)有关4.4.3.任务是计算密集型(computationally intensive)的4.4.3.1.最快的计算方法是在工作完成之前不做其他事情4.4.3.2.与其以最快的速度进行计算,不如腾出一些计算周期来显示一个进度条,也许可以计算出估计的剩余时间,并在用户等待的时候显示一个漂亮的动画4.4.3.3.最后,你的代码运行速度会变慢,但结果会更成功4.4.4.延迟也会影响性能,而不仅仅是用户体验4.4.4.1.你的数据库驻留在磁盘上,而你的数据库服务器驻留在网络上,这意味着,即使你写了最快的SQL查询,并在你的数据库上定义了最快的索引,你仍然受到物理定律的约束,你不能得到任何快于1毫秒的结果5.迟缓的剖析5.1.并不是所有的性能问题都是关于速度的5.1.1.有些是关于响应性的5.2.CPU是处理从RAM中读取的指令的芯片,并在一个永无止境的循环中重复执行这些指令5.3.时钟周期(clock cycle)5.3.1.简称为周期5.4.CPU速度通常以赫兹(Hz)为单位,表示它在1秒内能处理多少个时钟周期5.5.有时CPU甚至可以处理比其处理速度所允许的更多指令5.5.1.一些指令需要一个以上的时钟周期来完成5.5.2.现代CPU可以在一个核心上并行处理多条指令5.6.每一个与代码执行速度有关的性能问题都归结为有多少条指令被执行和被执行多少次5.7.当你优化代码时,本质上你要做的是减少指令的执行次数,或者使用更快版本的指令6.从头开始6.1.直接在源头解决问题6.1.1.定位到根本问题6.2.减少执行指令数量第二好的方法是选择一个更快的算法6.3.最好的方法显然是完全删除代码6.3.1.删除你不需要的代码6.3.2.不要在代码库中保留不需要的代码6.3.2.1.即使不会直接降低代码的性能,也会降低开发人员的“性能”,最终降低代码的性能6.3.3.不要保留注释过的代码6.3.3.1.可以使用你最喜欢的源代码控制系统(如Git或Mercurial)的历史功能来恢复旧代码6.4.让代码运行速度变慢的最简单的方法之一是把它放在另一个循环里6.4.1.不应该把计算密集型的代码放在属性的源代码里面6.4.2.小心属性6.4.2.1.它们包含逻辑,而它们的逻辑并不简单6.5.面向字符串的编程6.5.1.选择适合的类型会比使用字符串拥有更好的性能6.5.2.字符串有一些微妙的方式可以被添加进你的代码里6.5.3.一个布尔变量来优化代码6.5.3.1.C#

if ((string)HttpContext.Items["Bozo"] == "true") {...}

6.5.3.2.C#

if ((bool?)HttpContext.Items["Bozo"] == true) {...}

6.5.3.3.节省存储开销和解析开销,还有助于避免你的打字错误,比如把True打成ture6.5.3.4.简单的错误对你来说影响不是特别大,但如果错误变成了习惯,影响就会积小成大6.6.if语句中的布尔表达式是按照它们的书写顺序来评估6.6.1.可以简单地调换表达式的位置6.6.2.建议根据操作数类型对表达式进行排序6.6.2.1.1.变量6.6.2.2.2.字段6.6.2.3.3.属性6.6.2.4.4.方法调用6.6.3.逻辑符号是有优先级之说的,以确保你在优化布尔运算时不会意外地破坏if语句中的逻辑