OpenMP学习 第一章 并行计算

第一章 并行计算


并发性

并发性(concurrency): 如果来自任何一个流的单个指令与来自其他流的指令相比是无序的,则这两个或多个指令流就被称为是并发的.

为了进行OpenMP开发,记得去 属性->C/C++->语言->OpenMP支持 中,选择 是(/openmp)

并行程序实验(第一个并行程序):

#include <cstdio>
#include <omp.h>

int main()
{
	#pragma omp parallel
	{
		printf("Hello");
		printf("World");
	}
	return 0;
}

观察结果,会发现与设想中的不同

HelloHelloHelloWorldWorldHelloWorldWorldHelloWorldHelloWorldHelloWorldHelloHelloWorldHelloWorldHelloWorldHelloWorldHelloWorldHelloWorldHelloWorldHelloWorldWorldHelloWorldHelloWorldHelloWorldHelloWorld

这就是并发性的体现.

多处理器系统

单指令多数据(SIMD): 单一指令流驱动多个数据元素的向量单元.

对称多处理器模型(SMP): 一个SMP有N个处理器,共享一个内存.硬件由操作系统管理,操作系统对所有处理器一视同仁.对于任何处理器来说,访问内存中任何变量的成本都一样的多处理器系统.

缓存: 现代CPU紧密集成在核上的高速内存小区域,缓存上的存储空间映射到DRAM中的地址上,而这种映射是以被称为缓存行的小分块形式进行的.

动态随机存取存储器(DRAM): 计算机中内存的具体实现,其比芯片内的核慢得多.

中央处理器(CPU): 一个通用处理器.它被优化为快速地提供单个事件的结果,即 低延迟.
CPU每个核都有用于数据(L1D$)和指令(L1I$)的一级缓存,统一的二级缓存(L2$)和共享的三级缓存(L3$).
CPU每个内存控制器都有三个通道,用于访问片外内存(DRAM).

访问内存中一个值所需的时间取决于该值在内存层级结构中的位置.考虑从内存中访问一个值所需的时间,即内存访问延迟.

  • L1缓存延迟=4个周期
  • L2缓存延迟=12个周期
  • L3缓存延迟=42个周期
  • DRAM访问延迟=约250个周期

因此,与其说内存系统具有均匀的内存访问时间,不如说系统具有 非均匀的内存架构(NUMA). 即现代多处理器系统不是SMP,而是NUMA系统.

图形处理单元

计算机图形学的核心问题是 为一个场景选用合适的模型,并将其转化为像素呈现给视觉处理系统. 因而图形学中的处理方式一般为数据并行的:一个场景被分解为几何图形,这些图形又被进一步转化为分片的集合.这些分片流经处理流水线后被渲染成一组像素显示在屏幕上.

总的处理流水线由以下几个阶段组成:

  • 遍历每一个3D对象,用多边形平铺可见平面.
  • 遍历每一个多边形,分解成三角形.
  • 遍历每一个三角形,计算遍历组成三角形的每一个像素点的颜色.

这里的关键思想是 遍历每一个(foreach),后面跟着某种集合的名称.该函数被应用于集合中的每个成员.集合中每个成员的计算是独立的并且可以同时进行,即并行是在数据中进行的(因此称为 数据级并行).

通用图形处理器(GPGPU): 是一种利用处理图形任务的图形处理器来计算原本由中央处理器处理的通用计算任务.这些通用计算常常与图形处理没有任何关系.

区分GPGPU与CPU编程的最根本问题是 吞吐量延迟.当一个帧流显示在显示器上时,你并不关心更新一个像素点需要多长时间.

  • 如果你关心的是在屏幕上移动的帧的吞吐量,那么你关心的将是整个帧的生成速度是否足够让视频看起来流畅.

  • 如果你关心的是单个计算的延迟,那么你关心的是在屏幕上的某一点上点击鼠标后能够得到立即响应.

通用处理器的低延迟要求通常通过大量电路的使用实现.

吞吐量优化的GPU编程模型的基本思想是将foreach语句的索引范围变成索引空间.foreach语句的主体被转化为成为核的函数.核的实例在索引空间中的每一个点运行.我们称这个核实例为工作项(work-item).数据被对其到同一个NDRange上,所以我们将数据放置在靠近工作项的地方,处理元件将对其进行操作.

为了使GPU成为一个专门的吞吐量引擎,GPU内置的调度器知道核函数所需要的数据.工作项被分组到工作组(work-groups)中,工作组在它的数据可用之前等待执行.如果工作组比处理元件多得多,它们会排队等待.在等待数据流过系统时,有很多工作可以让处理元件保持忙碌状态.

分布式内存集群

将多个处理器集成到单一存储器域是可能的.

集群(cluster): 把标准化的现成服务器联网成一个大型系统,建造成大规模数据中心.然后用软件将它们捆绑在一起形成的大型计算机.

消息传递接口(MPI): 用于在分布式内存机器上编程的标准API

集群作为一种并行计算机,其服务器位于集群网络中的节点上.计算节点间不共享物理内存,因此内存分布在系统周围.节点之间通过相互传递消息进行交互.关键思想是"双向"通信.

并行软件

操作系统代表系统的用户管理硬件.当启动一个程序时,操作系统会创建一个进程.与这个进程相关联的是一块内存区域和对系统资源的访问权.

一个进程会分叉为一个或多个线程,这些线程都是该进程的一部分.每个线程都有自己的内存块,但所有线程都共享进程的内存和进程可用的系统资源.

操作系统对线程的执行进行调度,这些线程代表该进程执行程序的指令.在一个系统中,线程比处理器多得多.基本的思想是操作系统将并发线程交换执行.

pthread: pthread中的p是POSIX的缩写,而POSIX是Portable Operating System Interface的缩写,是IEEE为要在各种UNIX操作系统上运行软件,而定义API的一系列互相关联的标准的总称.

为了在windows系统中配置pthread环境,我们需要去手动编译与配置环境.步骤如下:

  • 前往https://raw.gitcode.com/gerhobbelt/pthread-win32/archive/refs/heads/master.zip下载pthread库文件

  • 在解压后的目录...\pthread-win32-master\pthread-win32-master\windows\VS2022中找到pthread.2022.sln,用VS2022打开后,通过 生成->生成解决方案 生成.

  • 在...\pthread-win32-master\windows\VS2022\bin\Debug-Unicode-64bit-x64目录中找到pthread.lib,pthread.dll,pthread_static_lib.lib三个文件,将其整理到一个另外的文件夹pthread-win32-lib中.

  • 在...\pthread-win32-master目录中找到所有.h,.c,.cpp类型的文件,将其整理到一个另外的文件夹爱pthread-win32-header中.

  • 在 项目->属性->链接器->常规->附加库目录 中将pthread-win32-lib的目录添加进去.在 项目->属性->链接器->输入->附加依赖项 中将pthread.lib和pthread_static_lib.lib添加进去.

  • 在 项目->属性->VC++目录->外部包含目录 中将pthread-win32-header的目录添加进去.在 项目->属性->VC++目录->库目录 中将pthread-win32-lib的目录添加进去.

  • 在程序中添加#include<pthread.h>语句,进行测试.

下面给出了一个通过pthread库进行并行程序设计的实例.

#include <pthread.h>
#include <stdio.h>
#include <stdlib.h>
#define NUM_THREADS 4

void* PrintHelloWorld(void* InputArg)
{
	printf("Hello\n");
	printf("World\n");
	return NULL;
}

int main()
{
	pthread_t threads[NUM_THREADS];
	pthread_attr_t attr;
	pthread_attr_init(&attr);
	pthread_attr_setdetachstate(&attr, PTHREAD_CREATE_JOINABLE);

	for (int id = 0; id < NUM_THREADS; id++)
		pthread_create(&threads[id], &attr, PrintHelloWorld, NULL);
	for (int id = 0; id < NUM_THREADS; id++)
		pthread_join(threads[id], NULL);

	pthread_attr_destroy(&attr);
	pthread_exit(NULL);

	return 0;
}
posted @ 2024-01-14 01:19  Mesonoxian  阅读(68)  评论(0编辑  收藏  举报