CPU合并写缓冲区简介

现代CPU采用了大量的技术来抵消内存访问带来的延迟。读写内存数据期间,CPU能执行成百上千条指令。

多级SRAM(Static Random-Access Memory 用作CPU的Cache)缓存是减小这种延迟带来的影响的主要手段。此外,SMP-对称多处理(Symmetrical Multi-Processing)系统采用消息传递协议来实现缓存之间的一致性。遗憾的是,现代的CPU实在是太快了,即使是使用了缓存,有时也无法跟上CPU的速度。因此,为了进一步减小延迟的影响,一些鲜为人知的缓冲区派上了用场。

本文将探讨“合并写存储缓冲区(write combining store buffers)”,以及如何写出有效利用它们的代码。

CPU缓存是一种高效的非链式结构的hash map,每个桶(bucket)通常是64个字节。这就是一个“缓存行(cache line)”。缓存行是内存交换的实际单位。例如,主存中地址A会映射到一个给定的缓存行C。

如果CPU需要访问的地址hash后的行尚不在缓存中,那么缓存中对应位置的缓存行会被清除,以便载入新的行。例如,如果我们有两个地址,通过hash算法hash到同一缓存行,那么新的值会覆盖老的值。

当CPU执行存储指令(store)时,它会尝试将数据写到离CPU最近的L1缓存。如果此时出现缓存未命中,CPU会访问下一级缓存。此时,无论是英特尔还是许多其它厂商的CPU都会使用一种称为“合并写(write combining)”的技术。

在请求L2缓存行的所有权尚未完成时,待存储的数据被写到处理器自身的众多跟缓存行一样大小的存储缓冲区之一。这些芯片上的缓冲区允许CPU在缓存子系统准备好接收和处理数据时继续执行指令。当数据不在任何其它级别的缓存中时,将获得最大的优势。

当后续的写操作需要修改相同的缓存行时,这些缓冲区变得非常有趣。在将后续的写操作提交到L2缓存之前,可以进行缓冲区写合并。 这些64字节的缓冲区维护了一个64位的字段,每更新一个字节就会设置对应的位,来表示将缓冲区交换到外部缓存时哪些数据是有效的。

也许你要问,如果程序要读取已被写入缓冲区的某些数据,会怎么样?我们的硬件工程师已经考虑到了这点,在读取缓存之前会先去读取缓冲区的。

这一切对我们的程序意味着什么?

如果我们能在缓冲区被传输到外部缓存之前将其填满,那么将大大提高各级传输总线的效率。如何才能做到这一点呢?好的程序将大部分时间花在循环处理任务上。

这些缓冲区的数量是有限的,且随CPU模型而异。例如在Intel CPU中,同一时刻只能拿到4个。这意味着,在一个循环中,你不应该同时写超过4个不同的内存位置,否则你将不能享受到合并写(write combining)的好处。

public final class WriteCombining {

    private static final int    ITERATIONS = Integer.MAX_VALUE;
    private static final int    ITEMS      = 1 << 24;
    private static final int    MASK       = ITEMS - 1;

    private static final byte[] arrayA     = new byte[ITEMS];
    private static final byte[] arrayB     = new byte[ITEMS];
    private static final byte[] arrayC     = new byte[ITEMS];
    private static final byte[] arrayD     = new byte[ITEMS];
    private static final byte[] arrayE     = new byte[ITEMS];
    private static final byte[] arrayF     = new byte[ITEMS];

    public static void main(final String[] args) {
        for (int i = 1; i <= 3; i++) {
            out.println(i + " SingleLoop duration (ns) = " + runCaseOne());
            out.println(i + " SplitLoop duration (ns) = " + runCaseTwo());
        }
        int result = arrayA[1] + arrayB[2] + arrayC[3] + arrayD[4] + arrayE[5] + arrayF[6];
        out.println("result = " + result);
    }

    public static long runCaseOne() {
        long start = System.nanoTime();
        int i = ITERATIONS;

        while (--i != 0) {
            int slot = i & MASK;
            byte b = (byte) i;
            arrayA[slot] = b;
            arrayB[slot] = b;
            arrayC[slot] = b;
            arrayD[slot] = b;
            arrayE[slot] = b;
            arrayF[slot] = b;
        }
        return System.nanoTime() - start;
    }

    public static long runCaseTwo() {
        long start = System.nanoTime();
        int i = ITERATIONS;
        while (--i != 0) {
            int slot = i & MASK;
            byte b = (byte) i;
            arrayA[slot] = b;
            arrayB[slot] = b;
            arrayC[slot] = b;
        }
        i = ITERATIONS;
        while (--i != 0) {
            int slot = i & MASK;
            byte b = (byte) i;
            arrayD[slot] = b;
            arrayE[slot] = b;
            arrayF[slot] = b;
        }
        return System.nanoTime() - start;
    }
}

这个程序在我的Windows 7 64位英特尔酷睿i7860@2.8 GHz系统上产生的输出如下:

    1 SingleLoop duration (ns) = 14019753545
    1 SplitLoop  duration (ns) = 8972368661
    2 SingleLoop duration (ns) = 14162455066
    2 SplitLoop  duration (ns) = 8887610558
    3 SingleLoop duration (ns) = 13800914725
    3 SplitLoop  duration (ns) = 7271752889

上面的例子说明:如果在一个循环中修改6个数组位置(内存地址),程序的运行时间明显长于将任务拆分的方式,即,先写前3个位置,再修改后3个位置。

通过拆分循环,我们做了更多的工作,但程序花费的时间更少!欢迎利用神奇的“合并写(write combining)”。通过使用CPU架构的知识,正确的填充这些缓冲区,我们可以利用底层硬件加速我们的程序。

不要忘了超线程(hyper-threading),可能会有2个线程竞争同一个核的缓冲区。

  • 4
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
### 回答1: A:在m路平衡归并排序中,需要对m个有序的子文件进行归并排序,其中每个子文件的大小可能非常大,如果一次性将所有子文件读入内存,会导致内存不够用。因此,需要使用输入缓冲区来分批读入子文件,以充分利用内存空间。同理,为了充分利用CPU和磁盘的速度,也需要将归并排序的过程中的内部归并和输出阶段进行并行处理。因此,需要使用2m个输入缓冲区进行内部归并,以及2个输出缓冲区来存储归并排序后的数据。这样可以提高效率并减少排序所需的时间。 ### 回答2: 在m路平衡归并排序中,为了实现输入、内部归并和输出的并行处理,需要设置2m个输入缓冲区和2个输出缓冲区的原因如下: 1、输入缓冲区:在进行归并排序时,需要将初始数据分成m个小块,每个小块分配一个输入缓冲区用于暂存数据。通过设置2m个输入缓冲区,可以实现同时对2m个小块进行读取和暂存,以提高读取数据的效率。 2、内部归并:在每一轮的内部归并中,需要将相邻的小块进行比较和合并合并的结果存放在输出缓冲区中。通过设置2个输出缓冲区,可以实现交替使用,一个用于存放当前轮次的合并结果,另一个用于存放下一轮次的合并结果。这样可以避免将数据直接回到输入缓冲区中,提高整体归并排序的效率。 3、并行处理:通过设置2m个输入缓冲区和2个输出缓冲区,可以实现并行处理的效果。在每一轮的内部归并中,可以同时读取和暂存2m个小块的数据,而在下一轮的内部归并中,可以同时入2m个小块的合并结果。这种并行处理方式可以充分利用计算机的多核心特性,提高归并排序的执行效率。 综上所述,设置2m个输入缓冲区和2个输出缓冲区可以实现输入/内部归并/输出的并行处理,提高m路平衡归并排序的效率和速度。 ### 回答3: 在进行m路平衡归并排序的过程中,为了实现输入、内部归并和输出的并行处理,需要设置2m个输入缓冲区和2个输出缓冲区的原因如下: 首先,对于输入缓冲区,由于m路平衡归并排序需要将输入数据分到m个归并段中并进行归并排序,每个归并段的数据需要依次读取到内存中进行排序。而为了实现并行处理,可以设置2m个输入缓冲区,将输入数据分散到不同的缓冲区中,使得多个归并段的数据可以同时读取到内存中,并行进行排序操作,提高排序的效率和速度。 其次,对于输出缓冲区,归并排序在内部归并过程中需要将已排序的数据按顺序入到输出缓冲区中,以便后续的归并操作。而为了实现并行处理,可以设置2个输出缓冲区,其中一个用于归并操作输出,另一个则可以作为备用输出缓冲区。这样,在进行归并操作时,可以将归并后的数据输出到一个缓冲区中,同时将备用输出缓冲区的数据入到磁盘或其他存储介质中,提高归并排序的并行处理效率。 综上所述,为了实现输入、内部归并和输出的并行处理,需要设置2m个输入缓冲区和2个输出缓冲区。这样可以实现多个归并段的数据同时读取到内存中进行排序,并且能够同时进行归并输出和备用输出的入操作,提高m路归并排序的效率和性能。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值