这是一个很基础的计算斐波那契额数列的算法,用递归实现的:
int fib(int n) {
if (n < 2)
return n;
int first = fib(n - 1);
int second = fib(n - 2);
return first + second;
}
int main() {
TICK(fib);
std::cout << fib(39) << std::endl;
TOCK(fib);
return 0;
}
在这里分别使用了两种算法,第一种是串行的,第二种是cut-off并行的(就是当数据较小的时候采用串行,数据较大的地方采用并行)。
封装好的排序库:tbb::parallel_sort
int main() {
size_t n = 1 << 20;
std::vector<int> arr(n);
std::generate(arr.begin(), arr.end(), std::rand);
TICK(sort);
/*std::cout << serial_fib(39) << std::endl;*/
tbb::parallel_sort(arr.begin(), arr.end(), std::less<int>{});
TOCK(sort);
return 0;
}
流水线并行:
加入要对上面的数据进行并行操作,可能一下子就会想到parallel_for_each这种简单粗暴的方法。
但这种加速效果并不是很好,这是因为循环体太大,每跑一遍都要重置指令缓存和数据缓存。而且每个核心都在读写不同地方的数据,导致内存瓶颈,效果很不好。
tbb采用了一种流水线并行的策略:tbb::parallel_pipeline
像是这一种,那么在这里当t4处理d1的s4的时候,t3正在处理d2的s3。
#include <iostream>
#include <cstdlib>
#include <vector>
#include <cmath>
#include <numeric>
#include "ticktock.h"
#include <tbb/parallel_pipeline.h>
struct Data {
std::vector<float> arr;
Data() {
arr.resize(std::rand() % 100 * 500 + 10000);
for (int i = 0; i < arr.size(); i++) {
arr[i] = std::rand() * (1.f / (float)RAND_MAX);
}
}
void step1() {
for (int i = 0; i < arr.size(); i++) {
arr[i] += 3.14f;
}
}
void step2() {
std::vector<float> tmp(arr.size());
for (int i = 1; i < arr.size() - 1; i++) {
tmp[i] = arr[i - 1] + arr[i] + arr[i + 1];
}
std::swap(tmp, arr);
}
void step3() {
for (int i = 0; i < arr.size(); i++) {
arr[i] = std::sqrt(std::abs(arr[i]));
}
}
void step4() {
std::vector<float> tmp(arr.size());
for (int i = 1; i < arr.size() - 1; i++) {
tmp[i] = arr[i - 1] - 2 * arr[i] + arr[i + 1];
}
std::swap(tmp, arr);
}
};
int main() {
size_t n = 1 << 11;
std::vector<Data> dats(n);
std::vector<float> result;
TICK(process);
auto it = dats.begin();
tbb::parallel_pipeline(8,
tbb::make_filter<void, Data*>(tbb::filter_mode::serial_in_order, [&](tbb::flow_control& fc)->Data* {
if (it == dats.end()){
fc.stop();
return nullptr;
}
return &*it++;
}),
tbb::make_filter<Data*, Data*>(tbb::filter_mode::parallel, [&](Data* dat) -> Data* {
dat->step1();
return dat;
}),
tbb::make_filter<Data*, Data*>(tbb::filter_mode::parallel, [&](Data* dat) -> Data* {
dat->step2();
return dat;
}),
tbb::make_filter<Data*, Data*>(tbb::filter_mode::parallel, [&](Data* dat) -> Data* {
dat->step3();
return dat;
}),
tbb::make_filter<Data*, float>(tbb::filter_mode::parallel,[&](Data* dat) -> float {
float sum = std::reduce(dat->arr.begin(), dat->arr.end());
return sum;
}),
tbb::make_filter<float, void>(tbb::filter_mode::serial_out_of_order,[&](float sum) -> void {
result.push_back(sum);
})
);
TOCK(process);
return 0;
}
tbb::make_filter<void, Data *>(tbb::filter_mode::serial_in_order, ...):这是一个函数调用,用于创建一个过滤器。它指定了过滤器的输入类型为void,表示没有输入数据,输出类型为Data*,表示输出为指向Data类型对象的指针。tbb::filter_mode::serial_in_order表示过滤器以串行顺序的方式执行。
[&] (tbb::flow_control &fc) -> Data * {:这是一个lambda表达式,定义了过滤器的功能。[&]表示使用lambda表达式中可见的所有变量(包括引用)。
if (it == dats.end()) {:这是一个条件语句,用于检查是否遍历完数据。it是一个迭代器,指向dats容器的当前元素。dats.end()是dats容器的结束迭代器,用于表示遍历结束。如果it等于dats.end(),即已经遍历完数据,则执行下面的代码块。
fc.stop();:这是一个流程控制函数,用于停止管道的执行。当遍历完数据时,调用fc.stop()停止后续过滤器的执行。
return nullptr;:如果遍历完数据,返回nullptr表示没有输出数据。
return &*it++;:如果尚未遍历完数据,执行这行代码。*it表示迭代器it指向的当前元素,it++将迭代器向前移动一位。&*it表示当前元素的地址,即指向Data类型对象的指针。该指针将作为过滤器的输出数据。
这段代码的功能是在并行管道中依次遍历dats容器的元素,并将每个元素作为输出数据传递给下一个过滤器。当遍历完所有元素时,通过调用fc.stop()停止管道的执行,并返回nullptr表示没有输出数据。
这个和cuda里的stream有点像。