如何花式计算20的阶乘?
今天刷知乎看到个挺有意思的问题:「如何优雅地利用c++编程从1乘到20?」
如何优雅地利用c++编程从1乘到20?
我想这有啥难的,还能写出花来不成?结果看到高赞回答,感觉自己的智商有点不够用了。
随便来看一个高赞回答是怎么写的:
这个其实还算比较简单的,没啥难度,还有更晦涩的:
这个乍一看根本看不懂在写啥,当然平时也很少会写这种晦涩的代码。
CUDA花式整活!
今天我就教大家用CUDA来计算一下20的阶乘,就当作是CUDA的一个入门例子。
先来看看我的回答:
如何优雅地利用c++编程从1乘到20?
我提供了两种CUDA的实现方法。
方法1
#include <iostream>
typedef unsigned long long int ull;
const int N = 20;__device__ ull atomicMul(ull* address, ull val) {ull *address_as_ull = (ull *)address;ull old = *address_as_ull, assumed;do {assumed = old;old = atomicCAS(address_as_ull, assumed, val * assumed);} while (assumed != old);return old;
}__global__ void mul(ull *x) {ull i = threadIdx.x + 1;atomicMul(x, i);
}int main() {ull *x;cudaMallocManaged(&x, sizeof(ull));x[0] = 1;mul<<<1, N>>>(x);cudaDeviceSynchronize();std::cout << x[0] << std::endl;cudaFree(x);return 0;
}
这个方法使用原子操作,一共20个线程,每个线程负责一个乘数,然后一起乘到x[0]
上。
但是由于并行执行,线程之间没有先后顺序,会导致同时乘的时候产生冲突,所以需要使用原子操作。在某一个线程将它的乘数乘到x[0]
上时,不会被其他线程打断。也就是会加锁,同一时刻只会有一个线程在进行乘法操作。
但是由于CUDA只提供了加法和减法的原子操作(atomicAdd
和atomicSub
),所以得自己实现乘法的原子操作atomMul
,利用的是atomicCAS
操作,也就是compare and swap
,如果目标地址元素和待比较的元素相同,就进行元素的交换,否则不进行任何操作。
可以看出,在atomicMul
函数的do while
循环中,先用old
变量保存x[0]
处的当前值,这时候如果有其他线程在x[0]
处写入了新的值,那么接下来该线程的atomicCAS
操作就会判断元素不相同,不进行任何操作,重新执行下一轮循环。
方法2
#include <iostream>
typedef unsigned long long int ull;
const int N = 20;
const int WARP_SIZE = 32;__global__ void mul(ull *x) {int i = threadIdx.x;ull val = x[i];for (int mask = WARP_SIZE / 2; mask > 0; mask >>= 1)val *= __shfl_xor_sync(WARP_SIZE - 1, val, mask, WARP_SIZE);x[i] = val;
}int main() {ull *x;cudaMallocManaged(&x, WARP_SIZE * sizeof(ull));for (int i = 0; i < WARP_SIZE; ++i)x[i] = i < N ? i + 1 : 1;mul<<<1, WARP_SIZE>>>(x);cudaDeviceSynchronize();std::cout << x[0] << std::endl;cudaFree(x);return 0;
}
这种方法使用线程束原语__shfl_xor_sync
,只要线程在同一个线程束中(32个线程),就可以获取其他线程的值,异或运算后写入指定地址。详细原理这里就不解释了,可以简单理解为:
- 一共进行5轮操作。
- 第一轮操作之后,下标为0-15的位置分别保存着下标0+1、2+3、一直到30+31的结果。
- 第二轮操作之后,下标为0-7的位置分别保存着下标0+1+2+3、4+5+6+7、一直到28+29+30+31的结果。
- 最后一轮之后,下标为0的位置保存着所有32个元素之和。
所以只需要在开始时,分配一个大小为32的数组,前20个元素分别保存1-20,后面12个元素是为了满足线程束大小32的条件,赋值为1就行了。
方法2改进
方法2需要额外开辟大小为32的数组,空间存在浪费,并且数组赋值也需要时间。
感谢 @NekoDaemon 老哥提供的优化建议,只需要在计算的时候根据线程号计算对应乘积元素就行,但是线程数仍然需要分配32个。
#include <iostream>
typedef unsigned long long int ull;
const int N = 20;
const int WARP_SIZE = 32;__global__ void mul(ull *x) {int i = threadIdx.x;ull val = i < N ? i + 1 : 1;for (int mask = WARP_SIZE / 2; mask > 0; mask >>= 1)val *= __shfl_xor_sync(WARP_SIZE - 1, val, mask, WARP_SIZE);if (!i) x[i] = val;
}int main() {ull *x;cudaMallocManaged(&x, sizeof(ull));mul<<<1, WARP_SIZE>>>(x);cudaDeviceSynchronize();std::cout << x[0] << std::endl;cudaFree(x);return 0;
}
执行结果
代码保存为run.cu
,然后执行nvcc run.cu -o run
,最后执行./run
,就会出来结果2432902008176640000。
今天没有讲解CUDA编程的基础概念,适合有一定基础的同学阅读,如果有对CUDA感兴趣的同学,可以在评论区留言,下次专门写一个CUDA入门系列教程。关注我的公众号【算法码上来】,每天跟进最新文章。
如何花式计算20的阶乘?相关推荐
- python利用函数求20的阶乘_请运用reduce函数,计算20的阶乘,慕课网2-19
# 从functools 中导入reduce函数 from functools import reduce def use_reduce(data): # 使用result接收reduce实现20的阶 ...
- C语言 1A gt $20,C语言输出 1到20 的阶乘之和
除了调用库,绝对找不到比这更精简的代码了. #include #include long long getdata(long long n){ long long sum = 1; while(n){ ...
- 第十周 计算1到20的阶乘的和
1./* 2.*程序的版权和版本声明部分: 3.*Copyright(c)2013,烟台大学计算机学院学生 4.*All rights reserved. 5.*文件名称: 6.*作者:尚振伟 7.* ...
- 综合训练3 计算1~20的阶乘的倒数之和
综合训练3 计算1~20的阶乘的倒数之和 编写Java程序,使用while循环语句计算1+1/2!+1/3!+-+1/20!之和. /*综合训练3 计算1~20的阶乘的倒数之和* 编写Java程序,使 ...
- 数据结构实验--大数运算之计算n的阶乘 (n≥20)
一.问题描述 大数运算--计算n的阶乘 (n≥20). [基本要求] (1)数据的表示和存储: ①累积运算的中间结果和最终的计算结果的数据类型要求是整型--这是问题本身的要求. ②试设计合适的存储结构 ...
- 用C语言计算1到20的阶乘之和,用C语言计算1~20的阶乘之和
昨天(2018/12/7)在做C语言的课后练习题的时候,有一道题要求我们计算1~20的阶乘之和.代码很快就写出来了,考虑到结果的值会比较大,而在Windows操作系统下,int 类型和 long 类型 ...
- C—计算10的阶乘以及1到10的阶乘之和
计算10的阶乘 //计算10的阶乘 int main() {long long ll = 1;int i = 10;for (i = 10; i >= 1; i--) {ll = ll * i; ...
- 突破数据极限:计算24的阶乘和n的m次方。
这里写自定义目录标题 欢迎使用Markdown编辑器 新的改变 功能快捷键 合理的创建标题,有助于目录的生成 如何改变文本的样式 插入链接与图片 如何插入一段漂亮的代码片 生成一个适合你的列表 创建一 ...
- raptor阶乘相加流程图_从键盘输入正整数n的值,计算n的阶乘,例如:输入5,输出120。请据此画出程序流程图。 (上传Raptor软件流程图和运行结果的截图)_学小易找答案...
[单选题]对现值指数法来说,当原始投资额现值相等的时候,实质上是( ). [单选题]某公司打算投资一个项目,预计该项目需固定资产投资400万元,预计可使用5年.项目预计流动资产需用额为160万元,流动 ...
- c语言 n阶阶乘尾0个数,计算n的阶乘(n!)末尾0的个数
题目: 给定一个正整数n,请计算n的阶乘n!末尾所含有"0"的个数. 举例: 5!=120,其末尾所含有的"0"的个数为1: 10!= 3628800,其末尾所 ...
最新文章
- 交互两个数(不引入第三个变量)
- docker错误:Error response from daemon: Cannot start container
- hdu 4472 Count(递推即dp)
- WCF rest 的帮助页面和缓存机制
- 学习《css世界》笔记之content自动添加开启闭合符号
- 【数据结构与算法-java实现】二 复杂度分析(下):最好、最坏、平均、均摊时间复杂度的概念
- 小白设计模式:责任链模式
- 《游戏改变企业》一一第 2 章 实时的经济信息反馈(为什么游戏很重要) 游戏改变企业...
- Java 执行SQL脚本文件
- VISIO各种图标超全(IT行业专用网络及硬件)_5G行业应用规划设计思路探讨
- 2019长江课堂作业答案_2019版长江课堂作业答案语文四年级
- python对txt文件进行处理实战_python数据处理实战
- 如何用excel实现并列排序
- 日常活动--英文短句
- 面试时,可以问面试官问题总结
- 医疗器械小程序或手机APP软件开发方案
- 【运筹学】对偶理论总结 ( 对称性质 | 弱对偶定理 | 最优性定理 | 强对偶性 | 互补松弛定理 ) ★★★
- VPC是什么,VPC详解
- 阿里巴巴收购网易考拉,定了!
- RoseHA9.0 for WindowsServer2008R2 配合sqlserver安装配置
热门文章
- http请求,普通的get和post方法
- Java事务之八——分布式事务(Spring+JTA+Atomikos+Hibernate+JMS)
- Build Settings发布设置
- 2017-12-3 Crontab(字符串处理)
- linux之C编程学习——getchar()和getch()
- ORACLE成果,天天10问(四)
- (宏)Word 仅修改选中图片的尺寸
- mysql 开启binlog
- MySQL Innodb Engine -- 文件格式(innodb_file_format)
- Whl自助搜索下载器