【问题标题】:Abstraction for bitset element iterationbitset 元素迭代的抽象
【发布时间】:2020-01-18 22:17:37
【问题描述】:

我在 C++ 中有一个自定义的 bitset 类实现。我经常迭代在 bitset 中设置的位的索引(即对于 bitset '10011',我想迭代数字 0、3、4。)这种迭代可以实现如下:

struct Bitset {
  uint64_t* data_;
  size_t chunks_;
  std::vector<int> Elements() const {
    std::vector<int> ret;
    for (size_t i=0;i<chunks_;i++){
      uint64_t td = data_[i];
      while (td) {
        ret.push_back(i*BITS + __builtin_ctzll(td));
        td &= ~-td;
      }
    }
    return ret;
  }
};

void Iterate(Bitset bitset) {
  for (int b : bitset.Elements()) {
    std::cout << "bit: " << b << std::endl;
  }
}

上面的实现为迭代提供了干净的代码,但它涉及到向量的不必要的堆分配。以下基本上内联 Elements() 函数的版本通常更快:

void Iterate(Bitset bitset) {
  int chunks = bitset.chunks_;
  for (int i = 0; i < chunks; i++) {
    uint64_t td = bitset.data_[i];
    while (td) {
      std::cout << "bit: " << i*BITS + __builtin_ctzll(td) << std::endl;
      td &= ~-td;
    }
  }
}

什么是实现迭代抽象的好方法,这样它就和上面的版本一样干净,而且没有性能成本。

【问题讨论】:

  • 您不妨看看std::vector&lt;bool&gt; 是如何解决这个确切问题的。
  • 返回一个int的向量很占内存,为什么不写一个迭代器呢?哦,这是你的问题。一个好方法是编写一个beginend 方法并编写一个迭代器。 i*BITS - 什么是“BITS”?为什么i*BITS + __builtin_ctzll(td) 会这样做?它的代码比普通的 value &amp;&amp; ( &lt;&lt; position) 更好吗?我无法理解 i*BITS 能做什么 - i 只会增加并且与 data_[i] 值无关,所以我认为这是一个错误。
  • 一个 C++20 协程?将函数作为参数的 Bitset::for_each?

标签: c++ performance abstraction


【解决方案1】:

只需遍历您的类。为您的Bitset 提供您自己的迭代器类实现,并提供begin()end() 方法。一个最简单(未经测试!)的实现可能如下所示:

#include <vector>
#include <cstdint>
#include <iostream>

struct Bitset {
  uint64_t* data_;
  size_t chunks_;
  struct iterator {
      uint64_t *pnt;
      uint_fast8_t pos;
      iterator(uint64_t *pnt, size_t pos) : 
        pnt(pnt), pos(pos) {}
      bool operator !=(const iterator& o) {
          return o.pnt != pnt || o.pos != pos;
      }
      void operator ++() {
          pos++;
          if (pos == 64) {
              pnt++;
              pos = 0;
          }
      }
      bool operator *() {
          return *pnt & (1 << pos);
      }
  };
  iterator begin() { return iterator(data_, 0); }
  iterator end() { return iterator(data_ + chunks_, 64); }
};

void Iterate(Bitset bitset) {
    for (auto&& b : bitset) {
        std::cout << "bit: " << b << std::endl;
    }
}

我相信对于你奇怪的while (td) { ... i*BITS + __builtin_ctzll(td) ... 循环,我不明白这可能是一些事情(未经测试!):

constexpr int BITS = 100000;
struct Bitset {
  uint64_t* data_;
  size_t chunks_;
  struct iterator {
      uint64_t *data_;
      int i = 0;
      uint64_t td = 0;
      iterator(uint64_t *data_, int i, uint64_t td) :
       data_(data_), i(i), td(td) {}
      bool operator !=(const iterator& o) {
          return o.data_ != data_ || o.i != i || o.td != td;
      }
      void operator ++() {
          if (td == 0) {
              td = *data_;
              data_++;
          } else {
            td &= ~-td;
          }
      }
      bool operator *() {
          return i * BITS + __builtin_ctzll(td);
      }
  };
  iterator begin() { return iterator(data_, 0, *data_); }
  iterator end() { return iterator(data_ + chunks_, 0, 0); }
};

【讨论】:

  • 我的意图是遍历位为 1 的索引。例如,对于“10011”,我会打印 0、3、4。我认为你是对的,我应该看看迭代器,但你的实现并没有真正解决我的问题。
  • 那么最好在你的问题中说明你的意图。哦,我看到你了。
【解决方案2】:

正如 KamilCuk 建议的那样,我使用迭代器来解决这个问题。现在实现看起来像:

struct Bitset {
  uint64_t* data_;
  size_t chunks_;
  class BitsetIterator {
   private:
    const Bitset* const bitset_;
    size_t pos_;
    uint64_t tb_;
   public:
    BitsetIterator(const Bitset* const bitset, size_t pos, uint64_t tb) :
      bitset_(bitset), pos_(pos), tb_(tb) { }
    bool operator!=(const BitsetIterator& other) const {
      return pos_ != other.pos_ || tb_ != other.tb_;
    }
    const BitsetIterator& operator++() {
      tb_ &= ~-tb_;
      while (tb_ == 0 && pos_ < bitset_->chunks_) {
        pos_++;
        if (pos_ < bitset_->chunks_) {
          tb_ = bitset_->data_[pos_];
        }
      }
      return *this;
    }
    int operator*() const {
      return pos_*BITS + __builtin_ctzll(tb_);
    }
  };

  BitsetIterator begin() const {
    size_t pos = 0;
    while (pos < chunks_ && data_[pos] == 0) {
      pos++;
    }
    if (pos < chunks_) {
      return BitsetIterator(this, pos, data_[pos]);
    } else {
      return BitsetIterator(this, pos, 0);
    }
  }
  BitsetIterator end() const {
    return BitsetIterator(this, chunks_, 0);
  }
};

void Iterate(Bitset bitset) {
  for (int b : bitset) {
    std::cout << "bit: " << b << std::endl;
  }
}

这避免了堆分配,并且比使用向量的版本快得多。我不确定这是否提供与没有任何抽象的版本完全相同的性能,但应该非常接近。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2015-09-23
    • 2018-08-19
    • 2023-03-10
    • 2015-01-19
    • 2011-05-13
    • 1970-01-01
    • 2012-11-20
    • 1970-01-01
    相关资源
    最近更新 更多