前半部分就是随手记的东西啦…
VS2019 拖动窗口布局VS崩溃卡住, 打开VS installer更新不要在VS2019中直接检查更新
gmp C/C++高精度库(终于不用手写啦
https://gmplib.org/
这可怜的孩子调环境调了好久https://blog.csdn.net/a675115471/article/details/104425406
最后发现了!当当当当!
vcpkg
vcpkg: 于 Windows、Linux 和 macOS 的 C++ 包管理器
https://zhuanlan.zhihu.com/p/87391067
有时候需要翻墙,有时候又不用???(最后都是没有翻墙
安装
> git clone https://github.com/microsoft/vcpkg
> .\vcpkg\bootstrap-vcpkg.bat
vcpkg list
vcpkg install
vcpkg search
vcpkg integrate install 集成到VS2019
-
将C:\Users\Eternal\vcpkg加入到path环境变量中
-
vcpkg默认安装32位的库,为了让它安装64位的库,添加系统环境变量:VCPKG_DEFAULT_TRIPLET=x64-windows
-
x64 gmp有些依赖需要x86的tool,因此先安装一遍32位的把依赖搞好
win10不重启使环境变量修改生效,只需要重启powershell
gmp
C/C+±常规:VS2019中需要把SDL检查设为否
否则
>error C4146: 一元负运算符应用于无符号类型,结果仍为无符号类型
windef.h中有min定义与gmpxx中冲突,原因是在计时器中加入了<windows.h>
在gmpxx.h前面加上#undef min #undef max即可
杂项: (我是zz)
对于.tar.lz 压缩文件,要用linux下的lzip解压
选项-d 删除原来的包
lzip -d gmp-6.1.2.tar.lz
0 实验目的
- 使用OpenMP计算圆周率。
- 通过设置不同的选项(如:OMP_NUM_THREADS)比较程序性能,并对结果进行分析。
1 实验原理
1.1 简介
OpenMP是编译器指令和库函数的集合,并行模式为fork/join并行执行:串行部分由主线程执行,并行执行的部分派生其他线程执行,全部结束后执行后面非并行部分的代码。
1.2 指令和库函数介绍
基本格式:#pragma omp 指令 [子句][子句]…
(编译制导语句)
指令包含:(反正有些试过了也没想到怎么巧妙的用到,知道有它就行了)
-
parallel,用在一个代码段前,表示这段代码将被多个线程并行执行
-
parallel for, parallel 和 for 语句的结合,也是用在一个 for 循环前,表示 for 循环的代码将被多个线程并行执行,要保证每次循环间无相关性,且for的格式必须按照最简单的标准写
-
parallel sections,parallel 和 sections 两个语句的结合用在可能会被并行执行的代码段前。多行代码需要用{}括起来
-
critical,用在一段代码临界区前
-
single,用在一段叧被单个线程执行的代码段乊前,表示后面的代码段将被单线程执行。
-
barrier,并行区内代码的线程同步,所有线程执行到 barrier 时要停止,直到所有线程都执行到 barrier 时才继续往下执行。
-
atomic,指定一块内存区域被制动更新
-
master,指定一段代码块由主线程执行
-
ordered, 指定并行区域的循环按顺序执行
-
threadprivate, 指定一个(global)变量是线程私有的。static也可
库函数:
-
omp_get_num_procs, 返回运行本线程的多处理机的处理器个数。
-
omp_get_num_threads, 返回当前并行区域中的活动线程个数。
-
omp_get_thread_num, 返回线程号
-
omp_set_num_threads, 设置并行执行代码时的线程个数
-
omp_init_lock, 初始化一个简单锁
-
omp_set_lock, 上锁操作
-
omp_unset_lock, 解锁操作,要和 omp_set_lock 函数配对使用。
-
omp_destroy_lock, omp_init_lock 函数的配对操作函数,关闭一个锁
子句:
-
private 指定每个线程都有它自己的变量私有副本。
-
firstprivate指定每个线程都有它自己的变量私有副本,并且变量要被继承主线程中的初值,可以与lastprivate混用
-
lastprivate主要是用来指定将线程中的私有变量的值在并行处理结束后复制回主线程中的对应变量。多个并行区,取语法上最后一个返回而不是实际执行的。
-
reduction用来指定一个或多个变量是私有的,并且在并行处理结束后这些变量要执行指定的运算。
#int i,sum = 100; #pragma omp parallel for reduction(+:sum) for(i = 0;i<1000;i++){ sum += i; } pritnf("sum = %ld\n",sum);
-
nowait忽略指定中暗含的等待
-
num_threads指定线程的个数
-
schedule指定如何调度 for 循环迭代
-
shared指定一个或多个变量为多个线程间的共享变量,如果要写,必须加以保护
-
ordered用来指定 for 循环的执行要按顺序执行
-
copyprivate用于single 指令中的指定变量为多个线程的共享变量
-
copyin用来指定一个 threadprivate 的变量的值要用主线程的值迚行刜始化。
-
default用来指定并行处理区域内的变量的使用方式,缺省是 shared
1.3
π
\pi
π 计算公式:BBP公式
π
=
∑
k
=
0
∞
[
1
1
6
k
(
4
8
k
+
1
−
2
8
k
+
4
−
1
8
k
+
5
−
1
8
k
+
6
)
]
\pi = \sum_{k=0}^{\infty}[\frac{1}{16^k}(\frac{4}{8k+1}-\frac{2}{8k+4}-\frac{1}{8k+5}-\frac{1}{8k+6})]
π=k=0∑∞[16k1(8k+14−8k+42−8k+51−8k+61)]
该公式可以很方便的使用openmp算出不同项的无限和,然后累加成为最后的结果。它的优点是不用计算前面n-1项的和,可以直接通过小精度的小项累加成为最后的结果。
2 实验内容
2.1 环境配置
上一个实验中已经配置过,在此简要说明下:
- 在VS2019中将C/C+±语言-OpenMP支持调为“是/(OpenMP)”
- 在头文件中包含<omp.h>
大数计算库gmp
安装vcpkg包管理器,安装gmp
git clone https://github.com/microsoft/vcpkg
.\vcpkg\bootstrap-vcpkg.bat
vcpkg install gmp
# 需要先设置VCPKG_DEFAULT_TRIPLET=x86-windows
# 然后再设置为VCPKG_DEFAULT_TRIPLET=x64-windows
# 安装所有依赖
vcpkg integrate install
# 集成到VS2019中
VS2019:
C/C++ 常规 : SDL检查设为否
gmpxx.h前加上#undef min #undef max去除windows.h中的宏定义冲突
2.2 主要代码
stop_watch.h中包含了打包好的高精度计时类
main.cpp
主要利用BBP公式的4个求和式子进行分解,4个并行块每个k次循环计算得到该项的值,最后求和得到;
即:
π
=
(
∑
k
=
0
∞
4
1
6
k
(
8
k
+
1
)
−
∑
k
=
0
∞
2
1
6
k
(
8
k
+
4
)
−
∑
k
=
0
∞
1
1
6
k
(
8
k
+
5
)
−
∑
k
=
0
∞
1
1
6
k
(
8
k
+
6
)
)
\pi = (\sum_{k=0}^{\infty}\frac{4}{16^k(8k+1)}-\sum_{k=0}^{\infty}\frac{2}{16^k(8k+4)}-\sum_{k=0}^{\infty}\frac{1}{16^k(8k+5)}-\sum_{k=0}^{\infty}\frac{1}{16^k(8k+6)})
π=(k=0∑∞16k(8k+1)4−k=0∑∞16k(8k+4)2−k=0∑∞16k(8k+5)1−k=0∑∞16k(8k+6)1)
将大数的精度设为100000以保证中间结果不会被截断;
迭代次数k=7000。
线程数由1增加到4。
最后与10000位的pi真实值做比较,将二者都转换为字符串,去掉小数点后由高位向低位逐位对比,输出时间以及正确的位数。
#include<iostream>
#include <omp.h>
#include<iomanip>
#include "stop_watch.h"
#include<gmpxx.h>
#include<gmp.h>
using namespace std;
static long num_steps = 7000;
static long num_threads = 1;
#define GLOBAL_PREC 100000
int main()
{
mpf_set_default_prec(GLOBAL_PREC);
for (num_threads = 1; num_threads <= 4; num_threads++) {
mpf_t sum, t1, t2, t3, t4;
mpf_init_set_ui(sum, 0);
mpf_init_set_ui(t1, 0);
mpf_init_set_ui(t2, 0);
mpf_init_set_ui(t3, 0);
mpf_init_set_ui(t4, 0);
stop_watch total;
total.start();
#pragma omp parallel sections num_threads(num_threads)
{
cout << "\nnumber of threads:" << omp_get_thread_num() << endl;
#pragma omp section
{
stop_watch clk;
clk.start();
for (int k = 0; k < num_steps; k++) {
mpf_t d;
mpf_t f;
mpf_init_set_ui(d, 16);
mpf_init_set_ui(f, 4);
mpf_pow_ui(d, d, k);
mpf_mul_ui(d, d, 8 * k + 1);
mpf_div(f, f, d);
mpf_add(t1, t1, f);
mpf_clear(d);
mpf_clear(f);
}
clk.stop();
cout << " id:" << omp_get_thread_num() << " time:" << clk.elapsed() / 1000000 << "ms" << endl;
}
#pragma omp section
{
stop_watch clk;
clk.start();
for (int k = 0; k < num_steps; k++) {
mpf_t d;
mpf_t f;
mpf_init_set_ui(d, 16);
mpf_init_set_ui(f, 2);
mpf_pow_ui(d, d, k);
mpf_mul_ui(d, d, 8 * k + 4);
mpf_div(f, f, d);
mpf_add(t2, t2, f);
mpf_clear(d);
mpf_clear(f);
}
clk.stop();
cout << " id:" << omp_get_thread_num() << " time:" << clk.elapsed() / 1000000 << "ms" << endl;
}
#pragma omp section
{
stop_watch clk;
clk.start();
for (int k = 0; k < num_steps; k++) {
mpf_t d;
mpf_t f;
mpf_t r;
mpf_init_set_ui(d, 16);
mpf_init_set_ui(f, 1);
mpf_pow_ui(d, d, k);
mpf_mul_ui(d, d, 8 * k + 5);
mpf_div(f, f, d);
mpf_add(t3, t3, f);
mpf_clear(d);
mpf_clear(f);
}
clk.stop();
cout << " id:" << omp_get_thread_num() << " time:" << clk.elapsed()/1000000 << "ms" << endl;
}
#pragma omp section
{
stop_watch clk;
clk.start();
for (int k = 0; k < num_steps; k++) {
mpf_t d;
mpf_t f;
mpf_init_set_ui(d, 16);
mpf_init_set_ui(f, 1);
mpf_pow_ui(d, d, k);
mpf_mul_ui(d, d, 8 * k + 6);
mpf_div(f, f, d);
mpf_add(t4, t4, f);
mpf_clear(d);
mpf_clear(f);
}
clk.stop();
cout << " id:" << omp_get_thread_num() << " time:" << clk.elapsed() / 1000000 << "ms" << endl;
}
}
//result = t1 -t2 -t3 -t4
mpf_add(sum, sum, t1);
mpf_sub(sum, sum, t2);
mpf_sub(sum, sum, t3);
mpf_sub(sum, sum, t4);
total.stop();
cout << "total time:" << total.elapsed() / 1000000 << "ms\n";
mpf_class result(sum);
mp_exp_t a = 10;
//转换结果为string
string res_str = result.get_str(a);
string pi("314159265358979323846264338327950...");//Pi 真实值
int flag;
for (flag = 0; flag < 10000; flag++) {
if (res_str[flag] != pi[flag]) {
break;
}
}
cout <<"precision: " << flag <<"digit(s)\n<-------------------------->";
mpf_clear(sum);
mpf_clear(t1);
mpf_clear(t2);
mpf_clear(t3);
mpf_clear(t4);
}
}
3 结果与分析
3.1 结果
number of threads:1
id:0 time:2.01282ms
id:0 time:1.91033ms
id:0 time:1.81013ms
id:0 time:1.85256ms
total time:7.59722ms
precision: 8438digit(s)
<-------------------------->
number of threads:2
id:1 time:2.03429ms
id:0 time:2.05222ms
id:1 time:1.99943ms
id:0 time:1.98397ms
total time:4.04432ms
precision: 8438digit(s)
<-------------------------->
number of threads:3
id:0 time:2.33938ms
id:2 time:2.4219ms
id:1 time:2.43272ms
id:0 time:1.94513ms
total time:4.29149ms
precision: 8438digit(s)
<-------------------------->
number of threads:4
id:0 time:2.48247ms
id:3 time:2.50972ms
id:2 time:2.51007ms
id:1 time:2.52254ms
total time:2.52844ms
precision: 8438digit(s)
<-------------------------->
threads | total_time |
---|---|
1 | 7.59722ms |
2 | 4.04432ms |
3 | 4.29149ms |
4 | 2.52844ms |
可见,当单线程时,要串行执行4个并行块,所需时间最长,为4线程的3倍
2线程时较单线程有所提升,速度大概在串行的1.6倍左右,因为线程需要调度,因此达不到两倍的速率
3线程负载不均衡,有一个线程需要额外跑一个并行块,与2线程接近(在多次试验中二者不相上下)
4线程负载均衡同时并行度高,是速度最快的,但同时单个线程的消耗也是最大的。
用VTune进行查看,CPU利用率与预期相符
随着线程增加,CPU总时间减少,但在3线程时有两个线程是空闲的,为图中右上绿色部分
3.2 改进方向
还可以按照k的顺序进行划分,反正最后求和项数是一定的。
即最外圈for循环作为并行块,一次计算4个项,这样可以增大线程数的上限。
由于section中无法嵌套parallel for,我的方法中中最大只能利用4个线程,即4个section。
但是要考虑到k增大,会导致小数位数增大,从而负载不平均,效率反而降低。