大家可以访问:
https://docs.nvidia.com/cuda/cuda-c-best-practices-guide/index.html 来阅读原文。
这是一本很经典的手册。
CUDA优化的冷知识10 | GPU卡和Jetson上显存优化的特色
CUDA优化的冷知识13 |从Global memory到Shared memory
CUDA优化的冷知识14|local memory你可能不知道的好处
接着之前的内容, 即说对GPU上的各种存储器的优化使用, 今天来到纹理存储.
这个其实我们之前在编程指南中已经说过很多了, 读者也应当对基本用法, 包括经典的纹理引用和较新的纹理对象都应该会使用了. 我们主要说一下使用纹理所带来的主要优势.
根据之前的内容, 你已经知道, 纹理可以提供免费的值变换, 和免费的坐标变换, 以及免费的越界处理, 以及, 更加优化的访存/缓存效果. 我们主要从这4点说开.
先说一下免费的值变换. 有些算法需要将数据作为8-bit或者16-bit整数存储, 然后读取到后, 再转换为float之类的浮点数, 和其他类型进行运算. 而这个转换过程, 需要用户手工写, 哪怕是一个简单的float b = (float)a;这种. 以及, 这种转换还需要占用SFU(特殊功能单元), 注意SFU在新版本的Nsight profiler中已经简单的改名成了XU单元了. 那么此时, 无论是从转换指令本身, 需要占据额外的硬件资源; 还是从编写代码的人的角度, 他需要手写额外的代码行, 都是一种开销. 而纹理读取的时候, 可以利用上其数据路径中的自带的转换功能, 从而节省掉对SFU/XU或者人工编码成本的开销.
这样有可能带来额外的性能提升, 和对人力成本的节省.
例如我们知道, 在很多代卡的架构上, 一次SFU完成的整数到float的转换, 性能只有常规指令的1/4:
如图, 我们可以看到了7.x的卡上, 每SM每周期可以执行64条常规的float加法/乘法/乘加, 这往往构成了你的代码的运算主体;
而从8-bit或者16-bit或者其他整数类型转换成float的时候, 吞吐率就只有16条/SM/周期了, 相当于在7.X上转换本身只有常规计算的1/4的性能. 甚至这点在8.6上更加糟糕, 因为8.6的双倍速的float运算, 导致如果你读取一个普通的8-bit或者16-bit整数(u)int8/16_t, 然后进行一次手工到float的转换, 相当于大约等效8条后续的正常计算的性能被浪费掉了(某种意义上), 即转换只有1/8的效率. 此时如果你的代码SFU/XU是瓶颈, 或者因为使用SFU而导致了浪费了指令发射能力的话, 应当考虑使用texture自带的免费转换功能, 来节省对应的SFU的I2F之类的转换指令. 这样会可能带来额外的性能提升.
不过需要注意的是, 自动的转换是一个"归一化"的过程, 将会从8-bit或者16-bit的有/无符号整数范围映射到[-1.0f, 1.0f]或者[0.0f, 1.0f], 其中包括了1.0f了, 这点使用的时候应当小心. 例如考虑是等效乘以了1/255还是1/256的系数的问题(包括还是不包括1.0f右边界).
好在大部分的使用float运算的代码, 应当很容易处理这种问题. 这是使用texture的带来的可能的第一个优化上的效果.
注意第一点的值变换除了归一化读取到的值, 还有低精度的插值效果, 这个线性插值效果我们曾经已经在编程指南手册中说过了, 这里就重点说了. (虽然本手册这里强调了一下). 如果适用你的算法, 则利用硬件自动的插值的效果可以进一步节省你的手工运算量, 从而潜在的可能提升性能.
这两点都属于今天的texture带来的4点中的第一大点, 即自动/免费对读取到的值变换的好处.