2014-01-20 29 views
11

对应std::bitset对应于无符号整数表示的比较(它应该适用于more than 64 bits的位集),实现<运算符的最优化方式是什么?比较位集的最快方法(<位操作符)?

一个简单的实现将是:

template<std::size_t N> 
bool operator<(const std::bitset<N>& x, const std::bitset<N>& y) 
{ 
    for (int i = N-1; i >= 0; i--) { 
     if (x[i] && !y[i]) return false; 
     if (!x[i] && y[i]) return true; 
    } 
    return false; 
} 

当我说“最优化的方式:”我期待使用按位运算和元编程技巧(之类的)的实现。

编辑:我认为我已经找到了技巧:模板元编程为编译时递归和正确的bitshift为了比较bitsets作为几个无符号的长整型。但没有清楚的想法如何做...

+0

关于你的想法无关分支使用正确的bitshift:这将创建大量的中间对象,并且to_ullong将必须检查移位后的值是否适合每个检查的“无符号long long”,从而创建相当的s头顶上。我怀疑它会更快,但只有基准可以证明这一点。 –

+1

复制std :: bitset的代码,重命名它,给它一种访问某个单词的方法。 –

+1

@brianbeuning如果你正在复制代码,你可以简单地提供一个可以访问内部的'operator <'。 –

回答

10

明显的优化将是

template<std::size_t N> 
bool operator<(const std::bitset<N>& x, const std::bitset<N>& y) 
{ 
    for (int i = N-1; i >= 0; i--) { 
     if (x[i]^y[i]) return y[i]; 
    } 
    return false; 
} 

除此之外,它应该是完全不可能的,因为没有访问它们符合标准的方式来使用更加位每测试。你可以基准x.to_string() < y.to_string()和希望为to_string()和字符串比较优化比按位访问bitset更好,但这是一个长镜头。

+0

@dyp谁知道。这是一个性能问题,所以最终你必须对它进行基准测试。它可能随每个编译器版本而改变。如果考虑“小”比特集,也可以使用'to_ullong'专门为<= 64比特,但我想这个问题的精神更像是几百比特。 –

+0

+1对于其尺寸的解决方案,很难做得更好。有关模板递归版本,请参阅下文。 – user

+0

请注意,即使'的std :: bitset的'会暴露一些'。数据()'成员,从标准集装箱的词典式排序和'的std :: tuple'是很难用这些知识来优化。要做的诱人的事情是对底层词表示进行整数比较,但实际上对应于* reverse colexicographical *排序。您可以将'std :: lexicographical_compare(rbegin(R.data),rend(R.data),rbegin(L.data),rend(L.data))'用作'operator <(L,R)'。 “反向”对应于左/右反转,并且“反向反应”中的“反向”对应于反向迭代器。 – TemplateRex

2

如何检查XOR的最高位?

bool operator<(const std::bitset<N>& x, const std::bitset<N>& y) 
{ 
    return y[fls(x^y)] 
} 

int fls(const std::bitset<N>& n) { 
    // find the last set bit 
} 

fps一些想法可以在这里找到http://uwfsucks.blogspot.be/2007/07/fls-implementation.html

+0

好主意,除了负运算符没有为位集定义... – Vincent

+0

问题:优化'fls'需要内部访问bitset,就像原始问题一样。 –

4

我只是看了看源代码,但不幸的是(除非,希望我错了),他们似乎并没有让你就位访问特定位块的const & unsigned long。如果他们这样做了,那么你可以执行模板递归,并且有效地比较每个unsigned long而不是无符号长整数中的每一位。

毕竟,如果A < B,那么不仅应该每个最高有效位a <= b,也是每个最重要的块A[i] <= B[i]

我讨厌这么说,但我可能会用C++ 11的std::array递归递归。如果你有权访问这些块,那么你可以做一个模板递归函数来做到这一点很容易(因为你确实知道你知道元编程),所以给编译器一个很好的机会来优化。

总而言之,这不是一个好的答案,但那就是我会做的。顺便说一下,这是一个很好的问题。

===========

编辑

这应该时间三种方法:将一个使用最新的upvotes,我描述区块策略和模板递归变种。我用bitsets填充矢量,然后使用指定的比较器函子重复排序。

快乐黑客!

输出我的电脑上:

 
RUNTIMES: 
compiled g++ -std=c++11 -Wall -g test.cpp 
    std::bitset   4530000 (6000000 original in OP) 
    Block-by-block  900000 
    Template recursive 730000 

compiled g++ -std=c++11 -Wall -g -O3 test.cpp 
RUNTIMES: 
    std::bitset   700000 (740000 original in OP) 
    Block-by-block  470000 
    Template recursive 530000 

C++代码11:

#include <iostream> 
#include <bitset> 
#include <algorithm> 
#include <time.h> 

/* Existing answer. Note that I've flipped the order of bit significance to match my own */ 
template<std::size_t N> 
class BitByBitComparator 
{ 
public: 
    bool operator()(const std::bitset<N>& x, const std::bitset<N>& y) const 
    { 
    for (int i = 0; i < N; ++i) { 
     if (x[i]^y[i]) return y[i]; 
    } 
    return false; 
    } 
}; 

/* New simple bit set class (note: mostly untested). Also note bad 
    design: should only allow read access via immutable facade. */ 
template<std::size_t N> 
class SimpleBitSet 
{ 
public: 
    static const int BLOCK_SIZE = 64; 
    static const int LOG_BLOCK_SIZE = 6; 
    static constexpr int NUM_BLOCKS = N >> LOG_BLOCK_SIZE; 
    std::array<unsigned long int, NUM_BLOCKS> allBlocks; 
    SimpleBitSet() 
    { 
    allBlocks.fill(0); 
    } 
    void addItem(int itemIndex) 
    { 
    // TODO: can do faster 
    int blockIndex = itemIndex >> LOG_BLOCK_SIZE; 
    unsigned long int & block = allBlocks[blockIndex]; 
    int indexWithinBlock = itemIndex % BLOCK_SIZE; 
    block |= (0x8000000000000000 >> indexWithinBlock); 
    } 
    bool getItem(int itemIndex) const 
    { 
    int blockIndex = itemIndex >> LOG_BLOCK_SIZE; 
    unsigned long int block = allBlocks[blockIndex]; 
    int indexWithinBlock = itemIndex % BLOCK_SIZE; 
    return bool((block << indexWithinBlock) & 0x8000000000000000); 
    } 
}; 

/* New comparator type 1: block-by-block. */ 
template<std::size_t N> 
class BlockByBlockComparator 
{ 
public: 
    bool operator()(const SimpleBitSet<N>& x, const SimpleBitSet<N>& y) const 
    { 
    return ArrayCompare(x.allBlocks, y.allBlocks); 
    } 

    template <std::size_t S> 
    bool ArrayCompare(const std::array<unsigned long int, S> & lhs, const std::array<unsigned long int, S> & rhs) const 
    { 
    for (int i=0; i<S; ++i) 
     { 
    unsigned long int lhsBlock = lhs[i]; 
    unsigned long int rhsBlock = rhs[i]; 
    if (lhsBlock < rhsBlock) return true; 
    if (lhsBlock > rhsBlock) return false; 
     } 
    return false; 
    } 
}; 

/* New comparator type 2: template recursive block-by-block. */ 
template <std::size_t I, std::size_t S> 
class TemplateRecursiveArrayCompare; 

template <std::size_t S> 
class TemplateRecursiveArrayCompare<S, S> 
{ 
public: 
    bool operator()(const std::array<unsigned long int, S> & lhs, const std::array<unsigned long int, S> & rhs) const 
    { 
    return false; 
    } 
}; 

template <std::size_t I, std::size_t S> 
class TemplateRecursiveArrayCompare 
{ 
public: 
    bool operator()(const std::array<unsigned long int, S> & lhs, const std::array<unsigned long int, S> & rhs) const 
    { 
    unsigned long int lhsBlock = lhs[I]; 
    unsigned long int rhsBlock = rhs[I]; 
    if (lhsBlock < rhsBlock) return true; 
    if (lhsBlock > rhsBlock) return false; 

    return TemplateRecursiveArrayCompare<I+1, S>()(lhs, rhs); 
    } 
}; 

template<std::size_t N> 
class TemplateRecursiveBlockByBlockComparator 
{ 
public: 
    bool operator()(const SimpleBitSet<N>& x, const SimpleBitSet<N>& y) const 
    { 
    return TemplateRecursiveArrayCompare<x.NUM_BLOCKS, x.NUM_BLOCKS>()(x.allBlocks, y.allBlocks); 
    } 
}; 

/* Construction, timing, and verification code */ 
int main() 
{ 
    srand(0); 

    const int BITSET_SIZE = 4096; 

    std::cout << "Constructing..." << std::endl; 

    // Fill a vector with random bitsets 
    const int NUMBER_TO_PROCESS = 10000; 
    const int SAMPLES_TO_FILL = BITSET_SIZE; 
    std::vector<std::bitset<BITSET_SIZE> > allBitSets(NUMBER_TO_PROCESS); 
    std::vector<SimpleBitSet<BITSET_SIZE> > allSimpleBitSets(NUMBER_TO_PROCESS); 
    for (int k=0; k<NUMBER_TO_PROCESS; ++k) 
    { 
     std::bitset<BITSET_SIZE> bs; 
     SimpleBitSet<BITSET_SIZE> homemadeBs; 
     for (int j=0; j<SAMPLES_TO_FILL; ++j) 
    { 
     int indexToAdd = rand()%BITSET_SIZE; 
     bs[indexToAdd] = true; 
     homemadeBs.addItem(indexToAdd); 
    } 

     allBitSets[k] = bs; 
     allSimpleBitSets[k] = homemadeBs; 
    } 

    clock_t t1,t2,t3,t4; 
    t1=clock(); 

    std::cout << "Sorting using bit-by-bit compare and std::bitset..." << std::endl; 
    const int NUMBER_REPS = 100; 
    for (int rep = 0; rep<NUMBER_REPS; ++rep) 
    { 
     auto tempCopy = allBitSets; 
     std::sort(tempCopy.begin(), tempCopy.end(), BitByBitComparator<BITSET_SIZE>()); 
    } 

    t2=clock(); 

    std::cout << "Sorting block-by-block using SimpleBitSet..." << std::endl; 
    for (int rep = 0; rep<NUMBER_REPS; ++rep) 
    { 
     auto tempCopy = allSimpleBitSets; 
     std::sort(tempCopy.begin(), tempCopy.end(), BlockByBlockComparator<BITSET_SIZE>()); 
    } 

    t3=clock(); 

    std::cout << "Sorting block-by-block w/ template recursion using SimpleBitSet..." << std::endl; 
    for (int rep = 0; rep<NUMBER_REPS; ++rep) 
    { 
     auto tempCopy = allSimpleBitSets; 
     std::sort(tempCopy.begin(), tempCopy.end(), TemplateRecursiveBlockByBlockComparator<BITSET_SIZE>()); 
    } 

    t4=clock(); 

    std::cout << std::endl << "RUNTIMES:" << std::endl; 
    std::cout << "\tstd::bitset  \t" << t2-t1 << std::endl; 
    std::cout << "\tBlock-by-block  \t" << t3-t2 << std::endl; 
    std::cout << "\tTemplate recursive \t" << t4-t3 << std::endl; 
    std::cout << std::endl; 

    std::cout << "Checking result... "; 
    std::sort(allBitSets.begin(), allBitSets.end(), BitByBitComparator<BITSET_SIZE>()); 
    auto copy = allSimpleBitSets; 
    std::sort(allSimpleBitSets.begin(), allSimpleBitSets.end(), BlockByBlockComparator<BITSET_SIZE>()); 
    std::sort(copy.begin(), copy.end(), TemplateRecursiveBlockByBlockComparator<BITSET_SIZE>()); 
    for (int k=0; k<NUMBER_TO_PROCESS; ++k) 
    { 
     auto stdBitSet = allBitSets[k]; 
     auto blockBitSet = allSimpleBitSets[k]; 
     auto tempRecBlockBitSet = allSimpleBitSets[k]; 

     for (int j=0; j<BITSET_SIZE; ++j) 
    if (stdBitSet[j] != blockBitSet.getItem(j) || blockBitSet.getItem(j) != tempRecBlockBitSet.getItem(j)) 
     std::cerr << "error: sorted order does not match" << std::endl; 
    } 
    std::cout << "success" << std::endl; 

    return 0; 
} 
4

虽然你说的位设置,你不是真的在谈论任意精度的无符号整数比较。如果是这样,那么你可能不会轻松做得更好,然后包装GMP。

从他们的网站:

GMP经过精心设计,以尽可能快的,既为小 操作数和巨大的操作数。速度是通过使用 fullwords作为基本算术类型来实现,通过使用快速算法,与 用于为 很多CPU的最常见的内环高度优化汇编代码,并通过一般的强调速度。

考虑their integer functions

2

如果你愿意采用的解决方案,如果STL bitset的变化,你可以使用

template<int n> 
bool compare(bitset<n>& l, bitset<n>& r){ 
    if(n > 64){ 
    typedef array<long, (n/64)> AsArray; 
    return *reinterpret_cast<AsArray*>(&l) 
     < *reinterpret_cast<AsArray*>(&r); 
    }//else 
    return l.to_ulong() < r.to_ulong(); 
} 

编译器会引发的,如果离开