From cbee0a18149dcebd5187c048d7a2ac83476bb2b1 Mon Sep 17 00:00:00 2001 From: jiayaozhang Date: Tue, 11 Jan 2022 17:23:30 +0800 Subject: [PATCH 1/3] add hw4 --- main.cpp | 127 ++++++++++++++++++++++++++++++++++++++++--------------- opt_main | Bin 0 -> 22992 bytes run.sh | 7 +-- 3 files changed, 96 insertions(+), 38 deletions(-) create mode 100755 opt_main diff --git a/main.cpp b/main.cpp index cf6369b..6e944ed 100644 --- a/main.cpp +++ b/main.cpp @@ -4,25 +4,34 @@ #include #include +constexpr float speedup = 1.0 / RAND_MAX; + float frand() { - return (float)rand() / RAND_MAX * 2 - 1; + return (float)rand() / speedup * 2 - 1; } +constexpr int length = 48; struct Star { - float px, py, pz; - float vx, vy, vz; - float mass; + float px[length]; + float py[length]; + float pz[length]; + float vx[length]; + float vy[length]; + float vz[length]; + float mass[length]; }; -std::vector stars; +Star stars; void init() { for (int i = 0; i < 48; i++) { - stars.push_back({ - frand(), frand(), frand(), - frand(), frand(), frand(), - frand() + 1, - }); + stars.px[i] = frand(); + stars.py[i] = frand(); + stars.pz[i] = frand(); + stars.vx[i] = frand(); + stars.vy[i] = frand(); + stars.vz[i] = frand(); + stars.mass[i] = frand() + 1; } } @@ -31,36 +40,84 @@ float eps = 0.001; float dt = 0.01; void step() { - for (auto &star: stars) { - for (auto &other: stars) { - float dx = other.px - star.px; - float dy = other.py - star.py; - float dz = other.pz - star.pz; - float d2 = dx * dx + dy * dy + dz * dz + eps * eps; - d2 *= sqrt(d2); - star.vx += dx * other.mass * G * dt / d2; - star.vy += dy * other.mass * G * dt / d2; - star.vz += dz * other.mass * G * dt / d2; + size_t len = length; + float eps2 = eps * eps; + float gdt = G * dt; + #pragma GCC unroll 16 + for (size_t i = 0 ; i < len; i++) { + float dxs[length]; + float dys[length]; + float dzs[length]; + float d2s[length]; + float ivf_d2s[length]; + #pragma opm simd + for(size_t j=0; j < len; j++) + { + dxs[j] = stars.px[j] - stars.px[i]; + } + #pragma opm simd + for(size_t j=0; j < len; j++) + { + dys[j] = stars.py[j] - stars.py[i]; + } + #pragma opm simd + for(size_t j=0; j < len; j++) + { + dzs[j] = stars.pz[j] - stars.pz[i]; + } + #pragma opm simd + for(size_t j=0; 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 Date: Tue, 11 Jan 2022 17:24:47 +0800 Subject: [PATCH 2/3] add hw4 for parallel computing --- README.md | 26 ++++++++++++++------------ 1 file changed, 14 insertions(+), 12 deletions(-) diff --git a/README.md b/README.md index 18caa7d..fa7eb42 100644 --- a/README.md +++ b/README.md @@ -1,18 +1,20 @@ # 高性能并行编程与优化 - 第04讲的回家作业 -通过 pull request 提交作业。会批分数,但是: -没有结业证书,回家作业仅仅作为评估学习效果和巩固知识的手段,不必为分数感到紧张 :) -量力而行,只要能在本课中,学到昨天的自己不懂的知识,就是胜利,没必要和别人攀比。 -注意不要偷看别人的作业哦! - -- 课件:https://github.com/parallel101/course -- 录播:https://space.bilibili.com/263032155 - -作业提交时间不限 :) 即使完结了还想交的话我也会看的~ 不过最好在下一讲开播前完成。 - -- 如何开 pull request:https://zhuanlan.zhihu.com/p/51199833 -- 如何设置 https 代理:https://www.jianshu.com/p/b481d2a42274 +## 详细解释 +1. size_t 在 64 位系统上相当于 uint64_t size_t 在 32 位系统上相当于 uint32_t + 从而不需要用 movslq 从 32 位符号扩展到 64 位,更高效。而且也能处理数组大小超过 INT_MAX 的情况,推荐始终用 size_t 表示数组大小和索引 +2. 开启优化:-O3 +3. 浮点作为参数和返回:xmm系列寄存器 xmm寄存器有128位宽, 可以容纳4个float,或2个double +4. SIMD(single-instruction multiple-data)称为单个指令处理多个数据的技术,他可以大大增加计算密集型程序的吞吐量 +5. AOS:紧凑存储多个属性. 符合一般面向对象编程(OOP)的习惯,但常常不利于性能 +6. SOA:分离存储多个属性. 不符合面向对象编程(OOP)的习惯,但常常有利于性能。又称之为面向数据编程 (DOP) +7. AOSOA: SOA便于SIMD优化;AOS便于存储在传统容器;AOSOA两者得兼! +8. 对齐到 16 或 64 字节 +9. 试试看 #pragma omp simd +10. 循环中不变的常量挪到外面来 +11. 对小循环体用 #pragma unroll +12. -ffast-math 和 -march=native ## 评分规则 From 6587d5882150bacd690a33184033de1852db269b Mon Sep 17 00:00:00 2001 From: jiayaozhang Date: Tue, 11 Jan 2022 19:08:13 +0800 Subject: [PATCH 3/3] add hw4 for parallel computing --- main.cpp | 23 ++++++++++++++++++++--- opt_main | Bin 22992 -> 21504 bytes run.sh | 1 + 3 files changed, 21 insertions(+), 3 deletions(-) diff --git a/main.cpp b/main.cpp index 6e944ed..6a8b504 100644 --- a/main.cpp +++ b/main.cpp @@ -4,6 +4,9 @@ #include #include +//-march=native 让编译器自动判断当前硬件支持的指令 +//强迫编译器在编译期求值 +//用 constexpr 函数迫使编译器进行常量折叠! constexpr float speedup = 1.0 / RAND_MAX; float frand() { @@ -11,6 +14,9 @@ float frand() { } constexpr int length = 48; +//存储在栈上无法动态扩充大小,这就是为什么 vector +//这种数据结构要存在堆上,而固定长度的 array 可以存在栈上 + struct Star { float px[length]; float py[length]; @@ -21,6 +27,11 @@ struct Star { float mass[length]; }; +//SOA:分离存储多个属性 +//不符合面向对象编程 (OOP) 的习惯,但常常有利于性能。又称之为面向数据编程 + +//AOS:紧凑存储多个属性 +//符合一般面向对象编程 (OOP) 的习惯,但常常不利于性能 Star stars; void init() { @@ -34,7 +45,7 @@ void init() { stars.mass[i] = frand() + 1; } } - +//循环中的不变量:挪到外面来 float G = 0.001; float eps = 0.001; float dt = 0.01; @@ -87,6 +98,9 @@ void step() { stars.vz[i] += dzs[j] * stars.mass[j] * (gdt * ivf_d2s[j]); } } +//pragma omp simd +//C/C++ 的缺点:指针的自由度过高,允许多个 immutable reference 指向同一个对象 +//而 Rust 从语法层面禁止,从而让编译器放心大胆优化。 #pragma opm simd for(size_t i=0; i long benchmark(Func const &func) { auto t0 = std::chrono::steady_clock::now(); diff --git a/opt_main b/opt_main index 92c1a6aba3c2c1aeaf43f7888caeb3bdff5b640c..3c80de3c8b6339488b6dd26287c9201aebab192a 100755 GIT binary patch literal 21504 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 N=Oo9>1c8L6_MfK7=sW-b literal 22992 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