获得徽章 0
- 最近被朋友安利了一个叫 Mermaid 的画图工具,一入坑发现真好用!它是类似于 HTML 的标记语言,不要一听到「语言」就腿发软哈,Mermaid 的语法很简单,难度系数和 Markdown 差不多。而且 Mermaid 的能也很强大,支持很多种类型的图,比如流程图、ER 图、类图、甘特图等。具体教程可以看它的官方文档:
mermaid-js.github.io
Mermaid 必须得嵌入到 Markdown 文件中才行。把下面这段代码复制到 Markdown 文件中,就可以绘制一个简单的流程图(图一):
```mermaid
graph TD;
A-->B;
A-->C;
B-->D;
C-->D;
```
用这种标记语言画图的好处就是大大降低了维护的成本。比如你千辛万苦画好了一个流程图,然后突然发现中间漏了一个节点,那你为了补上这个节点,又要拖来拖去的修改,手动调整布局,想想就恶心。但是用标记语言画图的话,也许只需添加一行代码就可以。
比如图一的流程图,如果想加一个与 B、C 并列的节点 E,E 也指向 D,那么把上面的代码稍微改一下就可以:
```mermaid
graph TD;
A-->B;
A-->C;
A-->E;
B-->D;
C-->D;
E-->D;
```
效果如图二所示。Mermaid 在渲染时自动帮你调整了布局,比手动调整效率高很多。
图三四五六展示了 Mermaid 支持的其他类型的图。展开评论6 - 1 KB 大小的内存能不能运行一个 2 GB 的程序?
先说答案,能。
程序运行时,本应需要一段连续的内存,但是现实中由于内存碎片的存在,往往不能满足这一要求。比如运行一个程序需要 128 M 大的内存,但是内存中只有一块连续的 100 M 的内存和一块连续的 64 M 的内存,这样的话,程序就运行不了了,明明内存中空闲内存大于 128 M,程序却运行不了,很不合理。于是人们想到了内存分页的办法。
内存分页就是说,加载程序时不再是给程序分配一整块连续的内存,而是把程序所需的内存分成一个个固定大小的内存页,按页去加载,内存页之间不必是连续的,只要知道这个程序所需的内存页都分布在哪就行。
内存分页是操作系统实现的,对于程序来说,它可以把自己使用的内存看作是连续的,操作系统会将程序中使用的内存地址映射到物理内存。
内存分页不仅解决了内存碎片的问题,也降低了内存交换的成本,而且加载程序时也不需要一次性把整个程序都加载到内存,操作系统可以根据需要去加载部分内存页,这使得小内存运行大程序成为可能。Linux 默认的一个内存页大小是 4 KB。用下面这个命令可以查看:
getconf PAGE_SIZE
那么回到刚才的问题,如何用 1 KB 的内存去运行 2 GB 的程序?我们可以把内存页大小设置为 1 KB,每次只加载 1 个内存页,需要哪一页,就把哪一页从硬盘上交换到内存里面。不过由于磁盘I/O速度远远小于内存的I/O速度,这样运行程序会很卡顿。展开517 - 这张图是 MIPS 分配内存的约定。栈由内存高端开始并向下增长。内存低端的第一部分是保留的,之后是代码段,存储程序指令,程序计数器中存的便是这部分内存的地址;代码段之上是静态数据段,是存储常量和其他静态变量的空间。再往上是动态数据区,习惯上称为堆,在程序运行中如果需要动态申请内存,就是在这个区域内给程序分配内存。堆和栈的大小是不固定的,他们两个相互增长,在此消彼长的过程中达到高效的使用。
C 语言通过显式的函数调用在堆上分配和释放空间,malloc() 在堆上分配空间并返回指向它的指针,free() 释放指针指向的堆空间。内存分配完全由 C 程序控制是很多错误产生的根源,忘记释放空间会导致“内存泄漏”,它会逐渐耗尽大量内存以至于操作系统可能崩溃,而过早地释放空间会导致“悬摆指针”(dangling pointer),会造成指针指向程序不想访问的位置。Java 的自动内存分配和垃圾回收机制避免了类似错误的发生。展开33 - 字符集是字符的集合,规定了字符对应的二进制表示方式,由于计算机只认识 0 和 1,所以我们平时使用的各种字符在计算机底层也必须用二进制表示,Unicode 就是一个包含了世界上几乎所有语言的每一个字符的集合。而字符编码则规定了字符的编码方式,常见的字符编码方案有 UTF-8、UTF-16、GBK 等。如果编码和解码的方式不匹配,就会产生乱码。最著名的乱码当属“锟斤拷”和“烫烫烫”了。
“锟斤拷”的来源是这样的。如果我们想要用 Unicode 编码记录一些文本,特别是一些遗留的老字符集内的文本,但是这些字符在 Unicode 中可能并不存在。于是,Unicode 会统一把这些字符记录为 U+FFFD 这个编码。如果用 UTF-8 的格式存储下来,就是\xef\xbf\xbd。如果连续两个这样的字符放在一起,\xef\xbf\xbd\xef\xbf\xbd,这个时候,如果程序把这个字符,用 GB2312 的方式进行 decode,就会变成“锟斤拷”。
而“烫烫烫”,则是因为如果你用了 Visual Studio 的调试器,默认使用 MBCS 字符集。“烫”在里面是由 0xCCCC 来表示的,而 0xCC 又恰好是未初始化的内存的赋值。于是,在读到没有赋值的内存地址或者变量的时候,程序就会输出“烫烫烫”。展开44 - 衡量计算机性能的好坏有两个指标,一个是响应时间,一个是吞吐率。拿人来搬东西做类比的话,响应时间就好比一个人从 A 点搬东西到 B 点所用的时间,吞吐率就好比每次搬的东西的多少。
程序的响应时间包括了硬盘访问、内存访问、I/O操作和操作系统开销等一切时间,这些时间内 CPU 并没有运行我们自己的程序,所以我们真正关注的是 CPU 时间,即 CPU 真正运行我们自己的程序的时间。任何程序到了 CPU 层面都是一条条的指令,所以一个程序的 CPU 时间 = 程序包含的指令数 x 每条指令的平均时钟周期数 x 时钟周期,每条指令的平均时钟周期数简称为 CPI,时钟周期的倒数就是时钟频率,也就是我们常说的主频,所以 CPU 时间 = 指令数 x CPI / 时钟频率。
一开始,为了提高性能,计算机的设计者想法设法地提高主频,从 1978 年到 2000 年,CPU 的主频从 5 MHz 提高到 1.4 GHz,20 多年主频提升了近 300 倍。但是,随着主频的提升,功耗也越来越大,主频提升的速度慢慢降了下来,从 2000 年到 2019 年,CPU 主频只提升了 3 倍。
当 CPU 的性能在主频这里遇到瓶颈时,人们不得不另辟蹊径,在吞吐率上想办法。提高吞吐率的一个办法就是增加 CPU 的核数,以达到并行计算的目的,所以现在 4 核、8 核的处理器几乎成了标配。
但是 CPU 的核数也不可能无限制地加下去,除了上述两个方法,还有一些原则性的性能提升方法,比如加速大概率事件、通过流水线提高性能、通过预测提高性能等。展开评论5