C++ 用于快速插入和查找n维实向量的适当容器(提供初始基准测试) 1.问题的描述

C++ 用于快速插入和查找n维实向量的适当容器(提供初始基准测试) 1.问题的描述,c++,performance,stl,performance-testing,C++,Performance,Stl,Performance Testing,我试图选择最合适(有效)的容器来存储由浮点数组成的唯一的n维向量。 解决整个问题,最重要的步骤(与问题相关)包括: 从外部程序获取一个新向量(在同一次运行期间,所有向量都具有相同的维度) 检查(尽快)此容器中是否已存在新点: 如果存在-跳过许多昂贵的步骤,然后执行其他步骤 如果不-将插入容器中(在容器中订购并不重要),然后执行其他步骤 事先,我不知道我会有多少个向量,但最大数量是预先规定的,等于100000。而且,每次迭代我总是只得到一个新向量。因此,在开始时,这些新向量中的大多数是唯一的,

我试图选择最合适(有效)的容器来存储由浮点数组成的唯一的n维向量。 解决整个问题,最重要的步骤(与问题相关)包括:

  • 从外部程序获取一个新向量(在同一次运行期间,所有向量都具有相同的维度)
  • 检查(尽快)此容器中是否已存在新点:
    • 如果存在-跳过许多昂贵的步骤,然后执行其他步骤
    • 如果不-插入容器中(在容器中订购并不重要),然后执行其他步骤
  • 事先,我不知道我会有多少个向量,但最大数量是预先规定的,等于100000。而且,每次迭代我总是只得到一个新向量。因此,在开始时,这些新向量中的大多数是唯一的,将被插入到容器中,但以后很难提前预测。很大程度上取决于唯一向量和公差值的定义

    因此,我的目标是为这种情况选择合适的容器

    2.选择正确的容器 我做了一些回顾,从第1项:小心选择您的容器中发现

    查找速度是一个重要的考虑因素吗?如果是的话,你会想看看 在散列容器(见第25项)、排序向量(见第23项)和 标准关联容器-可能按该顺序排列

    我在David Moore的流程图中看到的是,s.Meyers建议的所有三个选项,第1项,都值得进行更广泛的调查

    2.1复杂性(基于) 让我们从非常简单的角度出发,研究所有三个独立选项的查找和插入过程的理论复杂性:

  • 对于向量
    • find_if()
      -线性O(n)
    • push_-back()
      -常数,但如果新的
      size()
      大于旧的
      容量()
  • 对于集合
    • insert()
      -容器大小的对数,O(log(size())
  • 对于无序集
    • insert()
      -平均情况:O(1),最坏情况O(size())
  • 3.不同容器的基准测试 在所有的实验中,我用随机生成的三维向量来模拟情况,这些向量填充了区间[0,1]中的真实值

    编辑:

    使用的编译器:苹果LLVM 7.0.2版(clang-700.1.81)

    在发布模式下编译,优化级别为-O3,没有任何优化级别

    3.1使用未排序的
    向量
    

    第一,为什么未排序向量?我的场景与在强>项目23中描述的情况有很大的不同:考虑用排序向量替换关联容器。< /强> 因此,在这种情况下,使用排序向量没有任何优势

    第二,假设两个向量x和y相等,如果
    euclideandstance2(x,y)
    。 考虑到这一点,我对vector的最初(可能相当糟糕)实现如下:

    使用
    vector
    容器对实施的一部分进行基准测试:

    // create a vector of double arrays (vda)
    std::vector<std::array<double, N>> vda;
    const double tol = 1e-6;  // set default tolerance
    // record start time
    auto start = std::chrono::steady_clock::now();
    // Generate and insert one hundred thousands new double arrays
    for (size_t i = 0; i < 100000; ++i) {
      // Get a new random double array (da)
      std::array<double, N> da = getRandomArray();
      auto pos = std::find_if(vda.begin(), vda.end(),  // range
        [=, &da](const std::array<double, N> &darr) {  // search criterion
        return EuclideanDistance2(darr.begin(), darr.end(), da.begin()) < tol*tol;
        });
    
      if (pos == vda.end()) {
        vda.push_back(da);  // Insert array
      }
    }
    // record finish time
    auto end = std::chrono::steady_clock::now();
    std::chrono::duration<double> diff = end - start;
    std::cout << "Time to generate and insert unique elements into vector: "
              << diff.count() << " s\n";
    std::cout << "vector's size = " << vda.size() << std::endl;
    
    // create a set of double arrays (sda) with a special sorting criterion
    std::set<std::array<double, N>, compare_arrays> sda;
    // create a vector of double arrays (vda)
    std::vector<std::array<double, N>> vda;
    // record start time
    auto start = std::chrono::steady_clock::now();
    // Generate and insert one hundred thousands new double arrays
    for (size_t i = 0; i < 100000; ++i) {
      // Get a new random double array (da)
      std::array<double, N> da = getRandomArray();
      // Inserts into the container, if the container doesn't already contain it.
      sda.insert(da);
    }
    // record finish time
    auto end = std::chrono::steady_clock::now();
    std::chrono::duration<double> diff = end - start;
    std::cout << "Time to generate and insert unique elements into SET: "
              << diff.count() << " s\n";
    std::cout << "set size = " << sda.size() << std::endl;
    
      // create a unordered set of double arrays (usda)
      std::unordered_set<std::array<double, N>, ArrayHash, ArrayEqual> usda;
      // record start time
      auto start = std::chrono::steady_clock::now();
      // Generate and insert one hundred thousands new double arrays
      for (size_t i = 0; i < 100000; ++i) {
        // Get a new random double array (da)
        std::array<double, N> da = getRandomArray();
        usda.insert(da);
      }
      // record finish time
      auto end = std::chrono::steady_clock::now();
      std::chrono::duration<double> diff = end - start;
      std::cout << "Time to generate and insert unique elements into UNORD. SET: "
                << diff.count() << " s\n";
      std::cout << "unord. set size() = " << usda.size() << std::endl;
    
    排序标准基于有缺陷的(打破严格的弱排序)。目前,我想(大致)看看我能从不同的容器中得到什么,然后决定哪一个是最好的

    // return whether the elements in the arr1 are “lexicographically less than”
    // the elements in the arr2
    struct compare_arrays {
      bool operator() (const std::array<double, N>& arr1,
                       const std::array<double, N>& arr2) const {
        // Lexicographical comparison compares using element-by-element rule
        return std::lexicographical_compare(arr1.begin(), arr1.end(),  // 1st range
                                            arr2.begin(), arr2.end(),  // 2nd range
                                            compare_doubles);   // sorting criteria
       }
      // return true if x < y and not within tolerance distance
      static bool compare_doubles(double x, double y) {
        return (x < y) && !(fabs(x-y) < tolerance);
      }
     private:
      static constexpr double tolerance = 1e-6;  // Comparison tolerance
    };
    
    其中,原始哈希函数:

    // Hash Function
    struct ArrayHash {
      std::size_t operator() (const std::array<double, N>& arr) const {
        std::size_t ret;
        for (const double elem : arr) {
          ret += std::hash<double>()(elem);
        }
        return ret;
      }
    };
    
    //散列函数
    结构ArrayHash{
    std::size\u t运算符()(const std::array&arr)const{
    标准:尺寸和重量;
    用于(常数双元素:arr){
    ret+=std::hash()(elem);
    }
    返回ret;
    }
    };
    
    和等效标准:

    // Equivalence Criterion
    struct ArrayEqual {
      bool operator() (const std::array<double, N>& arr1,
                              const std::array<double, N>& arr2) const {
        return EuclideanDistance2(arr1.begin(), arr1.end(), arr2.begin()) < tol*tol;
      }
     private:
      static constexpr double tol = 1e-6;  // Comparison tolerance
    };
    
    //等价准则
    结构ArrayEqual{
    布尔运算符()(常数std::数组和arr1,
    常数std::数组和arr2)常数{
    返回欧几里德常数2(arr1.begin(),arr1.end(),arr2.begin())
    3.3.1测试未分类设备的性能 在下面最后一张凌乱的表格中,我再次总结了执行时间和容器大小,这取决于不同的公差(eps)值

    |eps |带-O3标志/不带优化标志的时间|大小|

    |1e-6 | 57.4823/0.0590703 | 100000/100000|

    |1e-3 | 57.9588/0.0618149 | 99978/100000|

    |1e-2 | 43.2816/0.0595529 | 82873/100000|

    |1e-1 | 0.238788/0.0578297 | 781/99759|

    简而言之,与其他两种方法相比,执行时间是最好的,但是即使使用非常宽松的公差(1e-1)几乎所有的随机向量都被识别为唯一的。所以,在我的例子中,节省了查找时间,但浪费了更多的时间来处理问题的其他昂贵步骤。我想,这是因为我的哈希函数真的很幼稚

    编辑:这是最意外的行为。对无序集启用-O3优化,会严重降低性能。更令人惊讶的是,唯一元素的数量取决于优化标志,这可能不是唯一的意思,这意味着我必须提供更好的哈希函数

    4.开放性问题
  • 如我所知,在前进中,可能的唯一向量的最大大小使用
    std::vector::reserve(100000)
    有意义吗
  • 根据
    的规定,保留
    不会对性能造成太大影响:

    我过去在读报纸时,对使用reserve()很小心 向量。我惊讶地发现,对于我的所有用途, 呼叫储备
    // Hash Function
    struct ArrayHash {
      std::size_t operator() (const std::array<double, N>& arr) const {
        std::size_t ret;
        for (const double elem : arr) {
          ret += std::hash<double>()(elem);
        }
        return ret;
      }
    };
    
    // Equivalence Criterion
    struct ArrayEqual {
      bool operator() (const std::array<double, N>& arr1,
                              const std::array<double, N>& arr2) const {
        return EuclideanDistance2(arr1.begin(), arr1.end(), arr2.begin()) < tol*tol;
      }
     private:
      static constexpr double tol = 1e-6;  // Comparison tolerance
    };
    
    struct ArrayEqual {
      bool operator() (const std::array<double, N>& arr1,
                  const std::array<double, N>& arr2) const {
          auto beg1 = arr1.begin(), end1 = arr1.end(),  beg2 = arr2.begin();
          double val = 0.0;
          while (beg1 != end1) {
            double dist = (*beg1++) - (*beg2++);
            val += dist*dist;
            if (val >= tol*tol)
                return false;
          }
          return true;
      }
     private:
      static constexpr double tol = 1e-6;  // Comparison tolerance
    };