在排序算法中,我们学习了很多的排序算法,对于这些排序算法,所能够做到的便是 O ( n l o g n ) O(nlogn) O(nlogn)为最佳复杂度了,那么,很多算法还是串行化的,单线程的算法,现在,我们是多线程的机器,我们可以对算法做并行化处理,使其可以更快的解出答案
以往单机器,小数据量的排序,我们可以使用内存排序迅速给算出来答案,可以,现在的排序即使你内存装得下,排序也慢了许多倍,即使是 l o g n logn logn里面的 n n n变得很大之后,效率任然会变慢
归并排序
归并排序是一种非常经典的排序算法,基于分治的思想,去解决子问题,然后合并子问题的解,来解决问题
归并排序的时间复杂度是 O ( n l o g n ) O(nlogn) O(nlogn)这个是老生常谈的了。
归并排序形如这种:
const int N = 1e5 + 111;
i64 g[N];
void merge_(int l, int mid, int r) {
int idx = 0, x = l, y = mid + 1;
std::vector<int> t(r - l + 2);
while (x <= mid && y <= r) {
if (g[x] > g[y])
t[++idx] = g[y++];
else
t[++idx] = g[x++];
}
while (x <= mid)
t[++idx] = g[x++];
while (y <= r)
t[++idx] = g[y++];
for (int i = 1; i <= idx; i++)
g[l++] = t[i];
}
void merge_B(int l, int r) {
if (l >= r)
return;
int mid = (l + r) >> 1;
merge_B(l, mid);
merge_B(mid + 1, r);
merge_(l, mid, r);
}
其中,归并排序天然适合并行化操作,因为在分治的过程中,子问题是独立求解的,并不会去影响其他区域
并且,在合并过程中,也算线程逐步向上走的一个过程
插曲,如何并行化处理,并拿到值
在C++中,C++11之后我们可以利用future和promise来进行异步操作拿值和设置值。
然后借用thread进行并行化操作。
C++也为我们提供了一个简洁的接口
template< class Function, class... Args>
std::future<std::result_of_t<std::decay_t<Function>(std::decay_t<Args>...)>>
async( Function&& f, Args&&... args );
可以使用其进行便捷的异步编写
由于我们的操作是通过新建线程来完成的,C++的线程开销还是比较多,所以,对于归并排序里面小块内存,不见得继续并行化,会有好的结构,不如设定一个阈值,在阈值之上才进行异步操作
改造如下:
void merge_A(int l, int r) {
if (l >= r)
return;
int mid = (l + r) >> 1;
if (r - l > 1000) {
auto t1 = std::async(std::launch::async, merge_A, l, mid);
auto t2 = std::async(std::launch::async, merge_A, mid + 1, r);
t1.wait();
t2.wait();
} else {
merge_A(l, mid);
merge_A(mid + 1, r);
}
merge_(l, mid, r);
}
就仅仅对递归的子问题开到了新的线程来达到并行的处理
测试
下面,利于随机数据来测试一下加于不加的区别:
测试数据为随机数据,测试计算器为chrono
{
auto t = std::chrono::system_clock::now();
merge_A(0, N - 1);
std::cout << std::is_sorted(g, g + N) << "\n"
<< std::chrono::duration_cast<std::chrono::milliseconds>(
(std::chrono::system_clock::now() - t))
.count()
<< std::endl;
}
数据量\运行时间(ms) | 1e6 | 1e7 | 1e8 |
---|---|---|---|
非并行 | 336 | 3651 | 41171 |
并行 | 133 | 1305 | 11438 |
基本上来说的话,并行算法还是快上非并行算法几倍的
总结
在日益增长的数据量的爆炸之下,对于多核处理器来说,我们应该尽可能的多压栈机器多个核的性能,当然,也不是绝对的,这个是临界区也有关系,若是临界区导致的数据竞争比较大,有时候可能并不那么的优秀。
但是对于可以分治的问题来说,一般不存在这种问题,可以更高效的处理大数据。
[amjieker]