wyhash
東北銘菓
女房が普段行かないスーパーへ行ったら、物産展と言うか、東北銘菓の展示即売会をやってたそうな。客集めで、駅弁とか空弁とかはたまに見るけど、菓子ってのは珍しい。
どんな物が有ったかと言うと、仙台の萩の月、岩手の南部せんべい、更に、かもめの玉子、福島の薄皮饅頭とかが並んでいたそうな。
で、迷わずに、萩の月をGet。池袋にある宮城県のアンテナショップでも、入荷すると直ぐに売れてしまうそうですから、貴重な月ですな。こんな田舎に出回るなんて、超珍しいぞ。
名月を 取ってくれろと 泣く子かな
by 小林 一茶
現代風だと、名月を 買ってくれろと 泣く子かな に、なるかな。
ああ、東北と言うと、新幹線の社内販売で、ホヤの燻製を大量に買ったのを思い出すな。たまには、食べてみたい。元祖の牛タンもね。テールスープ美味かったなあ。
wyhash
ひょんな事からwyhashってのを知った。2年前にリリースされた注目株。使いかたのサンプルがちょろっと出てたけど、生憎Cフラフラで書かれていた。しょうがないので普通言語に直して、前回のテストプログラムに組み込んでみた。下記はその追加部分。
#include "wyhash.h"
int wy(const char *s, int n) {
uint64_t _wyp[4];
make_secret(1126, _wyp);
uint64_t h = wyhash(s, strlen(s), 0, _wyp);
return h % n;
}
実用には、毎回実行されてる make_secret を外に出すべきだろう。そうでないと時間ばかりかかって苦痛だ。一度やっとけば済むものだからね。イイフロは、SEEDになる。好きな値を設定してくれ。相変わらず、語呂合せが好きだのう。
wy fnv
Mean: 1027.7400 1027.7400
Std Dev: 34.2230 32.6397
Minimum: 954.0000 [ 14] 940.0000 [ 52]
Maximum: 1115.0000 [ 67] 1122.0000 [ 53]
Quartile: 1005.5000 1006.0000
Median: 1031.0000 1026.0000
Quartile: 1051.5000 1048.5000
上記の結果はdebian(64Bit)のもの。最近のハッシュ関数では、バケット数を素数にしとけって制限は無いようだ。このデータは、バケット数100でのものだ。
関数は、wyhash.hの中に細密っぽく書かれている。まるで、見なくてもいいよと言ってるみたい(javascriptもそういうの多いね)。でも、覗いてみたくなるのが人情ってものです。そんなあなたには、
indent -i4 wyhash.h
ぐらいがお勧め。-krもつけて、昔の書式にするのも有り。あのK&R教科書風になるぞ。
/* quick example:
string s="fjsakfdsjkf";
uint64_t hash=wyhash(s.c_str(), s.size(), 0, _wyp);
*/
//the default secret parameters
static const uint64_t _wyp[4] =
{ 0xa0761d6478bd642full, 0xe7037ed1a0b428dbull, 0x8ebc6af09c88c6e3ull,
0x589965cc75374cc3ull };
冒頭から、こんな案内が出てたぞ。こんな初期値を内蔵してるんだね。
//The wyrand PRNG that pass BigCrush and PractRand
static inline uint64_t
wyrand (uint64_t * seed)
{
*seed += 0xa0761d6478bd642full;
return _wymix (*seed, *seed ^ 0xe7037ed1a0b428dbull);
}
検定に合格した乱数発生器も内蔵してます。
/* This is world's fastest hash map: 2x faster than bytell_hash_map.
It does not store the keys, but only the hash/signature of keys.
First we use pos=hash1(key) to approximately locate the bucket.
Then we search signature=hash2(key) from pos linearly.
If we find a bucket with matched signature we report the bucket
Or if we meet a bucket whose signature=0, we report a new position to insert
The signature collision probability is very low as we usually searched N~10 buckets.
そして、コメントとして、hash mapはうちの方法を使えば、世界で一番速いと自慢してましたよ。
32Bit system
#include "wyhash32.h"
int wy(const char *s, int n){
uint32_t h=wyhash32(s, strlen(s), 0);
return h % n;
}
OpenBSD(32Bit) butket size: 100
wy fnv
Mean: 2359.7100 2359.7100
Std Dev: 46.4589 50.2443
Minimum: 2260.0000 [ 76] 2243.0000 [ 46]
Maximum: 2482.0000 [ 33] 2513.0000 [ 53]
Quartile: 2330.5000 2326.0000
Median: 2360.0000 2356.5000
Quartile: 2389.5000 2393.5000
hash(map) of go
前回はたまたまgoをやったので、どうなってるか怖いもの見たさで調べてみる。野次馬根性ですな。
golangでhashと言うと、暗号系になっちゃうんで、今話題にしてる奴としては、mapと銘打って区別してる。インターネットを生業にする会社だと、暗号が超重要ってのが、なんだか透けて見えるな。同時に、このhash技術は、goで言うmapと地続きってのがよく分る。
普段goなんて使わないものだからmapについて調べておく。要するにぐぐるんだな。 逆引きGolang (マップ)
sakae@deb:/usr/local/go/src$ find . -name '*hash*' ./hash ./hash/maphash ./hash/maphash/maphash_test.go ./hash/maphash/smhasher_test.go ./hash/maphash/maphash.go ./hash/hash.go ./runtime/hash32.go ./runtime/hash_test.go ./runtime/hash64.go ./cmd/go/internal/cache/hash_test.go ./cmd/go/internal/cache/hash.go ./cmd/go/testdata/script/mod_get_hash.txt ./cmd/go/testdata/script/mod_get_missing_ziphash.txt ./cmd/go/testdata/script/mod_download_hash.txt ./cmd/vendor/golang.org/x/mod/sumdb/dirhash ./cmd/vendor/golang.org/x/mod/sumdb/dirhash/hash.go
色々出てきてるけど、32,64とわざわざ注釈めいた名前になってるのが怪しいな。runtime/hash64.goを見る。
// Hashing algorithm inspired by // wyhash: https://github.com/wangyi-fudan/wyhash
こんなコメントが出てた。go 1.17から採用されたようだ。で、ざっと見したけど、ピンとこない。ぐぐれ。 goの魔改造が間に合わなかったのでそのついでに理解したmapの実装についての話 ああ、そろそろAdvent Calendarの季節だな。
from comment
//#include <sys/time.h>
//#include <fstream>
//#include <vector>
#include <iostream>
#include <cstdlib>
#include <stdint.h>
#include <cstring>
#include "wyhash.h"
using namespace std;
int main () {
uint64_t _wyp[4];
make_secret(12345,_wyp);
string s="fcdskhfjs";
uint64_t h=wyhash(s.c_str(),s.size(),0,_wyp);
cout << h; cout << "\n";
}
githubのREADMEに出てたのを苦労して、Cフラフラ語でコンパイル出来るようにした。初期値はtime(NULL)だったんだけど、余計な物を省いていって、上記に落ち着いた。Cフラフラを避けて通るのもあれなんで、速習してみるか。
c++ vector using namespace std こんなオイラーの知らない語を入れてググル。
C++ 素朴な疑問 using namespace std; は使わないほうが良い?
選び方に偏りが有るのは、承知の上です。
STLの第三弾として連想配列 Map なんてのに行き当った。STL了解しました。良く使う標準機能の事なのね。それから、iostreamはstdio.hな訳ね。stringってのはSTLに昇格してC言語とは違うんですって、訴えてる訳か。
もう一発、コメントに隠れていたコード片を発掘してきて、組み立ててみた。色々とエラーが出てきて、それを潰したよ。最初にnamespaceは使わない約束にした。それで一苦労。mainに相当するのが先に截てて、その後ろに関数。おかげで、タイプを取り違えていた。このあたりは、C言語の習性を引き摺っているんだね。
で、こんなエラーが出てきた。
sakae@pen:/tmp/wyhash$ g++ map.cpp
map.cpp: In function ‘int main()’:
map.cpp:50:80: error: invalid conversion from ‘const uint64_t*’ {aka ‘const long unsigned int*’} to ‘uint64_t*’ {aka ‘long unsigned int*’} [-fpermissive]
50 | = wyhashmap(idx.data(), idx.size(), key.c_str(), key.size(), 1, _wyp);
| ^~~~
| |
| const uint64_t* {aka const long unsigned int*}
map.cpp:14:108: note: initializing argument 6 of ‘size_t wyhashmap(wyhashmap_t*, size_t, const void*, size_t, uint8_t, uint64_t*)’
14 | const void *key, size_t key_size, uint8_t insert, uint64_t * secret) {
| ~~~~~~~~~~~^~~~~~
:
なんか、意味不明。セカンドオピニオンとしてOpenBSDのc++で試したら、呼び出した関数にマッチングするものが無いぞ。候補として、wyhashmap関数の第6引数を何とかすれば、何とかなるかもって言われた。目を皿のようにしてみたよ。
#ifdef WYHASHMAP_WEAK_SMALL_FAST
// for small hashmaps whose size < 2^24 and acceptable collision
typedef uint32_t wyhashmap_t;
#else
typedef uint64_t wyhashmap_t;
#endif
static inline size_t
wyhashmap(wyhashmap_t * idx, size_t idx_size, const void *key, size_t key_size, uint8_t insert, const uint64_t * secret) {
:
int main() {
const size_t size = 213432;
std::vector <wyhashmap_t> idx(size, 0);
//allocate the index of fixed size.idx MUST be ZERO.
std::vector <int> value(size);
//we only care about the index, user should maintain his own value vectors.
std::string key = "dhskfhdsj";
//the object to be inserted into idx
size_t pos = wyhashmap(idx.data(), idx.size(), key.c_str(), key.size(), 1, _wyp);
//get the position and insert
:
constも有ると無いでは大違い! シグネチャが違うものになるんだね。Cフラフラ語、面倒くさい。
c++ study
こんなのも見つつ、
C++ 動的配列クラス std::vector 入門 もう一発、コメントから発掘してみる。gitしてきた中にある古いやつ wyhash_final2.h からね。
#include "wyhash.h"
#include <iostream>
#include <string>
#include <vector>
using namespace std;
typedef uint64_t wyhashmap_t;
static inline uint64_t
wyhashmap (wyhashmap_t * keys, uint64_t size, wyhashmap_t hash) {
uint64_t i0 = wy2u0k (hash, size), i;
for (i = i0; i < size && keys[i] && keys[i] != hash; i++);
if (i < size)
return i;
for (i = 0; i < i0 && keys[i] && keys[i] != hash; i++);
return i < i0 ? i : size; // return size if out of capacity
}
int main () {
const uint64_t size = 1ull << 20;
wyhashmap_t *idx = (wyhashmap_t *) calloc (size, sizeof (wyhashmap_t));
vector < int > value (size);
vector < string > keys (size);
string key = "dhskfhdsj";
wyhashmap_t hash_of_key = wyhash (key.c_str (), key.size (), 0, _wyp);
uint64_t pos = wyhashmap (idx, size, hash_of_key);
if (idx[pos])
value[pos]++;
else {
idx[pos] = hash_of_key;
keys[pos] = key;
value[pos] = 0;
}
free (idx);
}
こちらの方が、実装が分かりやすいかな。
コメントの冒頭部分
This is world's fastest hash map: 2X~3X faster than bytell_hash_map. It is a probabilistic hashmap with very low error rate, please DO NOT use it in any serious tasks. It does not store the keys, but only the hash of keys. If hash(key1)==hash(key2), we are almost sure that key1==key2. Prob(Collision)=2^-64 * N * (N-1)/2, where N the number of objects stored. For 1 million keys, Prob(Colision)=2^-25, which is very safe For 16 million keys, Prob(Colision)=2^-17, which is safe For 256 million keys, Prob(Colision)=2^-9, a bit worry For 1 billion keys, Prob(Colision)=2^-5, worry but not die For more keys, define wyhashmap128 and use double hash functions to construct 128 bit keys which is very safe
比較対象に挙げられていた実装 更に世界のハッシュテーブル c++を勉強するには、良いサイトかな。
at Debian(64Bit)
(gdb)
24 vector < string > keys (size);
(gdb)
25 string key = "dhskfhdsj";
(gdb)
26 wyhashmap_t hash_of_key = wyhash (key.c_str (), key.size (), 0, _wyp);
(gdb) p key
$1 = "dhskfhdsj"
(gdb) p keys
$2 = std::vector of length 1048576, capacity 1048576 = {"", "", "", "", "",
"", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "",
:
"", "", "", "", ""...}
(gdb) n
27 uint64_t pos = wyhashmap (idx, size, hash_of_key);
(gdb) n
28 if (idx[pos])
(gdb) p hash_of_key
$3 = 15510592570693831727
(gdb) p pos
$4 = 881675
OpenBSD(64Bit)
24 string key = "dhskfhdsj";
(gdb) n
25 wyhashmap_t hash_of_key = wyhash (key.c_str (), key.size (), 0, _wyp
);
(gdb) n
26 uint64_t pos = wyhashmap (idx, size, hash_of_key);
(gdb) n
27 if (idx[pos])
(gdb) p key
$1 = {<std::__1::__basic_string_common<true>> = {<No data fields>},
static __short_mask = 1, static __long_mask = 1,
__r_ = {<std::__1::__compressed_pair_elem<std::__1::basic_string<char, std::__
1::char_traits<char>, std::__1::allocator<char> >::__rep, 0, false>> = {
__value_ = {{__l = {__cap_ = 7235145413054456850,
__size_ = 6045149719155,
__data_ = 0x40a0d1786bb38785 <error: Cannot access memory at address
0x40a0d1786bb38785>}, __s = {{__size_ = 18 '\022', __lx = 18 '\022'},
__data_ = "dhskfhdsj\000\177\177\005\000\000\205\207\263kxѠ@"},
__r = {__words = {7235145413054456850, 6045149719155,
4656952329834301317}}}}}, <std::__1::__compressed_pair_elem<std::_
_1::allocator<char>, 1, true>> = {<std::__1::allocator<char>> = {<No data fields
>}, <No data fields>}, <No data fields>}, static npos = 18446744073709551615}
(gdb) p keys
$2 = {<std::__1::__vector_base<std::__1::basic_string<char, std::__1::char_trait
s<char>, std::__1::allocator<char> >, std::__1::allocator<std::__1::basic_string
<char, std::__1::char_traits<char>, std::__1::allocator<char> > > >> = {<std::__
1::__vector_base_common<true>> = {<No data fields>}, __begin_ = 0x57fb7e5b000,
__end_ = 0x57fb965b000,
__end_cap_ = {<std::__1::__compressed_pair_elem<std::__1::basic_string<char,
std::__1::char_traits<char>, std::__1::allocator<char> >*, 0, false>> = {
__value_ = 0x57fb965b000}, <std::__1::__compressed_pair_elem<std::__1::a
llocator<std::__1::basic_string<char, std::__1::char_traits<char>, std::__1::all
ocator<char> > >, 1, true>> = {<std::__1::allocator<std::__1::basic_string<char,
std::__1::char_traits<char>, std::__1::allocator<char> > >> = {<No data fields>
}, <No data fields>}, <No data fields>}}, <No data fields>}
gdbで追跡するの躊躇しちゅな。
/usr/include/c++
Cフrフラ語のマニュアルがないかと探してみたけど、いわゆるmanの原稿って探し当てられなかった。外様大名もいいところだから、冷遇されてるのかな。代わりにヘッダーでもみておけ。
C++リファレンス こんなオンライン資料があった。
namespace std
{
template <class stateT>
class fpos
{
private:
stateT st;
:
template<class charT, class traits, class Allocator>
bool operator<=(const basic_string<charT, traits, Allocator>& lhs,
const basic_string<charT, traits, Allocator>& rhs) noexcept;
:
こういうのが多数出てくるんで、テンプレートを勉強しとけとな。
at FreeBSD
[sakae@fb /tmp/wyhash]$ c++ --version FreeBSD clang version 11.0.1 (git@github.com:llvm/llvm-project.git llvmorg-11.0.1-0-g43ff75f2c3fe) Target: i386-unknown-freebsd13.0 Thread model: posix InstalledDir: /usr/bin
ちょいとFreeBSDにもCフラフラが入っている事を思い出したんで、gdbでの挙動を調べてみた。
(gdb) p value
$1 = std::vector of length 1048576 = {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
:
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0...}
(gdb) p pos
$2 = 881675
(gdb) p idx[$2]
$3 = 15510592570693831727
(gdb) p keys[$2]
$4 = "dhskfhdsj"
普通の挙動だ。こうでなくちゃね。OpenBSDでCフラフラするのは佳子ちゃんにしておこう。 まあ、Cフラフラを積極的に使うかは疑問ですが。。。
あれ? valueて言うvectorは、イニシャライズしてないけど、初期値がZEROになってるぞ。これってタマタマな事なのかな。それとも、規格でクリアする事になってるの?