以最大性能将双精度的向量截断为单精度
Truncate vector of doubles to single precision at maximum performance
我正在流体动力学代码中试验降低某些操作中浮点数的精度,以测试是否真的需要双精度。
为此,我编写了一个截断函数,它将双精度向量的精度降低到单精度,而不转换数据。这使我能够评估某些函数的准确性,而无需将代码转换为单个精度。由于这些评估的计算成本很高,因此我的目标是拥有一个具有最高性能的截断函数。我已经尝试了以下内容,有什么方法可以提高truncate
功能的性能吗?
#include <vector>
#include <iostream>
#include <iomanip>
#include <chrono>
#include <random>
void truncate(std::vector<double>& v)
{
for (double& d : v)
{
float d_float = static_cast<float>(d);
d = static_cast<double>(d_float);
}
}
int main()
{
std::random_device rd;
std::mt19937 mt(rd());
std::uniform_real_distribution<double> dist(0., 1.);
const int n = 512*512*512;
std::vector<double>v(n);
for (double& d : v)
d = dist(mt);
std::cout << "Before: " << std::setprecision(15) << v[0] << std::endl;
auto start = std::chrono::high_resolution_clock::now();
truncate(v);
auto duration = std::chrono::duration_cast<std::chrono::microseconds>(std::chrono::high_resolution_clock::now() - start);
std::cout << "After: " << std::setprecision(15) << v[0] << std::endl;
std::cout << "Duration in microseconds: " << duration.count() << std::endl;
return 0;
};
执行截断函数,您可能需要手动执行操作;假设您有权访问 OpenMP 和 SSE 内部函数,请查看_mm_cvtpd_ps(将 2 个双精度转换为两个浮点数)和_mm_cvtps_pd(转换回双精度)。
像这样:
double * vec; // aligned properly
#pragma omp parallel for schedule(static, 512)
for (int i = 0; i < size; i += 2)
{
_mm_store_pd(vec + i, _mm_cvtps_pd(_mm_cvtpd_ps(_mm_load_pd(vec + i))));
}
这就是我会尝试的事情;你可以使用OpenMP选项,内部函数的确切形状(如果有的话,也许使用AVX)等。
编辑:AVX变体只是_mm256_cvtpd_ps等等,如果你可以实现这个建议,你也可以实现AVX版本。
如果您只想截断双精度以快速浮动,那么有更快(和更黑客)的方法可以做到这一点。根据您已经知道或可以假设的数字,它可以更快或更慢。
- 你能有浮点尺度的畸形吗?
- 你能有零吗?
- 你能有NaN吗?
- 浮点标度上会有无穷大的数字吗?
对于这个解决方案,我假设你可以有零,但没有非正规,NaN或无穷大。换句话说,我可以掩盖浮点数所没有的每一点,并获得足够接近的近似值:
for (double &d : doubles) { (*(uint64_t*)&d) &= 0xFFFF_FFFF_E000_0000; }
这保持你的符号位和指数,以及 23 位尾数。为了完全准确,您还需要裁剪指数 - 但它会导致不正常(我们假设不会发生)或无穷大(相同)。
请注意,告知处理器实际类型的解决方案可能更好、更准确。这是一篇解释性文章,以说明浮点数和双精度之间的实际区别。
您是否考虑过使用截断函数的多线程版本?像这样:
void truncate(std::vector<double>& v, const int n_threads = 1)
{
if(n_threads <= 1) {
for (double& d : v) {
float d_float = static_cast<float>(d);
d = static_cast<double>(d_float);
}
}
else {
std::vector<std::thread> threads;
for (size_t id = 0; id < n_threads; ++id) {
auto threadFunc = [=,&v]() {
size_t beg = id*v.size()/n_threads;
size_t end = std::min(v.size(), (id+1)*v.size()/n_threads + (id == n_threads-1)*(v.size() % n_threads));
for (size_t i=beg; i < end; ++i) {
float d = static_cast<float>(v[i]);
v[i] = static_cast<double>(d);
}
};
threads.push_back(std::thread(threadFunc));
}
for (auto & t : threads) t.join();
}
}
对于大矢量,如果您能够负担得起使用许多线程,则增益应该很重要。
您是否考虑过使用普通的旧typedef(我更喜欢使用C++11的别名)作为using myType = float
,然后使用std::vector<myType>
作为您希望在代码中浮点的变量?这也将准确了解模拟的准确性和性能。
在这里宣传myType
的使用需要一些时间,但值得 IMO,因为如果您愿意,您可以翻转回双倍。此外,正如@steiner所指出的,尽可能多地使用并行构造也会提高性能。
- 从"int*"强制转换为"unsigned int"会丢失精度错误
- 如何防止 c++ 在从浮点型转换为双精度型(不适用于 IO)时添加额外的小数?
- 正在将csv文件读取为双精度矢量
- 如何理解将半精度指针转换为无符号长指针和相关的内存对齐
- 我可以信任表示整数的浮点或双精度来保持精度吗
- 如何在C++中的同一函数中使用字符串和双精度
- 特征::矩阵<双精度,1,3> 结构类型函数中的返回类型函数
- 当使用比格式支持的精度更高的精度来显示数字时,会写出什么数据
- 如何计算具有指定类型的表达式的相对精度和绝对精度
- 如何打印boost多精度128位无符号整数
- 检查是否以特定精度给出双精度
- 转换函数,将 std::数组的双精度作为参数或双精度作为参数单独转换
- C 字符串返回字符串的整数/双精度/长整型值
- 为什么将双精度转换为 int 似乎在第 16 位数字之后将其四舍五入?
- 如何使双精度值的 C++ 和 C# 中的结果相同
- 以最大性能将双精度的向量截断为单精度
- 将十六进制值视为单精度或双精度值
- 对于固定的数据大小,双精度 CUDA 代码比单精度对应代码更快
- 单精度和双精度铸造给出了不同的答案
- 如何判断双精度浮点数是否可以安全地存储为单精度浮点数