快捷搜索:  汽车  科技

redis核心难点(Redis为什么这么快)

redis核心难点(Redis为什么这么快)由于线程有创建和上下文切换的开销,导致并发执行的速度会比串行慢的情况出现。循环次数 串行执行耗时/ms 并发执行耗时 并发比串行快多少 1亿 130 77 约1倍 1千万 18 9 约1倍 1百万 5 5 相差无几 10万 4 3 相差无几 1万 0 1 慢Talk is cheap Show me the code如下代码演示了串行和并发执行并累加操作的时间:public class ConcurrencyTest { private static final long count = 1000000000; public static void main(String[] args) { try { concurrency(); } catch (InterruptedException e) {

并发(concurrency):指在同一时刻只能有一条指令执行,但多个进程指令被快速的轮换执行,使得在宏观上具有多个进程同时执行的效果,但在微观上并不是同时执行的,只是把时间分成若干段,使多个进程快速交替的执行。

并行(parallel):指在同一时刻,有多条指令在多个处理器上同时执行。所以无论从微观还是从宏观来看,二者都是一起执行的。

不难发现并发在同一时刻只有一条指令执行,只不过进程(线程)在CPU中快速切换,速度极快,给人看起来就是“同时运行”的印象,实际上同一时刻只有一条指令进行。但实际上如果我们在一个应用程序中使用了多线程,线程之间的轮换以及上下文切换是需要花费很多时间的

redis核心难点(Redis为什么这么快)(1)

何同学

Talk is cheap Show me the code

如下代码演示了串行和并发执行并累加操作的时间:

public class ConcurrencyTest { private static final long count = 1000000000; public static void main(String[] args) { try { concurrency(); } catch (InterruptedException e) { e.printStackTrace(); } serial(); } private static void concurrency() throws InterruptedException { long start = System.currentTimeMillis(); Thread thread = new Thread(new Runnable() { @Override public void run() { int a = 0; for (long i = 0; i < count; i ) { a = 5; } } }); thread.start(); int b = 0; for (long i = 0; i < count; i ) { b--; } thread.join(); long time = System.currentTimeMillis() - start; System.out.println("concurrency : " time "ms b=" b); } private static void serial() { long start = System.currentTimeMillis(); int a = 0; for (long i = 0; i < count; i ) { a = 5; } int b = 0; for (long i = 0; i < count; i ) { b--; } long time = System.currentTimeMillis() - start; System.out.println("serial : " time "ms b=" b); } }

执行时间如下表所示,不难发现,当并发执行累加操作不超过百万次时,速度会比串行执行累加操作要慢。

循环次数 串行执行耗时/ms 并发执行耗时 并发比串行快多少 1亿 130 77 约1倍 1千万 18 9 约1倍 1百万 5 5 相差无几 10万 4 3 相差无几 1万 0 1 慢

由于线程有创建和上下文切换的开销,导致并发执行的速度会比串行慢的情况出现。

redis核心难点(Redis为什么这么快)(2)

上下文切换

redis核心难点(Redis为什么这么快)(3)

多个线程可以执行在单核或多核CPU上,单核CPU也支持多线程执行代码,CPU通过给每个线程分配CPU时间片(机会)来实现这个机制。CPU为了执行多个线程,就需要不停的切换执行的线程,这样才能保证所有的线程在一段时间内都有被执行的机会。

此时,CPU分配给每个线程的执行时间段,称作它的时间片。CPU时间片一般为几十毫秒。CPU通过时间片分配算法来循环执行任务,当前任务执行一个时间片后切换到下一个任务。

但是,在切换前会保存上一个任务的状态,以便下次切换回这个任务时,可以再加载这个任务的状态。所以任务从保存到再加载的过程就是一次上下文切换

根据多线程的运行状态来说明:多线程环境中,当一个线程的状态由Runnable转换为非Runnable(Blocked、Waiting、Timed_Waiting)时,相应线程的上下文信息(包括CPU的寄存器和程序计数器在某一时间点的内容等)需要被保存,以便相应线程稍后再次进入Runnable状态时能够在之前的执行进度的基础上继续前进。而一个线程从非Runnable状态进入Runnable状态可能涉及恢复之前保存的上下文信息。这个对线程的上下文进行保存和恢复的过程就被称为上下文切换。

基于内存

以MySQL为例,MySQL的数据和索引都是持久化保存在磁盘上的,因此当我们使用SQL语句执行一条查询命令时,如果目标数据库的索引还没被加载到内存中,那么首先要先把索引加载到内存,再通过若干寻址定位和磁盘I/O,把数据对应的磁盘块加载到内存中,最后再读取数据。

如果是机械硬盘,那么首先需要找到数据所在的位置,即需要读取的磁盘地址。可以看看这张示意图:

redis核心难点(Redis为什么这么快)(4)

磁盘结构示意图

读取硬盘上的数据,第一步就是找到所需的磁道,磁道就是以中间轴为圆心的圆环,首先我们需要找到所需要对准的磁道,并将磁头移动到对应的磁道上,这个过程叫做寻道。

然后,我们需要等到磁盘转动,让磁头指向我们需要读取的数据开始的位置,这里耗费的时间称为旋转延迟,平时我们说的硬盘转速快慢,主要影响的就是耗费在这里的时间,而且这个转动的方向是单向的,如果错过了数据的开头位置,就必须等到盘片旋转到下一圈的时候才能开始读。

最后,磁头开始读取记录着磁盘上的数据,这个原理其实与光盘的读取原理类似,由于磁道上有一层磁性介质,当磁头扫过特定的位置,磁头感应不同位置的磁性状态就可以将磁信号转换为电信号。

可以看到,无论是磁头的移动还是磁盘的转动,本质上其实都是机械运动,这也是为什么这种硬盘被称为机械硬盘,而机械运动的效率就是磁盘读写的瓶颈。

扯得有点远了,我们说回redis,如果像Redis这样把数据存在内存中,读写都直接对数据库进行操作,天然地就比硬盘数据库少了到磁盘读取数据的这一步,而这一步恰恰是计算机处理I/O的瓶颈所在。

在内存中读取数据,本质上是电信号的传递,比机械运动传递信号要快得多。

redis核心难点(Redis为什么这么快)(5)

猜您喜欢: