使用OpenMP实现并行归并排序(Report)

从std::sort到TB级并行排序:C++高性能排序算法实战指南 排序算法是计算机科学的基础核心概念,其本质是通过特定规则对数据元素进行重新排列。从时间复杂度与空间复杂度的理论分析,到内存访问模式、缓存友好性等底层原理,排序算法的选择直接影响程序性能。在工程实践中,排序算法的技术价值在于高效处理海量数据,支撑大数据分析、数据库索引、科学计算等关键应用场景。随着数据规模从GB级扩展到TB级,单线程std::sort已无法满足需求,必须结合多线程并行、分布式计算及内存层次优化等策略。本文聚焦基数排序、并行归并排序等高效算法,通过分析数据特征与硬件特性,提供从单机到分布式的完整 阅读详情

归并排序算法:

归并排序算法是一种经典的分治算法。

分治

分治算法分为由三部分组成:
分解:将原问题分解为一系列子问题;
解决:递归的解决各个子问题。若子问题足够小,那么直接求解。
合并:将子问题的结果合并成原问题。

归并排序步骤

归并排序完全依照了上述模式,直观的操作如下:
分解:将n个元素分成各含n/2个元素的子序列;
解决:用合并排序法对两个子序列递归地排序;
合并:合并两个已经排序的子序列,已得到排序结果。
这里递归的边界是序列长度为1时,显然是有序的。

合并过程

这里最关键的步骤,是合并步骤里如何合并两个有序的序列,并保证合并后的序列依然有序。

假设有序的序列为递增的,A、B为需要合并的序列,C为合并后的结果序列,p、q分别为A和B的下标,top为C的下标。定义如果一个下标大于序列的长度后,表示的值为无穷大。

初始状态:p、q、top均为0.

操作:选择A[p]和B[q]中的小的元素,加入到C[top]中,然后让较小的元素所在的序列的下标加一,top加一。当A[p]和B[q]均为无穷大时,结束操作。

由于每次操作均是比较A[p]和B[q],然后取较小者加入C中,显然时间复杂度是O(n)的。

归并排序时间复杂度分析:

假设归并排序一个长度为n的序列需要的时间为T(n)。
首先归并排序分如下三个步骤:
分解:这一步是把序列分为两个子序列,只需要常量时间,O(1);
解决:递归的解决规模为n/2的两个子问题,时间为2*T(n/2);
合并:上面已经证明,只需时间O(n)。

那么接下来可以UI递归的表示出所需的时间T(n):
当n = 1是,T(n) = O(1);
否则:T(n) = 2*T(n/2) + O(n)。

可以证明出上述的T(n)其实就是O(n*log(n))。

T(n) = 2*T(n/2) + O(n)
= 2*(2*T(n/4) + O(n/2) + O(n)
= 4*T(n/4) + 2*O(n/2) + O(n)
= 4*T(n/4) + 2*O(n)
= 8*T(n/8) + 8*O(n/8) + 2*O(n)
= 8*T(n/8) + 3*O(n)
= x*T(1) + y*O(n)

显然y即为n除多少次才为1,y = log2(n),x等于2^y,那么T(n) = O(n*log(n))。

一个容易理解的代码:

Python is very beautiful!

def merge_sort(array):
    if len(array) > 1:
        mid = len(array) / 2
        left = merge_sort(array[:mid])
        right = merge_sort(array[mid:])
        return merge(left, right)
    return array


def merge(left, right):
    rst = []
    while len(left) > 0 or len(right) > 0:
        if len(right) == 0 or len(left) != 0 and left[0] < right[0]:
            rst.append(left.pop(0))
        else:
            rst.append(right.pop(0))
    return rst

串行过程:

串行排序代码:

void merge_sort(int *A, int x, int y, int *T) {
    if (y - x > 1) {
        int m = x + (y-x)/2;
        int p = x, q = m, i = x;
        merge_sort(A, x, m, T);
        merge_sort(A, m, y, T);

        while (p < m || q < y) {
            if (q >= y || (p < m && A[p] <= A[q])) {
                T[i++] = A[p++];
            }
            else {
                T[i++] = A[q++];
            }
        }
        for (i = x; i < y; i++) {
            A[i] = T[i];
        }
    }
}

串行求和代码:

int get_sum(int* data, int N) {
    int sum = 0, i;
    for (i = 0; i < N; i++) {
        sum += data[i];
    }
    return sum;
}

运行时间:

num_elementssort timesum time
1000.0000120.000001
10000.0001660.000005
100000.0021620.000045
1000000.0229150.000384
10000000.2160750.003397
100000002.4045430.034109
10000000027.2043180.340051

ignore the input time.

并行过程:

归并排序算法的并行化:

首先,归并排序的步骤分为已下三步:

分解:将n个元素分成各含n/2个元素的子序列;
解决:用合并排序法对两个子序列递归地排序;
合并:合并两个已经排序的子序列,已得到排序结果。

然后发现,按照这个思路很难并行化,因为许多过程有依赖的,比如当[1, 1], [2, 2] 区间没有合并之前,那么[1, 2], [3, 4]区间是不能进行合并的。

但是我们可以把归并的步骤反过来。原来归并是要不断的分解一个序列,直到分解成长度为1的区间,最后依次合并。我们现在假设有N个区间,要分别合并,最后合并成一个区间。那么我现在的操作是没有前后依赖的,对于任意两个区间,只需要合并就好,不用考虑其他的线程。

这样排序的过程就类似一颗线段树(严格的来讲并不是),自底向上的不断合并。

这里写图片描述

排序代码:

//合并两个区间
void merge(int l1, int r1, int r2, int* data, int* temp) {
    int top = l1, p = l1, q = r1;
    while (p < r1 || q < r2) {
        if (q >= r2 || (p < r1 && data[p] <= data[q])) {
            temp[top++] = data[p++];
        }
        else {
            temp[top++] = data[q++];
        }
    }
    for (top = l1; top < r2; top++) {
        data[top] = temp[top];
    }
}

void merge_sort(int l, int r, int* data, int N) {
    int i, j, t, *temp;
    temp = (int*)malloc(N * sizeof(int));
    //这里做了一些优化,预处理合并了单个的区间,略微提高的速度
    #pragma omp parallel for private(i, t) shared(N, data)
    for (i = 0; i < N/2; i++)
        if (data[i*2] > data[i*2+1]) {
            t = data[i*2];
            data[i*2] = data[i*2+1];
            data[i*2+1] = t;
        }

    //i代表每次归并的区间长度,j代表需要归并的两个区间中最小的下标
    for (i = 2; i < r; i *= 2) {
        #pragma omp parallel for private(j) shared(r, i)
        for (j = 0; j < r-i; j += i*2) {
            merge(j, j+i, (j+i*2 < r ? j+i*2 : r), data, temp);
        }
    }
}

求和代码:

int get_sum(int* data, int N) {
    int sum = 0, i;
    #pragma omp parallel for private(i) reduct(+:sum)
    for (i = 0; i < N; i++) {
        sum += data[i];
    }
    return sum;
}

运行时间:

num_elementssort timesum time
1000.0001640.000009
10000.0002090.000009
100000.0023180.000052
1000000.0105890.000166
10000000.1100900.001279
100000001.0935720.013541
10000000011.8724080.127646

ignore the input time.

运行时间分析:

排序时间对比:

这里写图片描述

求和时间对比:

这里写图片描述

结论:

增加线程数在是可以加快程序的运行速度的,但是随着线程的增加,加速的效果逐渐变得不明显,双线程与单线程的差异较大,整体上多线程的用时为单线程的一半。

C语言多线程编程进阶:线程池、同步模式与性能优化实战 多线程编程是现代软件开发中提升性能的核心技术,它通过并发执行任务来充分利用多核CPU的计算能力。其基本原理是创建多个执行流,共享进程资源,但需要同步机制来协调对共享数据的访问,以避免数据竞争。这项技术的价值在于能够显著提高计算密集型或I/O密集型应用的吞吐量和响应速度,广泛应用于服务器开发、科学计算、数据处理等场景。本文以C语言pthreads库为基础,深入探讨线程池的实现原理,它通过复用线程来避免频繁创建销毁的开销,并结合生产者-消费者模式管理任务队列。文章进一步分析了映射-归约等并行算法模式,并针对性能 阅读详情

相关推荐

C++编程精髓:从核心语法到性能优化的现代工程实践指南

在计算机编程领域,内存管理和性能优化是构建高效系统的核心基础。理解内存分配、缓存机制和多线程同步等底层原理,对于开发操作系统、游戏引擎和高频交易系统等高性能应用至关重要。C++语言以其对硬件的直接控制能力和零开销抽象原则,成为实现极致性能的首选工具。通过智能指针、移动语义和RAII等现代特性,开发者可以编写更安全、高效的代码,同时利用模板元编程和编译期优化进一步提升执行效率。这些技术广泛应用于需要精细控制资源的场景,如并发网络服务器和实时数据处理系统,体现了C++在工程实践中的独特价值。

weixin_30731305的博客 426

list按照某个字段排序_「八大排序算法」16张图带你搞懂基数排序

首发公众号:bigsai 头条号:程序员bigsai 转载需联系授权前言在排序算法中,大家可能对桶排序、计数排序、基数排序不太了解,不太清楚其算法地思想和流程,也可能看过会过但是很快就忘记了,但是不要紧,幸运的是你看到了本篇文章。本文将通俗易懂的给你讲解基数排序。基数排序,是一种原理简单,但实现复杂的排序。很多人在学习基数排序的时候可能会遇到以下两种情况而浅尝辄止:一看原理,这么简单,懂了懂了(顺...

weixin_39739846的博客 283

C++代码优化实战:从编译器选项到缓存友好的性能提升策略

在软件工程实践中,代码优化是提升程序执行效率、降低资源消耗的核心技术。其原理在于通过编译器智能分析和代码结构调整,减少冗余计算、改善内存访问模式,从而充分发挥硬件潜力。这项技术的价值在于,它能让应用程序在嵌入式设备、高并发服务器、游戏引擎及高频交易等对性能有严苛要求的场景中,满足低延迟、高吞吐的关键需求。特别是在处理海量数据或实时系统时,有效的优化策略直接决定了产品的竞争力与用户体验。本文聚焦于C++这一高性能编程语言,深入探讨了包括编译器优化选项、链接时优化(LTO)在内的关键技术,旨在为开发者提供一套从

weixin_34265814的博客 656

排序算法

排序算法总结排序算法的理解1. 冒泡排序2. 选择排序3. 插入排序4. 希尔排序5. 归并排序6. 快速排序7. 计数排序8. 堆排序9. 基数排序10. 桶排序 排序算法的理解 现在家里事情不多,就总结了一下排序算法,以后找工作时,也好使用,主要使用python3实现的。大家有兴趣可以自己实现。 1. 冒泡排序       &nbs...

卡卡西的博客 425

看动画学算法之:排序-归并排序

文章目录简介归并排序的例子归并排序算法思想归并排序的java实现归并排序的时间复杂度 简介 归并排序简称Merge sort是一种递归思想的排序算法。这个算法的思路就是将要排序的数组分成很多小的部分,直到这些小的部分都是已排序的数组为止(只有一个元素的数组)。 然后将这些排序过的数组两两合并起来,组成一个更大一点的数组。接着将这些大一点的合并过的数组再继续合并,直到排序完整个数组为止。 归并排序的例子 假如我们有一个数组:29,10,14,37,20,25,44,15,怎么对它进行归并排序呢? 先看一个动画

程序那些事 1万+

【信息科学与工程学】计算机科学与自动化——第八十四篇 C++分布式软件高并发/高可用算法01

4. 提交:协调者选择提交时间戳(>所有读时间戳),两阶段提交:a) 写意图预提交到所有参与者,b) 参与者持久化后确认,c) 协调者决定提交,异步清理意图。:客户端请求发送到协调节点,协调节点根据路由转发到主分片所在节点。Span有父子关系。1. 索引文档:客户端发送PUT请求到协调节点,协调节点路由到主分片节点,主分片写入本地,然后并行复制到副本分片,等待确认后响应客户端。3. 搜索:客户端发送搜索请求到协调节点,协调节点广播到所有相关分片,每个分片执行查询,返回结果,协调节点合并、排序、分页后返回。

weixin_49199313的博客 1077

排序算法总结(二)

归并排序算法思想是分而治之。下面将对分而治之算法排序做一个简单描述。 //归并排序之分而治之 void sort(E, n) { //对E中的n个元素排序 if(n >= k) { i = n / k; j = n - I; 令A由E的前i个元素组成; 令B由E的后j个元素组成; sort(A, i);

AlphaGQ 437

OpenMP 下实现的快速排序(c语言代码)

并行(OpenMp)快速排序代码(c语言),里面可以统计执行的时间,估计并行效率。

OpenMP并行程序设计(一)

 OpenMP并行程序设计(一) OpenMP是一个支持共享存储并行设计的库,特别适宜多核CPU上的并行程序设计。今天在双核CPU机器上试了一下OpenMP并行程序设计,发现效率方面超出想象,因此写出来分享给大家。 在VC8.0中项目的属性对话框中,左边框里的“配置属性”下的“C/C++”下的“语言”页里,将OpenMP支持改为“是/(OpenMP)”就可以支持OpenMP了。 先看一个简单的

软件开发资料汇总 276

并行实验快速排序实验报告完整版

我们班一起做出来的并行计算的杰作,值得收藏呀!!!

归并排序的多线程实现

对大规模文件进行排序操作,首先将文件分割以便在内存中进行内排序.使用堆排序的方法对每个子文件排序.多线程实现.

openmp

http://stackoverflow.com/questions/22634121/openmp-c-matrix-multiplication-run-slower-in-parallel 转载于:https://www.cnblogs.com/Wanggcong/p/5566092.html

weixin_33743661的博客 110

OPENmp 快速排序的实现带部分注释

实现了OPENmp的快速排序,与用了sections,不同的section对不同区域进行排序。

归并排序

归并排序:   归并排序的核心思想是采用分治法,对已有序的子序列进行合并,从而得到一个有序的序列。 例如:两个已经有序的子序列:2,4,6,8与3,5,7,首先首元素也就是2与3比较,2小于3,把2放到最终的序列中,然后拿第一个序列中的4再与3比较,4大于3,于是把3放入最终的序列中,重复以上过程最终当把7放入最终的序列之后,第一个序列中还有8未放入,将8放入最终的子序列,归并结束,已成为一个

haipeng的博客 280

【传感器标定(三):激光雷达标定原理与实现详解】

本文系统介绍了激光雷达标定的基本原理与方法。主要内容包括:激光雷达标定的分类(内参标定和外参标定)、坐标系定义和刚体变换数学基础;详细阐述了测距误差和光束方向两种内参标定方法;重点分析了基于目标(平面标定板、三维标定物)和基于运动(手眼标定、点云配准)的外参标定方法;介绍了多传感器联合标定技术及非线性优化、鲁棒标定等实现方法;最后提出了定量和定性两种标定评估体系。为激光雷达在自动驾驶等领域的精确应用提供了系统的标定技术指导。

weixin_47429781的博客 3788

深度学习PyTorch代码模板——模型训练框架、数据处理、绘图…

个人用深度学习代码模板:模型训练框架、数据处理、绘图等。待补充。

https://www.hyperplasma.top 1905

现代C++与高性能并行编程:从智能指针到GPU计算的完整指南

在当今多核处理器普及的时代,高性能计算已成为软件开发的核心需求。并行编程通过将计算任务分解到多个核心同时执行,能够显著提升程序运行效率,这是其核心原理。这项技术的价值在于充分挖掘硬件潜力,应对大数据处理、科学模拟和实时系统等场景的性能挑战。现代C++语言特性,如智能指针和移动语义,为编写安全高效的并行代码提供了坚实基础。结合OpenMP、TBB等并行框架,开发者可以构建出充分利用CPU多核优势的应用程序。对于计算密集型任务,GPU并行计算进一步释放了异构计算的威力。本文围绕现代C++与高性能并行编程这一主题

weixin_34025051的博客 372

VisionMaster -Group循环、数组数据格式化

这里创建一个格式化工具,按照自己想要的模式格式化刚刚输出的数据。因为是检测多个物体,所以这里选择输出的是数组。第一次的模板很不满意,反复调整参数也没好,边缘不整齐,强迫症患者受不了。实测如果输入设置太多,会导致模块失效,所以尽量选择需要的少量参数。这里要打开循环使能,循环数值就是高精度匹配的匹配个数。Group输出设置里选择刚刚建立的数据集。左边是主流程,右边是组合模块内部流程。修改采图这里的曝光值,图像提亮一点。建一个数据集,把想要的数据收集起来。用TCP助手测试一下OK。成功得到了想要的数据。

weixin_69505365的博客 4529

十大排序算法专题 java实现:冒泡、选择、插入、希尔、归并、快速、堆排序、计数、桶、基数

文章目录1、冒泡排序(Bubble Sort)2、选择排序(Selection Sort)3、插入排序(Insertion Sort) 1、冒泡排序(Bubble Sort) 冒泡排序是一种简单的排序算法。它重复地走访过要排序的数列,一次比较两个元素,如果它们的顺序错误就把它们交换过来。走访数列的工作是重复地进行直到没有再需要交换,也就是说该数列已经排序完成。 这个算法的名字由来是因为越小的元素会...

严老板的技术梦想博客 2万+

告别反复格式化!用Ventoy制作2025年终极启动盘,Windows/Linux/macOS一网打尽

本文详细介绍了如何使用Ventoy制作2025年终极多系统启动盘,彻底告别反复格式化U盘的烦恼。Ventoy通过创新的引导加载器架构,允许用户在一个U盘内同时存放并直接启动Windows、Linux、macOS等多种系统镜像和WePE等维护工具,实现真正的非破坏性管理和极高的U盘利用率。文章涵盖从安装配置、镜像库构建到安全启动设置等全流程实战指南。

weixin_29280219的博客 240

【SystemVerilog基础】SystemVerilog Assertion 概述

断言主要是描述复杂的设计行为;是传统验证方法学一个很好的补充,可以检查到电路内部细节,相当于一种白盒验证,传统的验证方法学是纯粹的黑盒验证!property跟上面的sequence差别不大,唯一有差别可能多了两个implication(|->,|=>)注意,当有前置条件时,不满足前置条件不会fail,而是伪真!上面这个因为没有前置条件,每个周期都会去检查!注意:当没有前置条件时,不满足条件会直接fail!

MoorePlus的博客 332
上一篇: HDU3085 - Nightmare Ⅱ(双向BFS)
下一篇: UVA12511 - Virus(DP+最长公共上升子序列)
ACM_Fish
博客等级 码龄10年 69粉丝 76原创
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

个

红包个数最小为10个

元

红包金额最低5元

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

抵扣说明:

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

余额充值