目标:

  • 负载均衡(本节)
  • 减少由并行带来的额外工作overhead(本节)
  • 减少通信成本

负载均衡

所有处理器同时起步、同时结束

任务分配方式

  • static:可预计任务时间和数量
  • semi-static:短期内可预计任务时间和数量,阶段性调整任务安排
  • dynamic:不可预计任务时间和数量,使用共享工作队列(线程池)

动态任务分配

单一队列

  • 任务颗粒度:任务过小时,任务间切换时间(或lock等待时间)过长,导致整体性能下降;可以增加颗粒度,让一个任务串行执行一系列任务。
  • 任务数量的权衡:任务数量增加,导致颗粒度减小,但能使动态分配负载更均衡。
  • 长任务:先安排,需要关于负载的预测

分布式队列(简单介绍)

  • 定义:每个线程对应一个队列,线程从自己的队列里取任务,或者从其他队列里“偷”(当队列为空)
  • 优点:避免多个线程访问单一队列的同步锁问题
  • 额外问题:
    • “偷”时引起额外的同步、通信,引起负载不均衡,只在必要时进行
    • 局部性:自己产生-消费

队列中任务可以存在依赖性,只有所有依赖条件被满足,任务才能被取出(CUDA可以设置依赖性??)

foo_handle = enqueue_task(foo);                 // enqueue task foo (independent of all prior tasks)
bar_handle = enqueue_task(bar, foo_handle);     // enqueue task bar, cannot run until foo is complete

fork-join并行模式

cilk-plus:C++语言拓展,GCC、Intel ICC支持

基本用法

  • cilk_spawn:实例与主程序异步执行
  • cilk_sync:等待所有展开实例结束
cilk_spawn foo();	
cilk_spawn bar();	
cilk_spawn fizz();	
buzz();	
cilk_sync;

例子:并行快排

void quick_sort(int* begin, int* end) {	
    if (begin >= end - PARALLEL_CUTOFF)	
        std::sort(begin, end);
    else {
        int* middle = partition(begin, end);
        cilk_spawn quick_sort(begin, middle);
        quick_sort(middle+1, last);
    }	
}

内部实现

  • pthread实现
    • 用pthread_create实现存在的问题:
      • 展开操作开销大
      • 过多的实例(远大于核数,很多并发线程,导致上下文切换开销,缺少缓存局部性)
    • 实际实现
      • 线程池,在第一次展开时创建所有线程
  • 基本队列结构
    • 队列中任务顺序:子任务放底部(优先处理),当前任务放顶部(供“偷取”),原因见下一条
    • 双端队列,当前线程从底部取任务,其他线程从顶部取任务(1. 当前线程和其他“偷取”线程不会同时操作队列中的相同元素;2. 队列顶部任务处于递归树的顶部,是“大”任务,需要优先处理;3. 最大化局部性,让当前线程优先处理当前队列中任务)
    • 任意性:空闲线程从任意线程队列“偷取”任务
    • 贪婪性:任意队列为空,线程就会“偷取”,而不是等待
    • cilk_sync结束时同步信号
      • 不存在“偷取”时,可以直接结束,不需要同步
      • 每次出现“偷取”时,会记录有多少偷取,已完成多少,当“偷取”等于已完成时,表示整个流程结束

利用子任务优先处理的机制,写更高效的代码

for (int i=0; i<N; i++) {
    cilk_spawn foo(i);
}
cilk_sync;

// 并行生成子任务
void recursive_for(int start, int end) {	
    while (start <= end - GRANULARITY){
        int mid = (end - start) / 2;	
        cilk_spawn recursive_for(start, mid);	
        start = mid;	
    }
    for (int i=start; i<end; i++)
        foo(i);
}	
recursive_for(0, N);

ShengYg

Step after step the ladder is ascended.


Tags •