An Event-Triggered Programmable Prefetcher for Irregular Workloads (2018 asplos)

An Event-Triggered Programmable Prefetcher for Irregular Workloads (2018 asplos)

  1. 主要解决的问题:大数据量应用程序(例如graph, database, HPC workloads)中的非规则数据访问所带来的延迟问题

  2. 论文提出的方法:论文提出了一种事件触发的可编程的预取器,该预取器结合了通用计算单元的灵活性和基于事件的编程模型,同时论文提供了编译器技术,能够自动的将原始代码转换为带有注释的事件集合。通过这种方法,能够提供复杂的推测预取。最终该方法能够提供3倍的加速比。

  3. 传统的解决方法:

    • 使用多线程掩盖存储访问延迟
    • 基于地址的预取器(固定步长的预取器)
    • 指针提取预取器
    • 软件预取指令
    • Fetcher Units:使用多个高度专用的访存单元来控制特点的访存类型,通过并行loads数据来提高性能。(会根据已有unit的类型和功能修改源代码中的load指令,因此可兼容性并不好)
    • 可配置的预取器(预取指令,可编程的fetchers等)
    • 隐式的非规则预取器,指针提取预取器(会预取指针所有可能指向的数据),提前执行机制(在cache访问发生miss时使用空闲的资源来进行预取)等基于硬件的执行时分析预取
    • 辅助线程:在编译过程中生成预执行线程,使用额外的core执行辅助线程
  4. 论文在硬件方面设计了一个可编程的预取器结构,结构图如下所示。

    预取流程为:

    • 所有main core发出的reads请求和所有预取到L1 cache的数据最初都会进入地址过滤器。经过过滤发现有价值的数据会被放入到观察队列(observation queue)中。
    • 当调度器(scheduler)发现有空闲的可编程预取单元(programmable prefetch unit, PPU)时,observation queue会弹出一个数据到PPU。PPU是一些低功率,按序执行的核,能够针对调度器提供的地址执行一些小的自定义的计算,生成0或多个预取地址。同时PPU也会使用load事件中的数据和全局寄存器中设定的状态,EWMA中计算的前瞻距离生成新的预取地址。这些生成的预取地址都会放入到预取请求队列中(prefetch request queue)
    • 当L1 cache有可用的MSHRs(miss status handing registers)时,将预取请求队列中请求取出,先检查MSHR中是否有该请求,如果有则取消,否则将该请求放入到MSHR中,并且发送给L2 cache进行预取。
      An Event-Triggered Programmable Prefetcher for Irregular Workloads (2018 asplos)_第1张图片
  5. 可编程预取器内部构成的具体介绍

    • 地址过滤器介绍(address filter)

      过滤器会监听所有来自main core的loads信息和预取到L1 cache的数据。过滤规则是人为设定的,例如监听hash表之类的。过滤器能够利用显式的main core执行的配置指令进行设定。配置信息都会存储在fiter table中,同时这个表中还会存储每种重要的数据结构的虚拟地址范围和两个指向小型计算内核的函数指针:Load Ptr(在load满足地址范围时执行),PF Ptr(在该范围内的预取完成之后执行)。过滤后的地址和两个指针(如果是观察预取的情况下还会包括预取的cache line)会被放入到观察队列中。

    • 观察队列和调度器(observation queue and scheduler)

      观察队列是一个简单的FIFO的buffer,用于存储过滤之后的地址等信息,知道PPU变为free。当该队列充满之后,前面的队列中的信息会被扔掉。

      一旦有可用的PPU,调度器会将数据的cache line和虚拟地址写入到PPU的寄存器中,然后设置PPU的程序计算器为当前观察所对应的kernel,之后便开始执行。

    • PPU(programmable prefetch units)

      PPU是一组按序、低功耗、可编程的RISC核,这些核和调度器相连,用于生成新的预取请求。这些核和main core具有相同的字长,从而能够在一条指令中执行地址运算。PPUs同时还带有一个共享的,多端口的指令cache。大多数应用程序所需的可编程预取代码量很小,因此指令高速缓存大小要求很小。PPU没有data cache,load或者是store单元,主要的延迟在于读取cache line和寄存器。

    • EWMA(Moving Average Calculators)

      EWMA主要用于确定在循环中预取的时机,从而尽可能的使得预取的数据正好被之后的指令使用。主要思想是将预取延迟除以循环中最短路径的执行时间,从而确定提前预取应该在哪一次迭代中开始。

      在实现过程中使用EWMA(exponentially weighted moving average calculators)。在执行过程中,动态的计算预取延迟和循环迭代的延迟的比值,用这个值来确定当前在数组中预取哪些值。

    • 预取请求队列(prefetch request queue)

      一个FIFO队列,用于存储PPU计算出的尚未被处理的虚拟地址。当L1 cache有可用的MSHR,队列中会将最先放入的值弹出,并且将其利用TLB转换为实地址。如果队列满了,则会将最初的值弹出队列

    • 内存请求标志(memory request tags)

      为了应对更多类型的数据结构,例如链表,图等,论文在MSHR中存储了一个tag,用于标注预取目标的数据结构的类型。这个tag对应于PPU中的函数指针,代表着不同的处理方式。

  6. PPU的编程模型:事件编程模型。因为预取指令的具有不同的延迟的特性比较适合使用事件进行驱动的编程模型。事件会生成新的预取而不是loads,并且在预取完成之后可以生成新的event。通过这种方式,PPU不会由于要等待预取完成而停顿。每一个事件可以有调度器选择在任意的PPU上执行,而不会被限定在某个特定的PPU上。

    PPU的编程模型受限于结构限制,因此没有提供访存功能和堆栈,PPU能获取到的数据仅来自于scheduler,EWMA,寄存器和单一的指令cache。

    由于prefetch并不会影响最后程序执行的结果,因此在多线程的切换时,所有PPUs停止执行。在PPU执行的过程中,如果出现了陷阱或者异常,PPU也会停止当前的事件。

  7. 编译支持

    • 使用深度优先搜索,从软件预取指令开始分析循环体中的指令序列,将序列拆分为以load为结尾的节点序列,之后再将其转换为PPU事件。
    • 数组的边界分析,在使用预取器的过程中必须首先获取每个数组的长度,并且存储在地址过滤器中,以保证不会产生越界访问。对于指针而言,确定边界的方法有两种:1. 从指针使用开始向前搜索,直到找到指针的分配语句;2. 使用循环的边界作为数组的长度
    • 生成代码:插入预取配置指令,生成PPU代码,并且移除原始的预取指令
    • Pragma预取:手工设置,用于设置初始的软件预取,然后再使用深度优先搜索生成事件。直接的选择是简单地指示需要预取的循环,并让编译器从头开始生成预取事件。

你可能感兴趣的:(computer,architecture,Irregular,Workloads,programmable,prefetcher,design)