2016-06-15 14 views
1

std :: unordered_mapで自己定義されたハッシュ関数を使用することについて、私は非常に奇妙な問題があります。std :: unordered_mapで使用されるstd :: arrayをハッシュします。

私のキータイプはint64よりも大きいので、私はそれを表現するためにstd :: arrayを使用します。 はそれをハッシュの値を取得するために、私はMyHashクラスを作成します。

class MyHash 
{ 
public: 
    std::size_t operator()(const std::array<char, 12>& oid) const 
    { 
     Convert t; 
     std::memcpy(t.arr, oid.data(), 12); 
     std::cout << t.a <<" "<<t.b << std::endl; 
     return (std::hash<std::int32_t>()(t.a)^(std::hash<std::int64_t>()(t.b) << 1)) >> 1; 
    } 
    union Convert { 
     struct { 
      std::int32_t a; 
      std::int64_t b; 
     }; 
     char arr[12]; 
    }; 
}; 

まず、それをテスト:

std::array<char, 12> arr = {1,2,3,4,5,6,7,8,9,10,11,12}; 
MyHash o; 
o(arr); 
o(arr); 

それはOKです。それは同じt.at.bを印刷します。今のstd :: unordered_mapとそれを使用します。

std::unordered_map<std::array<char, 12>, int, MyHash> map; 
std::array<char, 12> arr = {1,2,3,4,5,6,7,8,9,10,11,12}; 
map.insert(std::make_pair(arr, 1)); 
auto it = map.find(arr); 
if(it == map.end()) 
    std::cout << "error"; 
else 
    std::cout << it->second; 

さて、それはerrorを印刷しますが、その理由は、インサート内t.bは検索と異なるです。そして、これだけリリースモード(またはg ++ O2)対で起こる

+0

私はむしろ、例えば使用しているすべての12バイトのハッシュを計算したいです'boost :: hash_range':http://coliru.stacked-crooked.com/a/cddb1ea79a18d0b1 –

+0

まず、私はそれを使用しますが、それは12倍のハッシュを行うため、少し遅いことがわかりました。そして配列の最初の4バイトのために品質が良くないのは、最初の4バイトが常に同じ場合は – jean

+0

の値がほぼ同じです。ハッシュを計算するときにそれらをスキップするだけです( 'boost :: hash_range(oid.begin() +4、oid.end()); ');あなたは時間差を測定しましたか?あなたのmemcpy/unionアプローチよりどれだけ遅いですか? –

答えて

2

、梱包およびアライメントの問題、:

#include <cstdint> 
#include <cstring> 
#include <array> 

std::size_t array_hash(const std::array<char, 12>& array) { 
    std::uint64_t u64; 
    std::memcpy(&u64, array.data(), 8); 
    std::uint32_t u32; 
    std::memcpy(&u32, array.data() + 8, 4); 
    // return (std::hash<std::uint32_t>()(u32)^(std::hash<std::uint64_t>()(u64) << 1)) >> 1;; 
    return u64 + u32; // for simplicity 
} 

std::size_t uint_hash(std::uint64_t u64, std::uint32_t u32) { 
    // return (std::hash<std::uint32_t>()(u32)^(std::hash<std::uint64_t>()(u64) << 1)) >> 1;; 
    return u64 + u32; // for simplicity 
} 

(G ++バージョン4.8.4)G ++ -S --std = C++ 11 -O3を使用すると取得します:

_Z10array_hashRKSt5arrayIcLm24EE: 
.LFB914: 
     .cfi_startproc 
     movl 8(%rdi), %eax 
     addq (%rdi), %rax 
     ret 
     .cfi_endproc 

とかなり最適です

_Z9uint_hashmj: 
.LFB915: 
     .cfi_startproc 
     movl %esi, %eax 
     addq %rdi, %rax 
     ret 
     .cfi_endproc 

...。

も参照してください:Type Punning, Strict Aliasing, and Optimization

+0

+1 UBを回避し、これがどれほど揮発性であるかに関する警告にリンクしようと努力しています。私は 'reinterpret_cast'が私を落ち着かせるのを見て、何か不確実性があれば' memcpy'を使うことを知っていることに喜んでいます。さらに、あなたが示したように、コンパイラは 'memcpy'を' reinterpret_cast'から期待するものに最適化しますが、後者とは異なり、前者の定義された動作を保証します。両方の世界の最高! –

1

abの間かもしれないだけでなくパック余分なバイトコンパイラのは、この

union Convert { 
     struct { 
      std::int32_t a; 
      std::int64_t b; 
     }; 
     char arr[12]; 
    }; 

を見てみましょう。そのため、char配列をペイントするタイプは、必ずstructの一部をオーバーレイするとは限りません。型打ちは、C++での境界線の未定義動作です。私はだと思いますが、あなたはこの特定の例では大丈夫です。

リリースビルドの梱包方法は、デバッグビルドとは異なるようです。

多くのコンパイラでは、梱包の手配(#pragma pack?)を指定することができますが、コンパイラの最適化戦略を破っており、本質的に非標準のC++であるため、私はあなたであったとしても頼りません。

+0

'a'と' b'の順序を入れ替えます64ビットintは8バイト整列する必要があり、32ビットintは4バイト整列する必要があるだけなので、その間のパディングは削除されます。 –

+0

それはしないかもしれません。特に128ビットのチップで。 – Bathsheba

+0

メモリレイアウトパディングはコンパイル時に決定してはいけませんか? char配列はaとbを完全にオーバーレイすることはできませんが、実行時に変更されるのはなぜですか?それは、unordered_mapハッシュオブジェクトとして使用する場合にのみ発生しますか? – jean

0

これはハックのビットですが、あなたはそれを試してみて、それがどのように動作するかを見ることができる:

struct MyHash { 
    std::size_t operator()(const std::array<char, 12>& oid) const { 
     auto d = reinterpret_cast<const std::uint32_t*>(oid.data()); 
     std::size_t prime = 31; 
     std::size_t other_prime = 59; 
     return d[2] + other_prime*(d[1] + prime*d[0]); 
    } 
}; 

これだけ作品12はsizeof(uint32_t)あなたの心の倍数であるため。サイズが変わった場合は、調整する必要があります。あなたは、個々の整数にコピーすることが未定義の動作を避けるために

関連する問題