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中快速切换,速度极快,给人看起来就是“同时运行”的印象,实际上同一时刻只有一条指令进行。但实际上如果我们在一个应用程序中使用了多线程,线程之间的轮换以及上下文切换是需要花费很多时间的。
何同学
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 慢
由于线程有创建和上下文切换的开销,导致并发执行的速度会比串行慢的情况出现。
上下文切换
多个线程可以执行在单核或多核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这样把数据存在内存中,读写都直接对数据库进行操作,天然地就比硬盘数据库少了到磁盘读取数据的这一步,而这一步恰恰是计算机处理I/O的瓶颈所在。
在内存中读取数据,本质上是电信号的传递,比机械运动传递信号要快得多。