读程序员的制胜技笔记08_死磕优化(上)

这篇具有很好参考价值的文章主要介绍了读程序员的制胜技笔记08_死磕优化(上)。希望对大家有所帮助。如果存在错误或未考虑完全的地方,请大家不吝赐教,您也可以点击"举报违法"按钮提交疑问。

读程序员的制胜技笔记08_死磕优化(上)文章来源地址https://www.toymoban.com/news/detail-746155.html

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打成ture

6.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语句中的逻辑

到了这里,关于读程序员的制胜技笔记08_死磕优化(上)的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处: 如若内容造成侵权/违法违规/事实不符,请点击违法举报进行投诉反馈,一经查实,立即删除!

领支付宝红包 赞助服务器费用

相关文章

  • 读程序员的制胜技笔记05_测试(上)

    3.5.3.1. 也是最容易编写的 3.5.3.2. 只测试单个代码单元:公共函数(public function) 3.5.3.3. 需要是公开的,因为测试应该检查外部可见的接口,而不是类的内部细节 3.5.3.4. 问题是即便它让你能够知晓单个单元是否正常工作,但是并不能保证所有单元能够正常协同工作 3.5.4.1. 测

    2024年02月05日
    浏览(45)
  • 读程序员的制胜技笔记13_安全审查(上)

    5.6.1.1. 任何你不想丢失或泄露的东西都是资产,包括你的源代码、设计文档、数据库、私钥、API令牌、服务器配置,还有Netflix观看清单 5.6.2.1. 每台服务器都会被一些人访问,而每台服务器都会访问其他一些服务器 6.1.1.1. 设计时首先要考虑到安全问题,因为在既有基础上去

    2024年02月05日
    浏览(61)
  • 读程序员的制胜技笔记14_安全审查(下)

    1.2.2.1. 看不出来是什么?那我拒绝为你服务 1.4.1.1. 工作量证明相当消耗客户端的运算资源,对那些性能较低的设备不友好,并且它还会影响设备电池的使用寿命 1.4.1.2. 有可能会严重降低用户体验,其后果甚至比验证码的还要恶劣 3.5.2.1. 存储需求更少,性能更强,数据管理

    2024年02月05日
    浏览(45)
  • 读程序员的制胜技笔记10_可口的扩展

    2.8.3.1. 纯函数有一个好处,它们是100%线程安全的 2.9.1.1. 这套数据结构并不都是无锁的 2.9.1.2. 虽然它们依然使用锁,但它们是被优化过的,锁的持续时间会很短,保证了其速度,而且它们可能比真正的无锁替代方案更简单 2.9.2.1. 其中原始数据从未改变,但每个修改操作都

    2024年02月05日
    浏览(67)
  • 读程序员的制胜技笔记02_算法与数据结构

    3.1.1.1. 根据你的需要,可以有更智能的算法 3.1.3.1. 算法本身并不意味着它很聪明 3.2.1.1. public static bool Contains(int[] array, int lookFor) { for (int n = 0; n < array.Length; n++) {        if (array[n] == lookFor) {            return true;        }    }    return false; } 3.3.1.1. public sta

    2024年02月06日
    浏览(62)
  • 读程序员的制胜技笔记11_与Bug共存(上)

    2.7.3.1. 在构造时验证其有效性,这样一来就不可能包含无效值 2.8.2.1. 其主张一个花括号与声明在同一行 2.9.1.1. 看看这些现成的类型 2.9.3.1. 它代表持续时间 2.9.3.2. 你没有理由用TimeSpan以外的任何东西来表示持续时间,即使你所调用的函数不接受TimeSpan作为参数 2.9.4.1. 它也

    2024年02月05日
    浏览(55)
  • 读程序员的制胜技笔记12_与Bug共存(下)

    2.2.1.1. 故障代码(failing code)放在一个try语句块里,然后加上一个空的catch语句块,就大功告成了 2.2.1.2. 开发者为整个应用程序添加了一个通用的异常处理程序,但实际上这个程序的工作原理就是忽略所有的异常,也就防止所有的崩溃 2.2.1.3. 如果像那样添加一个空的处理程序

    2024年02月05日
    浏览(58)
  • 读程序员的制胜技笔记03_有用的反模式(上)

    4.5.4.1. 你在物理数据库结构上增加了一个依赖项 4.5.4.2. 如果你需要改变信息表的布局或所使用的数据库技术,你就必须检查所有的代码,确保所有的东西都能与新的表布局或新的数据库技术一起工作 4.5.6.1. 这是保持组件或整个层尽可能简单的关键 4.8.3.1. 每个成员只对自己

    2024年02月06日
    浏览(46)
  • 读程序员的制胜技笔记04_有用的反模式(下)

    1.3.1.1. 自己做自己的甲方 3.2.2.1. 紧耦合(tight coupling) 3.2.2.2. 依赖性是万恶之源 3.3.7.1. 因为你可能需要用接口而不是具体的引用来定义依赖关系,但这也会使代码摆脱依赖关系 5.2.3.1. 没有其他错误发生时执行的代码部分 5.3.3.1. 退出点(exit point)是指函数中导致其返回给调用

    2024年02月06日
    浏览(85)
  • 读程序员的README笔记08_依赖管理

    2.6.1.1. 版本不应该被重复使用 2.6.1.2. 永远不要在现有版本下重新发布更改的代码 2.6.2.1. 版本应该帮助人们和工具对版本的优先顺序进行推断 2.6.3.1. 版本信息区分了预先发布的代码和已发布的代码,将构建流水号与构件相关联,并设置了稳定性和兼容性的合理预期 6.2.5.1

    2024年02月05日
    浏览(51)

觉得文章有用就打赏一下文章作者

支付宝扫一扫打赏

博客赞助

微信扫一扫打赏

请作者喝杯咖啡吧~博客赞助

支付宝扫一扫领取红包,优惠每天领

二维码1

领取红包

二维码2

领红包