C++并行多线程归并排序

 下面是源代码

template<typename Iterator>
void F(Iterator start, Iterator end) {
	std::sort(start, end);
}
template <typename Iterator>
void merge(Iterator start, int length, int size)
{
	for (int i = 1; i < size; i++)
		std::inplace_merge(start, start + length * i, start + length * (i + 1));
}
template<typename Iterator>
void Sort_(Iterator start, Iterator end) {
	const size_t length = std::distance(start, end);
	size_t max_thread = std::min(length / 100, std::thread::hardware_concurrency());
	if (!max_thread)
		max_thread = 1;
	const size_t size = length / max_thread;
	std::vector<std::thread>threads;
	Iterator block_start = start;

	for (size_t i = 0; i < (max_thread - 1); i++) {
		Iterator block_end = block_start;
		std::advance(block_end, size);
		threads.emplace_back(F<Iterator>, block_start, block_end);
		block_start = block_end;
	}
	F(block_start, end);
	for (auto& i : threads)
		i.join();
	merge(start, size, max_thread);
}

 一直看书啥的最近人都有点迷糊,我们直接看代码吧,逻辑还是很简单

1、编写一个模板函数,调用algorithm的std::sort进行排序,其参数是两个迭代器,我们调用sort只是为了方便,想要自己写一个归并排序自然也可以,但是没有必要

2、写一个合并算法,因为我们想的是把一段很长的序列分成一段一段小的序列,用线程调用进行排序,那么最后线程排序完后就是一段一段有序的序列,所以需要一个模板函数进行合并。我们调用了algorithm的std::inplace_merge进行合并,函数的参数是:开始迭代器,每一项有序子序列的元素个数,有序子序列的数量,然后进行一个循环即可完成重复调用合并。std::inplace_merge基础使用方式如下

#include <iostream>    
#include <algorithm>  
int main() {
    //该数组中存储有 2 个有序序列
    int first[] = { 5,10,15,20,25,7,17,27,37,47,57 };
    //将 [first,first+5) 和 [first+5,first+11) 合并为 1 个有序序列。
    std::inplace_merge(first, first + 5, first + 11);
    for (int i = 0; i < 11; i++) {
        std::cout << first[i] << " ";
    }
    std::cout << std::endl;
    int first_[]{ 57,47,37,27,17,10,9,8,7,6,5,4 };
    std::inplace_merge(first_, first_ + 5, first_ + 11, [](auto a, auto b) {return a > b; });
    for (auto i : first_)
        std::cout << i << ' ';
    return 0;
}
//只能处理有序序列,且有序是指如果要按照从小到大,那么这个数组的两个序列必须也是分别从小到大

3、进入正题,Sort_函数模板接收两个迭代器作为参数,调用std::distance算法计算两个迭代器的距离。max_thread为最大线程数,我们取距离即元素个数/100,和设备支持的最大线程数的其中更小的,线程并不是越多越好,也有开销,而且不能超过设备所支持的最大数。

if这里为了防止元素个数不足100,导致为0的情况,所以赋值1

4、size = length / max_thread,size是单个线程一次处理的元素数量,也就是长度/线程数

std::vector<std::thread>threads;
    Iterator block_start = start;

创建线程容器与开始迭代器,开始迭代器初始化为传入参数的start。

5、

for (size_t i = 0; i < (max_thread - 1); i++) {
        Iterator block_end = block_start;
        std::advance(block_end, size);
        threads.emplace_back(F<Iterator>, block_start, block_end);
        block_start = block_end;
    }

循环,先创建结束迭代器让开始迭代器进行初始化,然后使用std::advance对迭代器进行移动,移动的距离就是size,即线程一次处理的范围,vector容器进行匿名构造thread插入。

最后开始迭代器被赋值为结束迭代器,以此类推循环。

6、循环条件之所以是最大线程数-1是因为主线程也是线程,也进行计算。循环结束后主线程调用F进行排序:F(block_start, end);,即开始迭代器和末尾,也就是把最后一部分数据留给主线程计算,到此整个序列全部变为一个一个有序的子序列,此时就可以调用join成员函数确保其他线程正常退出,然后调用函数进行合并

for (auto& i : threads)
        i.join();
 merge(start, size, max_thread);

如此便完成了

测试时间效率代码

class auto_timer {
	std::chrono::system_clock::time_point start;

public:
	// start record when entering scope
	explicit auto_timer(const char* task_name = nullptr) {
		if (task_name) std::cout << task_name << " running , ";
		start = std::chrono::system_clock::now();
	}

	// end record when leaving scope
	~auto_timer() {
		auto cost = std::chrono::duration_cast<std::chrono::nanoseconds>(std::chrono::system_clock::now() - start);
		std::cout << "cost: " << double(cost.count()) / 1000000.0 << " ms" << std::endl;
	}
};

加上对照打印测试代码,如下:

#include<iostream>
#include<thread>
#include<mutex>
#include<vector>
#include<algorithm>
#include <chrono>
class auto_timer {
	std::chrono::system_clock::time_point start;

public:
	// start record when entering scope
	explicit auto_timer(const char* task_name = nullptr) {
		if (task_name) std::cout << task_name << " running , ";
		start = std::chrono::system_clock::now();
	}

	// end record when leaving scope
	~auto_timer() {
		auto cost = std::chrono::duration_cast<std::chrono::nanoseconds>(std::chrono::system_clock::now() - start);
		std::cout << "cost: " << double(cost.count()) / 1000000.0 << " ms" << std::endl;
	}
};

template<typename Iterator>
void F(Iterator start, Iterator end) {
	std::sort(start, end);
}
template <typename Iterator>
void merge(Iterator start, int length, int size)
{
	for (int i = 1; i < size; i++)
		std::inplace_merge(start, start + length * i, start + length * (i + 1));
}
template<typename Iterator>
void Sort_(Iterator start, Iterator end) {
	const size_t length = std::distance(start, end);
	size_t max_thread = std::min(length / 100, std::thread::hardware_concurrency());
	if (!max_thread)
		max_thread = 1;
	const size_t size = length / max_thread;
	std::vector<std::thread>threads;
	Iterator block_start = start;

	for (size_t i = 0; i < (max_thread - 1); i++) {
		Iterator block_end = block_start;
		std::advance(block_end, size);
		threads.emplace_back(F<Iterator>, block_start, block_end);
		block_start = block_end;
	}
	F(block_start, end);
	for (auto& i : threads)
		i.join();
	merge(start, size, max_thread);
}
template<class T>
void Sort(std::vector<T>& data)
{
	const intptr_t size = data.size();
	intptr_t stride = 2048;

	//从stride = 1开始归并排序非常缓慢
	//因此这里对data进行初排序
	//从一个较大的stride开始归并排序
	if (stride != 1) {
#pragma omp parallel for schedule(dynamic, 2048 / stride + 1)
		for (intptr_t i = 0; i < size; i += stride) {
			auto left = data.begin() + i;
			auto right = i + stride < size ? data.begin() + i + stride : data.end();
			std::sort(left, right);
		}
	}

	//并行归并排序
#pragma omp parallel
	{
		intptr_t _stride = stride;
		do
		{
			_stride *= 2;

#pragma omp for schedule(dynamic, 2048 / _stride + 1)
			for (intptr_t i = 0; i < size; i += _stride) {
				//对[i, i+_stride/2)和[i+_stride/2, i+_stride)进行归并
				auto left = data.begin() + i;
				auto mid = (i + i + _stride) / 2 < size ? data.begin() + (i + i + _stride) / 2 : data.end();
				auto right = i + _stride < size ? data.begin() + i + _stride : data.end();
				inplace_merge(left, mid, right);
			}
		} while (_stride < size);
	}
}
int main() {
	srand(time(0));
	std::vector<int>V;
	for (int i = 10000000; i > 0; i--)
		V.push_back(rand());
	/*for (auto i : V)
		std::cout << i << ' ';*/
		//std::cout << std::endl;

	{
		auto_timer timer("并行排序");
		Sort_(V.begin(), V.end());
	}

	std::vector<int>V2;
	for (int i = 10000000; i > 0; i--)
		V2.push_back(rand());
	{
		auto_timer timer("库排序");
		std::sort(V2.begin(), V2.end());
	}
	std::vector<int>V3;
	for (int i = 10000000; i > 0; i--)
		V3.push_back(rand());
	{
		auto_timer timer("并行归并");
		Sort(V3);
	}
	std::vector<int>V4{ 10,9,8,7,6,5,4,3,2,1 };
	{
		{
			auto_timer timer("并行排序");
			Sort_(V4.begin(), V4.end());
			for (auto i : V4)
				std::cout << i << ' ';
		}
	}
	/*for (auto i : V)
		std::cout << i << ' ';*/
}

其中一个归并并行的代码是复制的网上的做对照使用

顺带提一句测试时间的使用方式

{
     auto_timer timer("任务名");
    // 耗时代码

}

如有错误还请指正,MSVC,C++20,release

  • 3
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
### 回答1: 归并排序是一种经典的排序算法,它的核心思想是将待排序的数组不断拆分成两个子数组,直到拆分为只包含一个元素的子数组,然后再将这些子数组合并成一个有序的数组。 在归并排序排序阶段,可以利用多线程的特性进行并行处理。当需要将数组拆分成两个子数组时,可以同时创建两个线程来处理这两个子数组的排序操作。这样可以减少排序的时间复杂度,提高整体的排序速度。 在归并排序归并阶段,同样可以利用多线程来进行并行处理。当需要将两个有序的子数组合并时,可以创建多个线程来同时处理这些子数组的合并操作。这样可以将整个归并过程分成多个小任务,各个线程独立地进行合并操作,最后再将结果合并成一个有序的数组。 通过在排序阶段和归并阶段都使用多线程,可以充分利用计算机的多核处理器并行处理能力,加快归并排序的执行速度。但是需要注意的是,多线程并不总是能够带来性能的提升,需要合理地调度线程和任务,避免线程之间的竞争和冲突,才能发挥出多线程的优势。 综上所述,归并排序排序阶段和归并阶段都可以使用多线程来提高排序效率,但需要注意线程之间的协调和调度,使得多线程能够充分发挥优势,最终得到正确的排序结果。 ### 回答2: 归并排序是一种经典的排序算法,可以通过多线程来提高其执行效率。在排序阶段和归并阶段都可以使用多线程来进行加速。 在排序阶段,归并排序将待排序的序列分成两个子序列,然后对这两个子序列进行排序。如果使用多线程,可以将分治算法中的分解操作交给不同的线程来处理。每个线程负责对一个子序列进行排序,从而同时进行多个子序列的排序。这样可以大大缩短排序时间,提高效率。 在归并阶段,归并排序将已排序的子序列进行合并。同样可以使用多线程来加速合并操作。可以将待合并的子序列分成多个部分,每个线程负责合并其中的一部分。通过并行地合并多个子序列,可以有效地减少合并时间。 需要注意的是,在多线程的情况下,需要合理地划分任务和数据,实现合理的负载均衡。如果划分不当,可能导致线程之间的竞争和同步开销,反而影响排序效率。因此,在实际应用中,需要综合考虑硬件环境、任务规模和数据分布等因素,来确定最适合的多线程策略。 总之,归并排序可以利用多线程在排序阶段和归并阶段进行并行计算,从而提高算法的执行效率。合理的多线程策略可以充分利用硬件资源,加速排序过程,提高排序效率。 ### 回答3: 归并排序是一种排序算法,通过将待排序的数组分成两个子数组,对每个子数组进行排序,然后再将已排序的子数组通过归并操作合并成一个有序的数组。在排序阶段和归并阶段都可以使用多线程来加速排序过程。 在排序阶段,可以将原始数组平均分成多个子数组,每个子数组由一个线程来排序。因为归并排序是递归算法,可以将排序过程按照树状结构分解成多个小任务,每个线程负责处理一个任务。在处理每个任务时,线程可以独立对该子数组进行排序,不需要等待其他线程的结果,从而提高排序效率。 在归并阶段,多个已排序的子数组可以并行地进行归并操作。可以使用多个线程将已排序的子数组两两归并成更大的有序子数组,然后再将这些有序子数组两两归并,直到最终得到一个完全有序的数组。多个线程可以并行地执行归并操作,每个线程独立负责一部分子数组的归并,而不需要等待其他线程的结果,从而加快整个数组的归并速度。 通过在排序阶段和归并阶段都使用多线程,归并排序可以充分利用多核处理器的计算能力,提高排序的速度。然而,使用多线程也需要注意线程间的同步和数据访问冲突问题,以及线程的创建和销毁开销等。因此,在实际应用中,需要综合考虑算法的复杂度和数据规模,选择合适的线程数以及线程的任务分配策略,以达到最佳的排序性能。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值