c++ - 推力 vector 距离计算

标签 c++ thrust

考虑以下数据集和质心。一共有7个人,两个均值有8个维度。它们按行主要顺序存储。

short dim = 8;
float centroids[] = {
    0.223, 0.002, 0.223, 0.412, 0.334, 0.532, 0.244, 0.612, 
    0.742, 0.812, 0.817, 0.353, 0.325, 0.452, 0.837, 0.441
};   
float data[] = {
    0.314, 0.504, 0.030, 0.215, 0.647, 0.045, 0.443, 0.325,
    0.731, 0.354, 0.696, 0.604, 0.954, 0.673, 0.625, 0.744, 
    0.615, 0.936, 0.045, 0.779, 0.169, 0.589, 0.303, 0.869, 
    0.275, 0.406, 0.003, 0.763, 0.471, 0.748, 0.230, 0.769, 
    0.903, 0.489, 0.135, 0.599, 0.094, 0.088, 0.272, 0.719, 
    0.112, 0.448, 0.809, 0.157, 0.227, 0.978, 0.747, 0.530, 
    0.908, 0.121, 0.321, 0.911, 0.884, 0.792, 0.658, 0.114
};

我想计算每个欧几里得距离。 c1-d1,c1-d2...。
在CPU上,我会这样做:
float dist = 0.0, dist_sqrt;
for(int i = 0; i < 2; i++)
    for(int j = 0; j < 7; j++)
    { 
        float dist_sum = 0.0;
        for(int k = 0; k < dim; k++)
        {
            dist = centroids[i * dim + k] - data[j * dim + k];
            dist_sum += dist * dist;
        }
        dist_sqrt = sqrt(dist_sum);
        // do something with the distance
        std::cout << dist_sqrt << std::endl;

    }

THRUST中有 vector 距离计算的内置解决方案吗?

最佳答案

可以通过推力来完成。解释将如何参与其中,并且代码非常密集。

首先要进行的主要观察是,可以通过转换归约来完成核心操作。推力变换操作用于对 vector 进行元素减法(单个质心)并对每个结果进行平方,而归约法将结果相加在一起,以得出欧几里德距离的平方。此操作的起点是thrust::reduce_by_key,但要正确地将数据呈现给reduce_by_key会涉及很多。

通过从上方取每个结果的平方根来产生最终结果,为此,我们可以使用普通的thrust::transform

以上是完成所有工作的仅两行推力代码的摘要说明。但是,第一行具有相当大的复杂性。为了利用并行性,我采取的方法是实际上按顺序“布局”必要的 vector ,并呈现给reduce_by_key。举一个简单的例子,假设我们有2个质心和4个个体,并且我们的维数是2。

centroid 0: C00 C01
centroid 1: C10 C11
individ  0: I00 I01
individ  1: I10 I11
individ  2: I20 I21
individ  3: I30 I31

我们可以像这样“布局” vector :
 C00 C01 C00 C01 C00 C01 C00 C01 C10 C11 C10 C11 C10 C11 C10 C11
 I00 I01 I10 I11 I20 I21 I30 I31 I00 I01 I10 I11 I20 I21 I30 I31

为了简化reduce_by_key,我们还需要生成键值来描绘 vector :
   0   0   1   1   0   0   1   1   0   0   1   1   0   0   1   1

上面的“布局”数据集可能非常大,我们不想增加存储和检索成本,因此我们将使用ostt的fancy iterators集合来生成这些“即时”数据。这是事情变得很密集的地方。考虑到以上策略,我们将使用thrust::reduce_by_key进行工作。我们将创建一个提供给transform_iterator的自定义仿函数,以对IC vector 进行减法(和平方),为此将它们压缩在一起。 vector 的“布局”将使用带有附加自定义索引创建函子的置换迭代器动态创建,以帮助IC中的每个复制模式。

因此,从“由内而外”的工作顺序如下:
  • (I)和data(C)的
  • 使用centrcounting_iterator内部的自定义索引函子相结合,以生成我们需要的索引序列。
  • 使用在步骤1中创建的索引序列以及基本transform_iteratorI vector ,通过C(每个布局 vector 一个)对 vector 进行虚拟“布局”。
  • 将2个“布局的”虚拟permutation_iteratorI vector 压缩在一起,以创建C元组 vector (虚拟)。
  • 从第3步中获取<float, float>,并在zip_iterator
  • 中与自定义距离计算函子((I-C)^2)组合
  • 使用另一个transform_iterator,将transform_iterator与自定义密钥生成函子结合在一起,以生成密钥序列(虚拟)
  • 将步骤4和5中的迭代器传递给counting_iterator作为要减少的输入(键,值)。 reduce_by_key的输出 vector 也是键和值。我们不需要密钥,因此我们将使用reduce_by_key转储这些密钥。我们将保存的值。

  • 以上步骤全部在一行推力代码中完成。

    这是说明上述内容的代码:
    #include <iostream>
    #include <thrust/device_vector.h>
    #include <thrust/host_vector.h>
    #include <thrust/reduce.h>
    #include <thrust/iterator/transform_iterator.h>
    #include <thrust/iterator/counting_iterator.h>
    #include <thrust/iterator/permutation_iterator.h>
    #include <thrust/iterator/zip_iterator.h>
    #include <thrust/iterator/discard_iterator.h>
    #include <thrust/copy.h>
    #include <math.h>
    
    #include <time.h>
    #include <sys/time.h>
    #include <stdlib.h>
    
    #define MAX_DATA 100000000
    #define MAX_CENT 5000
    #define TOL 0.001
    
    unsigned long long dtime_usec(unsigned long long prev){
    #define USECPSEC 1000000ULL
      timeval tv1;
      gettimeofday(&tv1,0);
      return ((tv1.tv_sec * USECPSEC)+tv1.tv_usec) - prev;
    }
    
    unsigned verify(float *d1, float *d2, int len){
      unsigned pass = 1;
      for (int i = 0; i < len; i++)
        if (fabsf(d1[i] - d2[i]) > TOL){
          std::cout << "mismatch at:  " << i << " val1: " << d1[i] << " val2: " << d2[i] << std::endl;
          pass = 0;
          break;}
      return pass;
    }
    void eucl_dist_cpu(const float *centroids, const float *data, float *rdist, int num_centroids, int dim, int num_data, int print){
    
      int out_idx = 0;
      float dist, dist_sqrt;
      for(int i = 0; i < num_centroids; i++)
        for(int j = 0; j < num_data; j++)
        {
            float dist_sum = 0.0;
            for(int k = 0; k < dim; k++)
            {
                dist = centroids[i * dim + k] - data[j * dim + k];
                dist_sum += dist * dist;
            }
            dist_sqrt = sqrt(dist_sum);
            // do something with the distance
            rdist[out_idx++] = dist_sqrt;
            if (print) std::cout << dist_sqrt << ", ";
    
        }
        if (print) std::cout << std::endl;
    }
    
    
    struct dkeygen : public thrust::unary_function<int, int>
    {
      int dim;
      int numd;
    
      dkeygen(const int _dim, const int _numd) : dim(_dim), numd(_numd) {};
    
      __host__ __device__ int operator()(const int val) const {
        return (val/dim);
        }
    };
    
    typedef thrust::tuple<float, float> mytuple;
    struct my_dist : public thrust::unary_function<mytuple, float>
    {
      __host__ __device__ float operator()(const mytuple &my_tuple) const {
        float temp = thrust::get<0>(my_tuple) - thrust::get<1>(my_tuple);
        return temp*temp;
      }
    };
    
    
    struct d_idx : public thrust::unary_function<int, int>
    {
      int dim;
      int numd;
    
      d_idx(int _dim, int _numd) : dim(_dim), numd(_numd) {};
    
      __host__ __device__ int operator()(const int val) const {
        return (val % (dim*numd));
        }
    };
    
    struct c_idx : public thrust::unary_function<int, int>
    {
      int dim;
      int numd;
    
      c_idx(int _dim, int _numd) : dim(_dim), numd(_numd) {};
    
      __host__ __device__ int operator()(const int val) const {
        return (val % dim) + (dim * (val/(dim*numd)));
        }
    };
    
    struct my_sqrt : public thrust::unary_function<float, float>
    {
      __host__ __device__ float operator()(const float val) const {
        return sqrtf(val);
      }
    };
    
    
    unsigned long long eucl_dist_thrust(thrust::host_vector<float> &centroids, thrust::host_vector<float> &data, thrust::host_vector<float> &dist, int num_centroids, int dim, int num_data, int print){
    
      thrust::device_vector<float> d_data = data;
      thrust::device_vector<float> d_centr = centroids;
      thrust::device_vector<float> values_out(num_centroids*num_data);
    
      unsigned long long compute_time = dtime_usec(0);
      thrust::reduce_by_key(thrust::make_transform_iterator(thrust::make_counting_iterator<int>(0), dkeygen(dim, num_data)), thrust::make_transform_iterator(thrust::make_counting_iterator<int>(dim*num_data*num_centroids), dkeygen(dim, num_data)),thrust::make_transform_iterator(thrust::make_zip_iterator(thrust::make_tuple(thrust::make_permutation_iterator(d_centr.begin(), thrust::make_transform_iterator(thrust::make_counting_iterator<int>(0), c_idx(dim, num_data))), thrust::make_permutation_iterator(d_data.begin(), thrust::make_transform_iterator(thrust::make_counting_iterator<int>(0), d_idx(dim, num_data))))), my_dist()), thrust::make_discard_iterator(), values_out.begin());
      thrust::transform(values_out.begin(), values_out.end(), values_out.begin(), my_sqrt());
      cudaDeviceSynchronize();
      compute_time = dtime_usec(compute_time);
    
      if (print){
        thrust::copy(values_out.begin(), values_out.end(), std::ostream_iterator<float>(std::cout, ", "));
        std::cout << std::endl;
        }
      thrust::copy(values_out.begin(), values_out.end(), dist.begin());
      return compute_time;
    }
    
    
    int main(int argc, char *argv[]){
    
      int dim = 8;
      int num_centroids = 2;
      float centroids[] = {
        0.223, 0.002, 0.223, 0.412, 0.334, 0.532, 0.244, 0.612,
        0.742, 0.812, 0.817, 0.353, 0.325, 0.452, 0.837, 0.441
      };
      int num_data = 8;
      float data[] = {
        0.314, 0.504, 0.030, 0.215, 0.647, 0.045, 0.443, 0.325,
        0.731, 0.354, 0.696, 0.604, 0.954, 0.673, 0.625, 0.744,
        0.615, 0.936, 0.045, 0.779, 0.169, 0.589, 0.303, 0.869,
        0.275, 0.406, 0.003, 0.763, 0.471, 0.748, 0.230, 0.769,
        0.903, 0.489, 0.135, 0.599, 0.094, 0.088, 0.272, 0.719,
        0.112, 0.448, 0.809, 0.157, 0.227, 0.978, 0.747, 0.530,
        0.908, 0.121, 0.321, 0.911, 0.884, 0.792, 0.658, 0.114,
        0.721, 0.555, 0.979, 0.412, 0.007, 0.501, 0.844, 0.234
      };
      std::cout << "cpu results: " << std::endl;
      float dist[num_data*num_centroids];
      eucl_dist_cpu(centroids, data, dist, num_centroids, dim, num_data, 1);
    
      thrust::host_vector<float> h_data(data, data + (sizeof(data)/sizeof(float)));
      thrust::host_vector<float> h_centr(centroids, centroids + (sizeof(centroids)/sizeof(float)));
      thrust::host_vector<float> h_dist(num_centroids*num_data);
    
      std::cout << "gpu results: " << std::endl;
      eucl_dist_thrust(h_centr, h_data, h_dist, num_centroids, dim, num_data, 1);
    
      float *data2, *centroids2, *dist2;
      num_centroids = 10;
      num_data = 1000000;
    
      if (argc > 2) {
        num_centroids = atoi(argv[1]);
        num_data = atoi(argv[2]);
        if ((num_centroids < 1) || (num_centroids > MAX_CENT)) {std::cout << "Num centroids out of range" << std::endl; return 1;}
        if ((num_data < 1) || (num_data > MAX_DATA)) {std::cout << "Num data out of range" << std::endl; return 1;}
        if (num_data * dim * num_centroids > 2000000000) {std::cout << "data set out of range" << std::endl; return 1;}}
      std::cout << "Num Data: " << num_data << std::endl;
      std::cout << "Num Cent: " << num_centroids << std::endl;
      std::cout << "result size: " << ((num_data*num_centroids*4)/1048576) << " Mbytes" << std::endl;
      data2 = new float[dim*num_data];
      centroids2 = new float[dim*num_centroids];
      dist2 = new float[num_data*num_centroids];
      for (int i = 0; i < dim*num_data; i++) data2[i] = rand()/(float)RAND_MAX;
      for (int i = 0; i < dim*num_centroids; i++) centroids2[i] = rand()/(float)RAND_MAX;
      unsigned long long dtime = dtime_usec(0);
      eucl_dist_cpu(centroids2, data2, dist2, num_centroids, dim, num_data, 0);
      dtime = dtime_usec(dtime);
      std::cout << "cpu time: " << dtime/(float)USECPSEC << "s" << std::endl;
      thrust::host_vector<float> h_data2(data2, data2 + (dim*num_data));
      thrust::host_vector<float> h_centr2(centroids2, centroids2 + (dim*num_centroids));
      thrust::host_vector<float> h_dist2(num_data*num_centroids);
      dtime = dtime_usec(0);
      unsigned long long ctime = eucl_dist_thrust(h_centr2, h_data2, h_dist2, num_centroids, dim, num_data, 0);
      dtime = dtime_usec(dtime);
      std::cout << "gpu total time: " << dtime/(float)USECPSEC << "s, gpu compute time: " << ctime/(float)USECPSEC << "s" << std::endl;
      if (!verify(dist2, &(h_dist2[0]), num_data*num_centroids)) {std::cout << "Verification failure." << std::endl; return 1;}
      std::cout << "Success!" << std::endl;
    
      return 0;
    
    }
    

    笔记:
  • 该代码设置为执行2次传递,使用与您的数据集相似的数据集进行简短传递,并通过打印输出进行视觉检查。然后,可以通过命令行大小调整参数(质心数量,然后是个人数量)输入更大的数据集,以进行基准比较和结果验证。
  • 与我在注释中所说的相反,推力代码的运行速度仅比朴素的单线程CPU代码快25%。你的旅费可能会改变。
  • 这只是考虑处理它的一种方法。我有其他想法,但没有足够的时间充实它们。
  • 数据集可能会变得很大。目前的代码旨在限制为discard_iterator的乘积小于20亿的数据集。但是,当您接近这个数字时,您将需要同时具有几GB内存的GPU和CPU。我简要探讨了更大的数据集大小。从(例如)扩展到各个地方都需要进行一些代码更改。 dimension*number_of_centroids*number_of_individualsint等。但是,由于我仍在研究该代码的问题,因此我没有提供。
  • 关于在GPU上计算欧几里德距离的另一种与推力无关的观察,您可能对this question感兴趣。如果遵循在那里进行的优化顺序,则可能会说明如何改进推力代码或如何使用其他非推力实现。
  • 对不起,我无法发挥更多性能。
  • 关于c++ - 推力 vector 距离计算,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/27823951/

    相关文章:

    仅当满足谓词时,CUDA 推力才会复制转换后的结果

    c++ - 链表反向函数 OOP C++

    c++ - 使用十六进制内存地址获取变量的值

    cuda - 使用 CUDA 计算不同集合中的点之间的全对距离

    visual-studio-2010 - 如何使用并行 nsight 在 Visual Studio 2010 中调试 cuda 推力功能

    c++ - CMAKE_CXX_SOURCE_FILE_EXTENSIONS 不适用于推力/cuda

    sorting - Thrust::sort 有多快以及最快的基数排序实现是什么

    c++ - 多字节到宽字符转换后内存中存在临时拷贝

    c++ - for循环与时钟功能之间的交互?

    c++ - 如何通过按 "cancel"键取消 CListCtrl 中的编辑?