競技プログラミング用に,map と unordered_map がどの程度のサイズまで使えるかを測定した.
0. おおざっぱな結論:
$2^{18}$ くらいなら多分大丈夫.$2^{20}$ あるとあぶない.$2^{22}$ だと多分無理.
1. 測定1
以下,ll で,long long int を表す.
1.1 測定方法
$N = 2^{18}$ から $N = 2^{22}$ までについて,2倍ずつ,
$0$ から $10^{18}$ くらいまでからとった一様乱数を長さ $N$ の vector<ll> vec に入れておいて,
測定対象 $o$ に対し,o[vec[i]] = i; を,$i \in [0, N)$ に対して実行し,トータルの時間を計る.
(コードは下に貼ってある.)
1.2 測定対象
map<ll, ll>unordered_map<ll, ll>safe_umap<ll, ll, false>unordered_mapに,比較的安全と思われるハッシュ関数を指定したもの
safe_umap<ll, ll, true>- PBDS の
gp_hash_tableに,上と同じハッシュ関数を指定したもの
- PBDS の
1.3 測定結果
| $2^{18}$ | $2^{19}$ | $2^{20}$ | $2^{21}$ | $2^{22}$ | |
|---|---|---|---|---|---|
map<ll, ll> |
116 | 321 | 805 | 2093 | 4914 |
unordered_map<ll, ll> |
45 | 146 | 411 | 997 | 2253 |
safe_umap<ll, ll, false> |
55 | 182 | 462 | 1080 | 2405 |
safe_umap<ll, ll, true> |
46 | 100 | 198 | 421 | 859 |
単位 ms.2秒間隔で10回実行して,平均を取った. ローカルマシンで実行している.AtCoder の「コードテスト」の方がおおむね速い (が,安定しない). 「コードテスト」での所要時間は,この表の50%から100%程度.
2. 測定2
mapなどのキーとして,少し複雑なものを指定してみた.
具体的には,vector<int> で,要素が小さく(6まで)長さも短い(10)ものを使ってみた (こういうのがときどき出てくると思う).
unordered_map に入れるにはハッシュ関数が必要だが,上のハッシュ関数をベースにしている.詳細はコード参照.
また,これを軽くする目的で前に書いた2つの型(後述)を使う場合とも比較した (今まで比較してなかったのはひどいという話もある).
2.1 測定方法
$N = 2^{18}$ から $N = 2^{22}$ までについて,4倍ずつ,
$1$ から $6$ からとった10個の一様乱数からなる列を$N$個とり,vector<T> vec に入れておいて,
測定対象 $o$ に対し,o[vec[i]] = i; を,$i \in [0, N)$ に対して実行し,トータルの時間を計る.
2.2 測定対象
map<vector<int>, int>unordered_map<vector<int>, int, H1>- H1 は上述のハッシュ関数
unordered_map<small_vector_u64<3>, int, H2>- データを unsigned long long int に 3 ビットずつ押し込んだもの.
unordered_map<small_vector_string, int, H3>- データを string に格納したもの
2.3 測定結果
| $2^{18}$ | $2^{20}$ | $2^{22}$ | |
|---|---|---|---|
map<vector<int>, int> |
201 | 1223 | 7392 |
unordered_map<vector<int>, int, H1> |
112 | 653 | 3282 |
unordered_map<small_vector_u64<3>, int, H2> |
57 | 466 | 2419 |
unordered_map<small_vector_string, int, H3> |
75 | 538 | 2736 |
単位 ms.2秒間隔で10回実行して,平均を取った.
がんばってデータ構造を作った甲斐はあまりなくて,単にハッシュ関数を定義しさえすれば良かったことがわかる.
3. 測定用コード
測定1
主要部分のみ.全体はこちら .
int main() {
random_device the_random_device;
mt19937_64 rng(the_random_device());
uniform_int_distribution<ll> dist(0, (ll)(1e18));
ll x; cin >> x;
ll N = 1LL << x;
vector<ll> vec(N);
REP(i, 0, N) vec[i] = dist(rng);
map<ll, ll> mp1;
unordered_map<ll, ll> mp2;
safe_umap<ll, ll, false> mp3;
safe_umap<ll, ll, true> mp4;
double t0 = get_time_sec();
REP(i, 0, N) { mp1[vec[i]] = i; }
double t1 = get_time_sec();
REP(i, 0, N) { mp2[vec[i]] = i; }
double t2 = get_time_sec();
REP(i, 0, N) { mp3[vec[i]] = i; }
double t3 = get_time_sec();
REP(i, 0, N) { mp4[vec[i]] = i; }
double t4 = get_time_sec();
cerr << ssize(mp1) + ssize(mp2) + ssize(mp3) + ssize(mp4) << endl;
cout << t1 - t0 << endl;
cout << t2 - t1 << endl;
cout << t3 - t2 << endl;
cout << t4 - t3 << endl;
}
測定2
主要部分のみ.全体はこちら .
int main() {
random_device the_random_device;
mt19937_64 rng(the_random_device());
const int len = 10;
const ll vmin = 1;
const ll vmax = 6;
uniform_int_distribution<ll> dist(vmin, vmax);
constexpr ll nbit = 3;
assert(vmax < (1 << nbit) and nbit * len <= 64);
using svu = small_vector_u64<nbit>;
int x; cin >> x;
int N = 1 << x;
vector vec1(N, vi(len));
REP(i, 0, N) REP(j, 0, len) vec1[i][j] = dist(rng);
vector vec3(N, svu());
REP(i, 0, N) REP(j, 0, len) vec3[i][j] = vec1[i][j];
vector vec4(N, small_vector_string(len));
REP(i, 0, N) REP(j, 0, len) vec4[i][j] = vec1[i][j];
map<vi, int> mp1;
unordered_map<vi, int, safe_custom_hash<vi>> mp2;
safe_umap<svu, int> mp3;
safe_umap<small_vector_string, int> mp4;
double t0 = get_time_sec();
REP(i, 0, N) { mp1[vec1[i]] = i; }
double t1 = get_time_sec();
REP(i, 0, N) { mp2[vec1[i]] = i; }
double t2 = get_time_sec();
REP(i, 0, N) { mp3[vec3[i]] = i; }
double t3 = get_time_sec();
REP(i, 0, N) { mp4[vec4[i]] = i; }
double t4 = get_time_sec();
cerr << ssize(mp1) + ssize(mp2) + ssize(mp3) + ssize(mp4) << endl;
cout << t1 - t0 << endl;
cout << t2 - t1 << endl;
cout << t3 - t2 << endl;
cout << t4 - t3 << endl;
}