boost :: unordered_map :: find根据编译器优化级别产生不同的结果,而boost :: unordered_map :: insert产生相同的结果

时间:2015-04-21 14:03:01

标签: c++ gcc boost compiler-optimization

使用gcc 4.8.1和libboost 1.53我会得到不同的结果,具体取决于我用来编译代码的优化级别。 作为较大程序的一部分,函数insertValues对同一akeyvalue执行两次:

/* Complex classes */
class A { /*....*/ }
class Value { /*.....*/ }
class SortedVector : public std::vector<T> { /*.....*/ }

/* Hash for the key */
struct KeyHash : std::unary_function<Key, size_t> {
    size_t operator()(Key const& x) const {
        size_t hash = x.get<0>();
        SortedVector<int>* xNumbers = x.get<2>();
        if(xNumbers != NULL) {
            BOOST_FOREACH(int num, *xNumbers) {
                MurmurHash3_x86_32(&num, sizeof(size_t), hash, &hash);
            }
        }
        return hash;
    }
};

/* Equals for the key */
struct KeyEqual : std::binary_function<Key, Key, bool> {
    size_t operator()(Key const& x, Key const& y) const {
        if(x.get<0>() != y.get<0>() || fabs(x.get<1>() - y.get<1>()) > 0.00001 || x.get<3>() != y.get<3>()) {
            return false;
        }

        SortedVector<int>* xNumbers = x.get<2>();
        SortedVector<int>* yNumbers = y.get<2>();
        if(xNumbers == yNumbers) {
            return true;
        }

        if(xNumbers == NULL || yNumbers == NULL) {
            return false;
        }

        if(xNumbers->size() != yNumbers->size()) {
            return false;
        }
        return std::equal(xNumbers->begin(), xNumbers->end(), yNumbers->begin());
    }
};

/* typedefs */
typedef boost::tuple<int, double, SortedVector<int>*, int> Key;
typedef boost::unordered_map<A, boost::unordered_map<Key, Value*, KeyHash, KeyEqual>, A::Hash, A::Equal> Map;

/* code that shows the problem */
void insertValues(A a, Key key, Value* value) {
    Map::iterator iter = map->find(a);
    if (iter == map->end()) {
        iter = map.insert(std::make_pair(a, Map::value_type::second_type())).first;
    }

    Map::value_type::second_type::iterator keyIter = iter->second.find(key);

    if (keyIter == iter->second.end()) {
        iter->second.insert(std::make_pair(key, value));
    }
}

-O2一起编译keyIter始终等于iter->second.end(),表示key, value对不在地图中。但是,第二次运行时,insert不会插入该对。 在咨询insert findfind之后,我得出结论,虽然insert找不到对,-O1找到它并拒绝插入。

使用find编译代码按预期工作。

有没有人有洞察力,为什么-O1可能会产生与-O2find不同的结果?或者为什么insert和{{1}}的查找结果会有所不同?

哈希使用来自boost documentation的MurmurHash3_x86_32。 该系统是OpenSuse x86_64 GNU / Linux。

2 个答案:

答案 0 :(得分:5)

最可能的错误来源是你对哈希函数的调用:

        BOOST_FOREACH(int num, *xNumbers) {
            MurmurHash3_x86_32(&num, sizeof(size_t), hash, &hash);
        }

您使用的是OpenSuse x86_64 GNU / Linux,这是一个LP64平台,因此int为32位,而size_t(和long)为64位宽。在the implementation of MurmurHash3_x86_32中,key以字节方式访问,也由32位块(uint32_t)访问。这是正常的,因为it's allowed to alias signed and unsigned variants of the same integral type(以及任何平滑的可复制类型字节),uint32_t必须是unsigned int,因为x86_64 Linux上没有其他基本的无符号32位整数类型。

但是,此代码中存在两个错误。首先,len参数应该是key的大小,但sizeof(size_t)在您的平台上为8,而int num的大小为4个字节。这意味着哈希函数将读取未定义的内存位置(&num + 1),并且优化编译器可以自由地为此读取提供任何值或导致其他未定义的行为。

其次,您提供&hash作为out参数。但是MurmurHash3_x86_32*out写为uint32_t,而size_t不能将uint32_t写为别名,因为这两种类型是不同的算术类型,而不是有符号/无符号变体。这意味着对hash的写入具有未定义的行为,并且优化编译器可以自由地忽略此写入或导致其他未定义的行为。

这样的事情会更正确:

        std::uint32_t result;
        MurmurHash3_x86_32(xNumbers->data(),
            sizeof(*xNumbers->data()) * xNumbers->size(),
            hash, &result);
        hash ^= (static_cast<std::uint32_t>(hash) ^ result);

请注意,与您的代码相反,这会在对哈希函数的单个调用中提供所有xNumbers。这保证在vector连续存储其元素时起作用,并且更接近于如何使用散列函数;它的设计不是为了重复调用。

答案 1 :(得分:0)

您的条件fabs(x.get<1>() - y.get<1>()) > 0.00001可以使不同的对象看起来与容器相同。你应该说x.get<1>() != y.get<1>()