Mechanical Sympathy 系列翻译---合并写(write combining )

本文探讨了现代CPU中的Write Combining技术,通过合并连续的写操作到缓存行来减少内存访问延迟。文中提供了代码示例,展示了如何通过合理安排写操作顺序来提高程序的运行效率。

 

此文乃是翻译。。

原文地址:http://mechanical-sympathy.blogspot.com/2011/07/write-combining.html

墙内地址:http://ifeve.com/write-combining/

 

                  合并写(write combining )

    现代CPU采用大量的技术来抵消内存访问延迟。 从DRAM存储中读取或者写入数据的时间CPU可以执行上百个指令。

    用来降低这种延迟的主要手段是使用多层次的SRAM缓存。此外,也有SMP系统采用消息传递协议来实现缓存之间的一致性。即便如此,现代CPU是如此之快,是缓存根本无法企及的。因此,为了进一步降低延迟一些鲜为人知的缓冲区(buffers )也被使用。

    本文探讨“合并写存储缓冲区(write combining store buffers)”,以及我们如何编写代码可以有效地使用它们。

     CPU缓存是一个高效的非链式结构的hash map,每个桶(bucket)通常是64个字节。被称为之为一个“缓存行(cache line)”。缓存行(cache line)是内存传输的有效单元。例如,主存中地址A会映射到一个给定的缓存行C。

     如果CPU需要访问的地址hash之后并不在缓存行(cache line)中,那么缓存中对应位置的缓存行(cache line)会失效,以便让新的值可以取代该位置的现有值。例如,如果我们有两个地址,通过hash算法hash到同一缓存行,那么新的值会覆盖老的值。

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

    当请求L2缓存行的所有权的时候,最典型的是将处理器的store buffers中某一项写入内存的期间, 在缓存子系统( cache sub-system)准备好接收、处理的数据的期间,CPU可以继续处理其他指令。当数据不在任何缓存层中缓存时,将获得最大的优势。

    当连串的写操作需要修改相同的缓存行时,会变得非常有趣。在修改提交到L2缓存之前,这连串的写操作会首先合并到缓冲区(buffer)。 这些64字节的缓冲(buffers )维护在一个64位的区域中,每一个字节(byte)对应一个位(bit),当缓冲区被传输到外缓存后,标志缓存是否有效。

也许你要问如果程序要读取一些已被写入缓冲区(buffer)的数据,会发生什么事呢?我们的硬件会友好的处理,它们在读取缓存之前会先读取缓冲区

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

    如果我们可以在缓冲区被传输到外缓存之前能够填补这些缓冲区(buffers ),那么我们将大大提高传输总线的效率。如何才能做到这一点呢?大部分程序花费其大部分时间在循环的处理某项任务。

由于这些缓冲区的数量是有限的,并且它们根据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个数组位置(对应6个内存地址),我们的程序运行时间明显长于拆分工作的方式,即是:先写前3个位置,后修改后3个位置的数据。

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

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

 

<think>好的,用户问的是如何使用Sympathy for Data这个数据处理工具。首先我需要确认这个工具的基本信息和主要功能。Sympathy for Data是一个用于数据流程自动化和可视化的工具,支持多种数据处理任务,比如数据转换、分析和可视化。用户可能需要进行数据预处理或者分析,所以需要了解工具的安装、基本操作和常见用例。 接下来,我需要回忆用户提供的引用内容。引用[1]提到使用Pandas处理CSV文件,引用[2]涉及数据预处理和access key,引用[3]是一篇论文可能与分布式系统有关,但不太相关。所以主要参考引用[1]和[2]中的数据处理步骤。 用户可能需要具体的步骤指南,比如安装、界面介绍、创建数据流、运行流程等。还需要考虑用户可能的后续问题,比如高级功能或问题排查,所以在相关问题里可以提到这些。 另外,用户的问题中没有提到是否已经安装该工具,所以应该在步骤中包含安装方法,比如通过pip或conda。同时,要确保示例代码正确,并符合LaTeX格式要求,比如行内公式用$...$,独立公式用$$...$$。 最后,生成的相关问题需要覆盖安装、常见操作、数据导入导出、可视化以及与其他工具的比较,这样用户如果有进一步的问题可以自行探索或继续提问。</think>### 如何使用 Sympathy for Data 数据处理工具? Sympathy for Data 是一个基于 Python 的开源工具,专注于数据流程的自动化和可视化。以下是其核心使用方法: --- #### 1. **安装与配置** - **安装**:通过 `pip` 或 `conda` 安装: ```bash pip install sympathy-for-data # 或 conda install -c conda-forge sympathy-for-data ``` - **启动界面**:安装后运行 `sympathy` 命令启动图形化界面[^2]。 --- #### 2. **基本操作流程** - **创建数据流**: - 新建项目后,通过拖拽节点(如 `Table`、`CSV Reader`)构建数据流。 - 示例:读取 CSV 文件并预处理: ```python # 引用节点配置示例(对应GUI操作) from sympathy.api import node as synode class ReadCSV(synode.Node): """读取CSV文件节点""" inputs = synode.parameters.FilePath('选择文件', filter='*.csv') outputs = synode.parameters.Table('输出数据表') ``` - **连接节点**:将数据源节点(如 `CSV Reader`)与处理节点(如 `Filter`、`Join`)连接。 --- #### 3. **数据操作示例** - **过滤与计算**: - 使用 `Filter` 节点筛选满足条件的行,例如保留 $valence > 0$ 的数据。 - 通过 `Calculator` 节点添加新列,例如计算 $arousal^2 + valence^2$: $$ \text{新列} = \sqrt{\text{arousal}^2 + \text{valence}^2} $$ - **可视化**:使用 `Plot` 节点生成散点图或折线图。 --- #### 4. **导出结果** - 通过 `CSV Writer` 或 `Excel Writer` 节点保存处理后的数据[^1]。 --- #### 5. **高级功能** - **自定义脚本**:通过 `Python Script` 节点编个性化处理逻辑。 - **集成外部工具**:例如与 LabelImg 标注工具联动,管理数据集路径[^1]。 ---
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值