首页 > 编程语言 >读程序员的制胜技笔记09_死磕优化(下)

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

时间:2023-11-11 09:02:38浏览次数:36  
标签:异步 缓存 int 09 程序员 buffer SIMD 制胜 CPU

1. 造成延迟的3个方面

1.1. CPU

1.2. I/O

1.3. 人

2. 不要打包数据

2.1. 一个打包的数据结构

2.1.1. C#

struct UserPreferences {

  public byte ItemsPerPage;
  public byte NumberOfItemsOnTheHomepage;
  public byte NumberOfAdClicksICanStomach;
  public byte MaxNumberOfTrollsInADay;
  public byte NumberOfCookiesIAmWillingToAccept;
  public byte NumberOfSpamEmailILoveToGetPerDay;
}

2.1.2. 由于CPU对未对齐边界的内存地址的访问速度较慢,你节省空间的好处就被这个速度损失给抵消了

2.1.3. 把结构中的数据类型从byte改为int,并创建一个基准测试来测试二者差异,你可以看到byte的访问时间几乎是int的两倍,尽管它只占用了1/4的内存

2.1.4. 要避免不必要地优化内存

2.2. 对齐是指内存地址为4、8、16等2的倍数,至少是CPU的字大小(word size)

2.3. CPU的字大小

2.3.1. 由CPU一次能处理多少比特的数据来决定

2.3.2. 与CPU是32位还是64位的密切相关

2.3.3. 字大小主要反映了CPU的累加寄存器的大小

2.4. CPU从没有对齐的内存地址(unaligned memory addresses)读取数据时,会造成“惩罚”

2.4.1. 从某一个内存地址,比如1023,读取数据的时间会比从内存地址1024读取数据的时间要长

2.5. 有些CPU根本不允许你访问未对齐的内存地址

2.5.1. Amiga计算机中用到的Motorola 68000和一些基于ARM的处理器

2.6. 编译器通常会处理好对齐问题

3. 就地取材

3.1. 缓存是指将经常使用的数据保存在同一个位置,这个位置相较其他位置来说,访问速度更快

3.2. CPU有自己的缓存存储器,访问速度各不相同,但都比RAM的访问速度快

3.2.1. 顺序读取比随机读取内存的速度要快

3.2.2. 顺序读取一个数组可以比顺序读取一个链表更快,尽管两者从头到尾读取一遍需要的时间都为O(N),但数组的性能比链表更好,原因是数组的下一个元素在内存的缓存区的可能性更大

3.3. CPU通常猜你是按顺序读取数据的

3.4. 链表中的元素在内存中是分散的,因为它们是单独分配的

3.4.1. 并不意味着链表没有用,它有很好的插入/删除性能,而且当它增长时,内存开销较少

3.5. 在大多数情况下,列表对你来说是最优选择,而且它的读取速度更快

4. 将依赖性工作分开

4.1. CPU指令是由处理器上不连续的单元处理的

4.2. 管线(pipelining)

4.2.1. 由于解码单元需要等待指令完成,它可以在内存访问时为下一条指令做解码工作

4.2.2. CPU可以在单个内核上并行执行多条指令,只要下一条指令不依赖于前一条指令的结果

4.3. 重新排序指令,减少代码之间的依赖性,这样一条指令就不会因为依赖上一个操作的结果而阻塞管线上的下一条指令

4.3.1. 可以帮你提高代码的运行速度,因为强依赖代码会阻塞管线

5. 要有可预测性

5.1. 分支预测(branch prediction)

5.2. 到了编译阶段,它们都会变成一堆比较、加法和分支操作

5.3. 机器语言,即CPU能看懂的原生语言,由一连串的数字组成

5.4. 汇编语言是由机器语言转换过来,让人能够看懂的语言

5.4.1. 当你需要了解CPU计算密集型的任务时,汇编语言尤其能发挥神奇的作用

5.4.2. 只要你熟悉汇编语言的结构,就可以阅读JIT编译器生成的机器代码,并了解其实际行为

5.5. 汇编语法在不同的CPU架构中有所不同,建议至少要熟悉一种

5.5.1. 它将减少你对“黑箱”内发生的事情的恐惧

5.5.2. 它可能看起来很复杂

5.5.3. 但它比我们写程序的语言更简单,甚至可以说是原始的

5.6. 在执行之前,CPU不可能知道compare指令是否会执行成功,但由于有了分支预测,它可以根据观察到的情况做出较为靠谱的预测

5.7. 根据它的预测,CPU开始处理它预测的那个分支的指令,如果它预测成功,那之前做的准备就派上了用处,提高了性能

5.8. 你给CPU的“惊喜”越少,它的表现就越好

5.8.1. 由随机数组成的数组会比较慢的原因:在这种情况下,分支预测会派不上用场

6. SIMD

6.1. 单指令、多数据(single instruction,multiple data,SIMD)

6.2. CPU也支持专门的指令,可以用一条指令同时对多个数据进行计算

6.3. 对多个变量进行相同的计算,SIMD可以在其支持的架构上大大提升其性能

6.4. 当你有一个计算密集型的任务,需要同时对多个元素进行相同的操作时,你可以考虑使用SIMD

6.5. C#通过System.Numerics命名空间中的Vector类型提供SIMD功能

6.6. 有些CPU根本不支持SIMD,所以你必须先检查CPU上是否有这个功能

6.6.1. C#

if (!Vector.IsHardwareAccelerated) {

//这里是非向量实现
}

6.7. CPU在同一时间可以处理多少个给定的类型。这在不同的处理器上是不同的,所以你必须先查询一下

6.7.1. int chunkSize = Vector<int>.Count;

6.7.2. 当你知道你一次可以处理的数量时,你可以直接分块处理缓冲区(buffer in chunk)

6.8. 经典的简单乘法

6.8.1. C#

public static void MultiplyEachClassic(int[] buffer, int value) {

  for (int n = 0; n < buffer.Length; n++) {
    buffer[n] *= value;
  }
}

6.9. Vector类型乘法

6.9.1. C#

public static void MultiplyEachSIMD(int[] buffer, int value) {

  if (!Vector.IsHardwareAccelerated) {
    MultiplyEachClassic(buffer, value);  ⇽--- 如果CPU不支持SIMD,则调用之前的普通方法来实现
  }
 
  int chunkSize = Vector<int>.Count;  ⇽--- 查询SIMD一次可以处理多少值
  int n = 0;
  for (; n < buffer.Length - chunkSize; n += chunkSize) {
    var vector = new Vector<int>(buffer, n);  ⇽--- 将数组段复制到SIMD寄存器当中
    vector *= value;  ⇽--- 一次性乘所有的值
    vector.CopyTo(buffer, n);  ⇽--- 替换结果
  }
 
  for (; n < buffer.Length; n++) {  ⇽--- 用普通的方法处理剩余的字节
    buffer[n] *= value;
  }  ⇽--- 
}

6.9.2. 基于SIMD的代码的运行速度是普通代码的约两倍

6.9.3. 根据你处理的数据类型和你对数据进行的操作,它还可以更快

7. I/O

7.1. I/O包含CPU与外围硬件沟通的一切,比如磁盘、网络适配器,还有GPU

7.2. I/O通常是性能链上最慢的环节

7.2.1. 缓慢源自物理学,但硬件可以独立于CPU运行,所以它可以在CPU做其他工作时保持运行

7.2.2. 可以把CPU和I/O的工作重叠起来,在更小的时间范围内完成整体操作

7.3. 键盘是字符设备,因为它一次发送一个字符

7.4. 许多I/O设备都是以块的形式进行读写的,称为块设备(block device)

7.4.1. 网络和存储设备通常是块设备

7.4.2. 块设备不能读取小于块大小的东西,所以读取小于典型块大小(typical block size)的东西不合理

7.5. 缓冲区大小对I/O性能的影响

7.5.1. 图

7.5.2. 使用512字节的缓冲区也会产生巨大的作用,复制操作的速度提高了6倍

7.5.3. 将其增加到256KB时效果最好,再大一点也只能产生边际改善

7.5.3.1. 在256KB之后,提升突然变得杯水车薪

7.5.4. Windows I/O使用256KB作为其I/O操作和缓冲区管理的默认缓冲区大小

7.6. 当你处理I/O时,找到理想的缓冲区大小,并避免分配超过你需要的内存

8. 异步I/O

8.1. 它经常与多线程相混淆,多线程是一种并行化模型,通过让一个任务在不同的核心上运行,使得操作的速度变快

8.2. 多线程和异步I/O也可以一起使用,因为它们分别解决不同场景的痛点

8.3. I/O自然是异步的,因为外部硬件几乎总是比CPU响应慢,而且CPU不喜欢等待

8.4. 中断和直接存储器访问(direct memory access,DMA)等机制的发明是为了让硬件在I/O操作完成后向CPU发出信号,以便CPU能够传输结果

8.5. 异步I/O是一种仅适用于I/O重度操作的并行化模型,它可以在单个核心上工作

8.6. 异步I/O可以并行地运行多个I/O操作并收集结果,而不需要忍受多线程带来的问题

8.7. 异步代码还可以帮助提高事件驱动机制(event-driven mechanism),特别是用户界面的响应速度,完成这个操作也不需要消耗线程

8.8. 直到2010年初,异步I/O还是使用回调函数管理的

8.9. async/await语义来编写异步I/O代码的好方法

8.10. 你开始的异步操作越多,你就越容易失去对操作的控制

8.10.1. "回调地狱”(callback hell)

8.11. 如果你的异步代码在等待某些东西完成,你就做错了

9. 缓存

9.1. 缓存是立即提高性能的最有力的方法之一

9.2. 缓存失效可能是个难题,但如果你只缓存你不担心失效的东西,那它就不是个问题

9.3. 不需要使用在如Redis或Memcached这样的单独服务器上的复杂缓存层,你可以使用内存缓存(in-memory cache)

9.3.1. 微软在System.Runtime.Caching包中提供的MemoryCache类

9.4. 避免使用那些不是为缓存设计的数据结构

9.4.1. 它们通常没有任何驱逐(eviction)或过期机制,从而成为内存泄露的来源,并最终导致程序崩溃

9.5. 不要害怕缓存会无限存在

9.5.1. 无论是缓存的驱逐还是应用程序的重启都会在世界末日前发生

10. 要点

10.1. 把不成熟的优化作为练习,并从中学习

10.2. 避免为了优化而优化,这会把自己带入“深坑”

10.3. 始终用基准测试来验证你的优化

10.4. 保持优化和响应性的平衡

10.5. 在构建数据结构时,尽可能做到内存对齐,以获得更好的性能

10.6. 配备缓存定位、管线和SIMD

10.7. 使用适当的缓冲机制来提高I/O性能

10.8. 使用异步编程来并行运行代码和I/O操作,不浪费线程

10.9. 在紧急情况下,放弃缓存方案

标签:异步,缓存,int,09,程序员,buffer,SIMD,制胜,CPU
From: https://www.cnblogs.com/lying7/p/17825040.html

相关文章

  • 8年经验的老程序员,告诉你如何看透面试、通过面试
    面试方法面试主要考察:技能、能力、价值观、匹配度1、技能一个程序员技能过关才能完成日常开发任务,所以基础知识也是面试的必考内容。一个Android程序员需要掌握的技能还真不少。Java基础,开源框架,Handler,Binder,项目实战通通得会。针对上面的每个Android技术栈的知识点,都有多年的面试......
  • 北上广的33岁的Android程序员:赚钱困难,跳槽畏首畏尾,何去何从?
    前言深夜,繁华的北上广街道上霓虹灯熠熠生辉,但对于我来说,这一切却仿佛失去了光泽。33岁资深Android程序员,已经在这一行奋斗了数年。曾怀揣着远大梦想,充满着热血与激情,勇敢地踏上北上广的热土,期望在这片大地上开创属于自己的精彩人生。然而,现实总是残酷无情。赚钱难:每日埋头于代码的......
  • [20231109]bash shell快捷键alt+number的问题.txt
    [20231109]bashshell快捷键alt+number的问题.txt--//前一阵子,我想实现12行合并1行的输出,理论讲要使用paste命令加入12个-.输入命令时候要数输入了多少-.我知道bashshell有一--//个快捷键alt+number可以产生连续输入某个字符,但是我一直不知道如何关掉这个功能.有时候误触发这......
  • 程序员的内卷的本质
    今天再聊聊程序员「内卷」。其实内卷的话题现在是中国新一代年轻人,包括中年人共同的问题。各行各业都在内卷,不仅仅互联网IT行业,也包括了现在的金融行业,电商行业,甚至包括开一个小店,做个小生意等等都陷入各种内卷之争。听起来大家其实都很厌倦内卷这个词,因为内卷在客观上和主观上都是......
  • 2023金九银十Android程序员面试题参考指南!
    前言还有不到一个礼拜就是金九银十招聘高峰期了,在这里分享一份《Android面试题汇总》这些面试题都是互联网大厂真实流出的面试内容,每个问题都附带完整详细的答案,不像网上的那些资料三教九流有的甚至还没答案,这些面试题都是我也是整理出来的精品资料。希望能给那些有需要朋友在求职......
  • .NET周刊【11月第1期 2023-11-09】
    国内文章C#/.NET/.NETCore优秀项目和框架2023年10月简报https://www.cnblogs.com/Can-daydayup/p/17804085.html本文主要介绍了公众号“追逐时光者”定期推广和分享的C#/.NET/.NETCore优秀项目和框架。其中包括SKIT.FlurlHttpClient.ByteDance和SwashbucklerDiary两个项目。S......
  • P1072 [NOIP2009 提高组] Hankson 的趣味题
    /*"爆int,爆int,你就会爆int了是吧"还是挺难的一道题具体思路就是通过求出b1的所有约数,然后看看其中有几个满足gcd(a0,x)==a1&&lcm(b0,x)==b1的数x通过上一题其实可以求出来,在int范围内一个数的约数数量最多只有1600个lcm可以通过a......
  • #yyds干货盘点# LeetCode程序员面试金典:累加数
    题目累加数是一个字符串,组成它的数字可以形成累加序列。一个有效的累加序列必须至少包含3个数。除了最开始的两个数以外,序列中的每个后续数字必须是它之前两个数字之和。给你一个只包含数字'0'-'9'的字符串,编写一个算法来判断给定输入是否是累加数。如果是,返回true......
  • #yyds干货盘点# LeetCode程序员面试金典:供暖器
    题目冬季已经来临。你的任务是设计一个有固定加热半径的供暖器向所有房屋供暖。在加热器的加热半径范围内的每个房屋都可以获得供暖。现在,给出位于一条水平线上的房屋houses和供暖器heaters的位置,请你找出并返回可以覆盖所有房屋的最小加热半径。注意:所有供暖器heaters......
  • 原来阿里字节大厂程序员的简历长这样!
    1前言疫情过后,IT行业内卷就不说了,有很多小伙伴跟我咨询面试环节及简历上的事,都想在简历方面有些突出,博眼球。我发现大部分初、中级甚至高级程序员的简历逻辑都比较混乱,花里胡哨,没有突出重点,筛选简历的人员一眼看上去不知道优缺劣势。通过本人多年的被面试和面试别人的经验看过......