在最大性能下截断双精度矢量到单精度

时间:2016-04-21 07:50:44

标签: c++ c++11

我正在试验流体动力学代码,在某些操作中降低浮点数的精度,以测试是否真的需要双精度。

为此,我编写了一个truncate函数,它将双精度向量的精度降低到单精度,而不转换数据。这使我可以评估某些函数的准确性,而无需将代码转换为单精度。由于这些评估在计算上很昂贵,我的目标是具有尽可能高的性能的截断功能。我试过以下,有没有办法提高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;
};

4 个答案:

答案 0 :(得分:4)

对于truncate函数的绝对性能,您可能需要手动执行操作;假设您可以访问OpenMP和SSE内在函数,请查看_mm_cvtpd_ps(将2个双精度转换为2个浮点数)和_mm_cvtps_pd(转换回双精度数)。

类似的东西:

post_id

这是我会尝试的事情;你可以使用OpenMP选项,内在函数的确切形状(如果你有它可能使用AVX)等等。

编辑:AVX变体只是_mm256_cvtpd_ps等等,如果你可以实现这个建议,你也可以实现AVX版本。

答案 1 :(得分:2)

如果你只想快速截断双精度浮点数,那么有更快(更黑客)的方法。根据您已经知道或可以假设的数字,它可以更快或更慢。

  • 你能有漂浮规模的非正规吗?
  • 你能有零吗?
  • 你能拥有NaN吗?
  • 浮点数上的数字是无穷大吗?

对于这个解决方案,我假设你可以有零,但没有非正规,NaN或无穷大。换句话说,我可以掩盖浮子不具有的每一个位并得到足够接近的近似值:

for (double &d : doubles) { (*(uint64_t*)&d) &= 0xFFFF_FFFF_E000_0000; }

这保持你的符号位和指数,以及23位的尾数。为了完全准确,你还需要剪切指数 - 但它会导致非正规(我们假设不会发生)或无穷大(相同)。

请注意,确实通知处理器实际类型的解决方案可能更好,更准确。这是一个解释性帖子,用于说明浮点数和双精度数之间的实际差异是什么。

答案 2 :(得分:0)

您是否考虑过使用多线程版本的截断功能?类似的东西:

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();
  }
}

对于大型向量,如果你能负担得起使用很多线程,那么增益应该很重要。

答案 3 :(得分:0)

您是否考虑使用普通的旧typedef(我更喜欢使用C ++ 11的别名)作为using myType = float,然后使用std::vector<myType>作为您希望在代码中浮动的变量?这样可以准确地了解模拟的准确性和性能。

这里传播使用myType需要一些时间,但IMO是值得的,因为如果你愿意,你可以翻回来加倍。同样正如@steiner所指出的那样,尽可能使用并行结构也会提高性能。