brpc/test/flat_map_unittest.cpp
2022-12-14 20:13:26 +08:00

1307 lines
37 KiB
C++

// Licensed to the Apache Software Foundation (ASF) under one
// or more contributor license agreements. See the NOTICE file
// distributed with this work for additional information
// regarding copyright ownership. The ASF licenses this file
// to you under the Apache License, Version 2.0 (the
// "License"); you may not use this file except in compliance
// with the License. You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing,
// software distributed under the License is distributed on an
// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
// KIND, either express or implied. See the License for the
// specific language governing permissions and limitations
// under the License.
#include <gtest/gtest.h>
#include <stdio.h>
#include <stdlib.h>
#include <map>
#include <vector>
#include "butil/time.h"
#include "butil/macros.h"
#include "butil/string_printf.h"
#include "butil/logging.h"
#include "butil/containers/hash_tables.h"
#include "butil/containers/flat_map.h"
#include "butil/containers/pooled_map.h"
#include "butil/containers/case_ignored_flat_map.h"
namespace {
class FlatMapTest : public ::testing::Test{
protected:
FlatMapTest(){};
virtual ~FlatMapTest(){};
virtual void SetUp() {
};
virtual void TearDown() {
};
};
int g_foo_ctor = 0;
int g_foo_copy_ctor = 0;
int g_foo_assign = 0;
struct Foo {
Foo() { ++g_foo_ctor; }
Foo(const Foo&) { ++g_foo_copy_ctor; }
void operator=(const Foo&) { ++g_foo_assign; }
};
struct Bar {
int x;
};
TEST_F(FlatMapTest, initialization_of_values) {
// Construct non-POD values w/o copy-construction.
butil::FlatMap<int, Foo> map;
ASSERT_EQ(0, map.init(32));
ASSERT_EQ(0, g_foo_ctor);
ASSERT_EQ(0, g_foo_copy_ctor);
ASSERT_EQ(0, g_foo_assign);
map[1];
ASSERT_EQ(1, g_foo_ctor);
ASSERT_EQ(0, g_foo_copy_ctor);
ASSERT_EQ(0, g_foo_assign);
// Zeroize POD values.
butil::FlatMap<int, Bar> map2;
ASSERT_EQ(0, map2.init(32));
Bar& g = map2[1];
ASSERT_EQ(0, g.x);
g.x = 123;
ASSERT_EQ(1u, map2.erase(1));
ASSERT_EQ(123, g.x); // g is still accessible in this case.
Bar& g2 = map2[1];
ASSERT_EQ(&g, &g2);
ASSERT_EQ(0, g2.x);
}
TEST_F(FlatMapTest, swap_pooled_allocator) {
butil::details::PooledAllocator<int, 128> a1;
a1.allocate(1);
const void* p1 = a1._pool._blocks;
butil::details::PooledAllocator<int, 128> a2;
a2.allocate(1);
const void* p2 = a2._pool._blocks;
std::swap(a1, a2);
ASSERT_EQ(p2, a1._pool._blocks);
ASSERT_EQ(p1, a2._pool._blocks);
}
TEST_F(FlatMapTest, copy_flat_map) {
typedef butil::FlatMap<std::string, std::string> Map;
Map uninit_m1;
ASSERT_FALSE(uninit_m1.initialized());
ASSERT_TRUE(uninit_m1.empty());
// self assignment does nothing.
uninit_m1 = uninit_m1;
ASSERT_FALSE(uninit_m1.initialized());
ASSERT_TRUE(uninit_m1.empty());
// Copy construct from uninitialized map.
Map uninit_m2 = uninit_m1;
ASSERT_FALSE(uninit_m2.initialized());
ASSERT_TRUE(uninit_m2.empty());
// assign uninitialized map to uninitialized map.
Map uninit_m3;
uninit_m3 = uninit_m1;
ASSERT_FALSE(uninit_m3.initialized());
ASSERT_TRUE(uninit_m3.empty());
// assign uninitialized map to initialized map.
Map init_m4;
ASSERT_EQ(0, init_m4.init(16));
ASSERT_TRUE(init_m4.initialized());
init_m4["hello"] = "world";
ASSERT_EQ(1u, init_m4.size());
init_m4 = uninit_m1;
ASSERT_TRUE(init_m4.initialized());
ASSERT_TRUE(init_m4.empty());
Map m1;
ASSERT_EQ(0, m1.init(16));
m1["hello"] = "world";
m1["foo"] = "bar";
ASSERT_TRUE(m1.initialized());
ASSERT_EQ(2u, m1.size());
// self assignment does nothing.
m1 = m1;
ASSERT_EQ(2u, m1.size());
ASSERT_EQ("world", m1["hello"]);
ASSERT_EQ("bar", m1["foo"]);
// Copy construct from initialized map.
Map m2 = m1;
ASSERT_TRUE(m2.initialized());
ASSERT_EQ(2u, m2.size());
ASSERT_EQ("world", m2["hello"]);
ASSERT_EQ("bar", m2["foo"]);
// assign initialized map to uninitialized map.
Map m3;
m3 = m1;
ASSERT_TRUE(m3.initialized());
ASSERT_EQ(2u, m3.size());
ASSERT_EQ("world", m3["hello"]);
ASSERT_EQ("bar", m3["foo"]);
// assign initialized map to initialized map (triggering resize)
Map m4;
ASSERT_EQ(0, m4.init(2));
ASSERT_LT(m4.bucket_count(), m1.bucket_count());
const void* old_buckets4 = m4._buckets;
m4 = m1;
ASSERT_EQ(m1.bucket_count(), m4.bucket_count());
ASSERT_NE(old_buckets4, m4._buckets);
ASSERT_EQ(2u, m4.size());
ASSERT_EQ("world", m4["hello"]);
ASSERT_EQ("bar", m4["foo"]);
// assign initialized map to initialized map (no resize)
const size_t bcs[] = { 8, m1.bucket_count(), 32 };
// less than m1.bucket_count but enough for holding the elements
ASSERT_LT(bcs[0], m1.bucket_count());
// larger than m1.bucket_count
ASSERT_GT(bcs[2], m1.bucket_count());
for (size_t i = 0; i < arraysize(bcs); ++i) {
Map m5;
ASSERT_EQ(0, m5.init(bcs[i]));
const size_t old_bucket_count5 = m5.bucket_count();
const void* old_buckets5 = m5._buckets;
m5 = m1;
ASSERT_EQ(old_bucket_count5, m5.bucket_count());
ASSERT_EQ(old_buckets5, m5._buckets);
ASSERT_EQ(2u, m5.size());
ASSERT_EQ("world", m5["hello"]);
ASSERT_EQ("bar", m5["foo"]);
}
}
TEST_F(FlatMapTest, seek_by_string_piece) {
butil::FlatMap<std::string, int> m;
ASSERT_EQ(0, m.init(16));
m["hello"] = 1;
m["world"] = 2;
butil::StringPiece k1("hello");
ASSERT_TRUE(m.seek(k1));
ASSERT_EQ(1, *m.seek(k1));
butil::StringPiece k2("world");
ASSERT_TRUE(m.seek(k2));
ASSERT_EQ(2, *m.seek(k2));
butil::StringPiece k3("heheda");
ASSERT_TRUE(m.seek(k3) == NULL);
}
TEST_F(FlatMapTest, to_lower) {
for (int c = -128; c < 128; ++c) {
ASSERT_EQ((char)::tolower(c), butil::ascii_tolower(c)) << "c=" << c;
}
const size_t input_len = 102;
char input[input_len + 1];
char input2[input_len + 1];
for (size_t i = 0; i < input_len; ++i) {
int choice = rand() % 52;
if (choice < 26) {
input[i] = 'A' + choice;
input2[i] = 'A' + choice;
} else {
input[i] = 'a' + choice - 26;
input2[i] = 'a' + choice - 26;
}
}
input[input_len] = '\0';
input2[input_len] = '\0';
butil::Timer tm1;
butil::Timer tm2;
butil::Timer tm3;
int sum = 0;
tm1.start();
sum += strcasecmp(input, input2);
tm1.stop();
tm3.start();
sum += strncasecmp(input, input2, input_len);
tm3.stop();
tm2.start();
sum += memcmp(input, input2, input_len);
tm2.stop();
LOG(INFO) << "tm1=" << tm1.n_elapsed()
<< " tm2=" << tm2.n_elapsed()
<< " tm3=" << tm3.n_elapsed() << " " << sum;
}
TEST_F(FlatMapTest, __builtin_ctzl_perf) {
int s = 0;
const size_t N = 10000;
butil::Timer tm1;
tm1.start();
for (size_t i = 1; i <= N; ++i) {
s += __builtin_ctzl(i);
}
tm1.stop();
LOG(INFO) << "__builtin_ctzl takes " << tm1.n_elapsed()/(double)N << "ns s=" << s;
}
TEST_F(FlatMapTest, case_ignored_map) {
butil::CaseIgnoredFlatMap<int> m1;
ASSERT_EQ(0, m1.init(32));
m1["Content-Type"] = 1;
m1["content-Type"] = 10;
m1["Host"] = 2;
m1["HOST"] = 20;
m1["Cache-Control"] = 3;
m1["CachE-ControL"] = 30;
ASSERT_EQ(10, m1["cONTENT-tYPE"]);
ASSERT_EQ(20, m1["hOST"]);
ASSERT_EQ(30, m1["cache-control"]);
}
TEST_F(FlatMapTest, case_ignored_set) {
butil::CaseIgnoredFlatSet s1;
ASSERT_EQ(0, s1.init(32));
s1.insert("Content-Type");
ASSERT_EQ(1ul, s1.size());
s1.insert("Content-TYPE");
ASSERT_EQ(1ul, s1.size());
s1.insert("Host");
ASSERT_EQ(2ul, s1.size());
s1.insert("HOST");
ASSERT_EQ(2ul, s1.size());
s1.insert("Cache-Control");
ASSERT_EQ(3ul, s1.size());
s1.insert("CachE-ControL");
ASSERT_EQ(3ul, s1.size());
ASSERT_TRUE(s1.seek("cONTENT-tYPE"));
ASSERT_TRUE(s1.seek("hOST"));
ASSERT_TRUE(s1.seek("cache-control"));
}
TEST_F(FlatMapTest, make_sure_all_methods_compile) {
typedef butil::FlatMap<int, long> M1;
M1 m1;
ASSERT_EQ(0, m1.init(32));
ASSERT_EQ(0u, m1.size());
m1[1] = 10;
ASSERT_EQ(10, m1[1]);
ASSERT_EQ(1u, m1.size());
m1[2] = 20;
ASSERT_EQ(20, m1[2]);
ASSERT_EQ(2u, m1.size());
m1.insert(1, 100);
m1.insert({3, 30});
ASSERT_EQ(100, m1[1]);
ASSERT_EQ(3u, m1.size());
ASSERT_TRUE(m1.seek(3));
ASSERT_EQ(NULL, m1.seek(4));
ASSERT_EQ(1u, m1.erase(3));
ASSERT_EQ(0u, m1.erase(4));
ASSERT_EQ(2u, m1.size());
ASSERT_EQ(1u, m1.erase(2));
ASSERT_EQ(1u, m1.size());
for (M1::iterator it = m1.begin(); it != m1.end(); ++it) {
std::cout << "[" << it->first << "," << it->second << "] ";
}
std::cout << std::endl;
for (M1::const_iterator it = m1.begin(); it != m1.end(); ++it) {
std::cout << "[" << it->first << "," << it->second << "] ";
}
std::cout << std::endl;
typedef butil::FlatSet<int> S1;
S1 s1;
ASSERT_EQ(0, s1.init(32));
ASSERT_EQ(0u, s1.size());
s1.insert(1);
ASSERT_TRUE(s1.seek(1));
ASSERT_EQ(1u, s1.size());
s1.insert(2);
ASSERT_TRUE(s1.seek(2));
ASSERT_EQ(2u, s1.size());
s1.insert(1);
ASSERT_TRUE(s1.seek(1));
ASSERT_EQ(2u, s1.size());
ASSERT_EQ(NULL, s1.seek(3));
ASSERT_EQ(0u, s1.erase(3));
ASSERT_EQ(2u, s1.size());
ASSERT_EQ(1u, s1.erase(2));
ASSERT_EQ(1u, s1.size());
for (S1::iterator it = s1.begin(); it != s1.end(); ++it) {
std::cout << "[" << *it << "] ";
}
std::cout << std::endl;
for (S1::const_iterator it = s1.begin(); it != s1.end(); ++it) {
std::cout << "[" << *it << "] ";
}
std::cout << std::endl;
}
TEST_F(FlatMapTest, flat_map_of_string) {
std::vector<std::string> keys;
butil::FlatMap<std::string, size_t> m1;
std::map<std::string, size_t> m2;
butil::hash_map<std::string, size_t> m3;
const size_t N = 10000;
ASSERT_EQ(0, m1.init(N));
butil::Timer tm1, tm1_2, tm2, tm3;
size_t sum = 0;
keys.reserve(N);
for (size_t i = 0; i < N; ++i) {
keys.push_back(butil::string_printf("up_latency_as_key_%lu", i));
}
tm1.start();
for (size_t i = 0; i < N; ++i) {
m1[keys[i]] += i;
}
tm1.stop();
tm2.start();
for (size_t i = 0; i < N; ++i) {
m2[keys[i]] += i;
}
tm2.stop();
tm3.start();
for (size_t i = 0; i < N; ++i) {
m3[keys[i]] += i;
}
tm3.stop();
LOG(INFO) << "inserting strings takes " << tm1.n_elapsed() / N
<< " " << tm2.n_elapsed() / N
<< " " << tm3.n_elapsed() / N;
tm1.start();
for (size_t i = 0; i < N; ++i) {
sum += *m1.seek(keys[i]);
}
tm1.stop();
tm2.start();
for (size_t i = 0; i < N; ++i) {
sum += m2.find(keys[i])->second;
}
tm2.stop();
tm3.start();
for (size_t i = 0; i < N; ++i) {
sum += m3.find(keys[i])->second;
}
tm3.stop();
LOG(INFO) << "finding strings takes " << tm1.n_elapsed()/N
<< " " << tm2.n_elapsed()/N << " " << tm3.n_elapsed()/N;
tm1.start();
for (size_t i = 0; i < N; ++i) {
sum += *m1.seek(keys[i].c_str());
}
tm1.stop();
tm2.start();
for (size_t i = 0; i < N; ++i) {
sum += m2.find(keys[i].c_str())->second;
}
tm2.stop();
tm3.start();
for (size_t i = 0; i < N; ++i) {
sum += m3.find(keys[i].c_str())->second;
}
tm3.stop();
tm1_2.start();
for (size_t i = 0; i < N; ++i) {
sum += *find_cstr(m1, keys[i].c_str());
}
tm1_2.stop();
LOG(INFO) << "finding c_strings takes " << tm1.n_elapsed()/N
<< " " << tm2.n_elapsed()/N << " " << tm3.n_elapsed()/N
<< " " << tm1_2.n_elapsed()/N << " sum=" << sum;
for (size_t i = 0; i < N; ++i) {
ASSERT_EQ(i, m1[keys[i]]) << "i=" << i;
ASSERT_EQ(i, m2[keys[i]]);
ASSERT_EQ(i, m3[keys[i]]);
}
}
TEST_F(FlatMapTest, fast_iterator) {
typedef butil::FlatMap<uint64_t, uint64_t> M1;
typedef butil::SparseFlatMap<uint64_t, uint64_t> M2;
M1 m1;
M2 m2;
ASSERT_EQ(0, m1.init(16384));
ASSERT_EQ(-1, m1.init(1));
ASSERT_EQ(0, m2.init(16384));
ASSERT_EQ(NULL, m1._thumbnail);
ASSERT_TRUE(NULL != m2._thumbnail);
const size_t N = 170;
std::vector<uint64_t> keys;
keys.reserve(N);
for (size_t i = 0; i < N; ++i) {
keys.push_back(rand());
}
butil::Timer tm2;
tm2.start();
for (size_t i = 0; i < N; ++i) {
m2[keys[i]] = i;
}
tm2.stop();
butil::Timer tm1;
tm1.start();
for (size_t i = 0; i < N; ++i) {
m1[keys[i]] = i;
}
tm1.stop();
LOG(INFO) << "m1.insert=" << tm1.n_elapsed()/(double)N
<< "ns m2.insert=" << tm2.n_elapsed()/(double)N;
tm1.start();
for (M1::iterator it = m1.begin(); it != m1.end(); ++it);
tm1.stop();
tm2.start();
for (M2::iterator it = m2.begin(); it != m2.end(); ++it);
tm2.stop();
LOG(INFO) << "m1.iterate=" << tm1.n_elapsed()/(double)N
<< "ns m2.iterate=" << tm2.n_elapsed()/(double)N;
M1::iterator it1 = m1.begin();
M2::iterator it2 = m2.begin();
for ( ; it1 != m1.end() && it2 != m2.end(); ++it1, ++it2) {
ASSERT_EQ(it1->first, it2->first);
ASSERT_EQ(it1->second, it2->second);
}
ASSERT_EQ(m1.end(), it1);
ASSERT_EQ(m2.end(), it2);
}
template <typename Key, typename Value, typename OnPause>
static void list_flat_map(std::vector<Key>* keys,
const butil::FlatMap<Key, Value>& map,
size_t max_one_pass,
OnPause& on_pause) {
keys->clear();
typedef butil::FlatMap<Key, Value> Map;
size_t n = 0;
for (typename Map::const_iterator it = map.begin(); it != map.end(); ++it) {
if (++n >= max_one_pass) {
typename Map::PositionHint hint;
map.save_iterator(it, &hint);
n = 0;
on_pause(hint);
it = map.restore_iterator(hint);
if (it == map.begin()) { // resized
keys->clear();
}
if (it == map.end()) {
break;
}
}
keys->push_back(it->first);
}
}
typedef butil::FlatMap<uint64_t, uint64_t> PositionHintMap;
static void fill_position_hint_map(PositionHintMap* map,
std::vector<uint64_t>* keys) {
srand(time(NULL));
const size_t N = 170;
if (!map->initialized()) {
ASSERT_EQ(0, map->init(N * 3 / 2, 80));
}
keys->reserve(N);
keys->clear();
map->clear();
for (size_t i = 0; i < N; ++i) {
uint64_t key = rand();
if (map->seek(key)) {
continue;
}
keys->push_back(key);
(*map)[key] = i;
}
LOG(INFO) << map->bucket_info();
}
struct CountOnPause {
CountOnPause() : num_paused(0) {}
void operator()(const PositionHintMap::PositionHint&) {
++num_paused;
}
size_t num_paused;
};
TEST_F(FlatMapTest, do_nothing_during_iteration) {
PositionHintMap m1;
std::vector<uint64_t> keys;
fill_position_hint_map(&m1, &keys);
// Iteration w/o insert/erasure should be same with single-threaded iteration.
std::vector<uint64_t> keys_out;
CountOnPause on_pause1;
list_flat_map(&keys_out, m1, 10, on_pause1);
EXPECT_EQ(m1.size() / 10, on_pause1.num_paused);
ASSERT_EQ(m1.size(), keys_out.size());
std::sort(keys_out.begin(), keys_out.end());
for (size_t i = 0; i < keys_out.size(); ++i) {
ASSERT_TRUE(m1.seek(keys_out[i])) << "i=" << i;
if (i) {
ASSERT_NE(keys_out[i-1], keys_out[i]) << "i=" << i;
}
}
}
struct RemoveInsertVisitedOnPause {
RemoveInsertVisitedOnPause() : keys(NULL), map(NULL) {
removed_keys.init(32);
inserted_keys.init(32);
}
void operator()(const PositionHintMap::PositionHint& hint) {
// Remove one
do {
int index = rand() % keys->size();
uint64_t removed_key = (*keys)[index];
if (removed_keys.seek(removed_key)) {
continue;
}
ASSERT_EQ(1u, map->erase(removed_key));
removed_keys.insert(removed_key);
break;
} while (true);
// Insert one
uint64_t inserted_key =
((rand() % hint.offset) + rand() * hint.nbucket);
inserted_keys.insert(inserted_key);
++(*map)[inserted_key];
}
butil::FlatSet<uint64_t> removed_keys;
butil::FlatSet<uint64_t> inserted_keys;
const std::vector<uint64_t>* keys;
PositionHintMap* map;
};
TEST_F(FlatMapTest, erase_insert_visited_during_iteration) {
PositionHintMap m1;
std::vector<uint64_t> keys;
fill_position_hint_map(&m1, &keys);
// Erase/insert visisted values should not affect the result.
const size_t old_map_size = m1.size();
RemoveInsertVisitedOnPause on_pause2;
std::vector<uint64_t> keys_out;
on_pause2.map = &m1;
on_pause2.keys = &keys_out;
list_flat_map(&keys_out, m1, 10, on_pause2);
EXPECT_EQ(old_map_size / 10, on_pause2.removed_keys.size());
ASSERT_EQ(old_map_size, keys_out.size());
std::sort(keys_out.begin(), keys_out.end());
for (size_t i = 0; i < keys_out.size(); ++i) {
if (i) {
ASSERT_NE(keys_out[i-1], keys_out[i]) << "i=" << i;
}
if (!m1.seek(keys_out[i])) {
ASSERT_TRUE(on_pause2.removed_keys.seek(keys_out[i])) << "i=" << i;
}
ASSERT_FALSE(on_pause2.inserted_keys.seek(keys_out[i])) << "i=" << i;
}
}
struct RemoveHintedOnPause {
RemoveHintedOnPause() : map(NULL) {
removed_keys.init(32);
}
void operator()(const PositionHintMap::PositionHint& hint) {
uint64_t removed_key = hint.key;
ASSERT_EQ(1u, map->erase(removed_key));
removed_keys.insert(removed_key);
}
butil::FlatSet<uint64_t> removed_keys;
PositionHintMap* map;
};
TEST_F(FlatMapTest, erase_hinted_during_iteration) {
PositionHintMap m1;
std::vector<uint64_t> keys;
fill_position_hint_map(&m1, &keys);
// Erasing hinted values
RemoveHintedOnPause on_pause3;
std::vector<uint64_t> keys_out;
on_pause3.map = &m1;
list_flat_map(&keys_out, m1, 10, on_pause3);
// Not equal sometimes because of backward of iterator
// EXPECT_EQ(m1.size() / 10, on_pause3.removed_keys.size());
const size_t old_keys_out_size = keys_out.size();
std::sort(keys_out.begin(), keys_out.end());
keys_out.resize(std::unique(keys_out.begin(), keys_out.end()) - keys_out.begin());
LOG_IF(INFO, keys_out.size() != old_keys_out_size)
<< "Iterated " << old_keys_out_size - keys_out.size()
<< " duplicated elements";
ASSERT_EQ(m1.size(), keys_out.size());
for (size_t i = 0; i < keys_out.size(); ++i) {
if (i) {
ASSERT_NE(keys_out[i-1], keys_out[i]) << "i=" << i;
}
if (!m1.seek(keys_out[i])) {
ASSERT_TRUE(on_pause3.removed_keys.seek(keys_out[i])) << "i=" << i;
}
}
}
struct RemoveInsertUnvisitedOnPause {
RemoveInsertUnvisitedOnPause()
: keys_out(NULL), all_keys(NULL), map(NULL) {
removed_keys.init(32);
inserted_keys.init(32);
}
void operator()(const PositionHintMap::PositionHint& hint) {
// Insert one
do {
uint64_t inserted_key =
((rand() % (hint.nbucket - hint.offset)) + hint.offset
+ rand() * hint.nbucket);
if (std::find(all_keys->begin(), all_keys->end(), inserted_key)
!= all_keys->end()) {
continue;
}
all_keys->push_back(inserted_key);
inserted_keys.insert(inserted_key);
++(*map)[inserted_key];
break;
} while (true);
// Remove one
while (true) {
int index = rand() % all_keys->size();
uint64_t removed_key = (*all_keys)[index];
if (removed_key == hint.key) {
continue;
}
if (removed_keys.seek(removed_key)) {
continue;
}
if (std::find(keys_out->begin(), keys_out->end(), removed_key)
!= keys_out->end()) {
continue;
}
ASSERT_EQ(1u, map->erase(removed_key));
removed_keys.insert(removed_key);
break;
}
}
butil::FlatSet<uint64_t> removed_keys;
butil::FlatSet<uint64_t> inserted_keys;
const std::vector<uint64_t>* keys_out;
std::vector<uint64_t>* all_keys;
PositionHintMap* map;
};
TEST_F(FlatMapTest, erase_insert_unvisited_during_iteration) {
PositionHintMap m1;
std::vector<uint64_t> keys;
fill_position_hint_map(&m1, &keys);
// Erase/insert unvisited values should affect keys_out
RemoveInsertUnvisitedOnPause on_pause4;
std::vector<uint64_t> keys_out;
on_pause4.map = &m1;
on_pause4.keys_out = &keys_out;
on_pause4.all_keys = &keys;
list_flat_map(&keys_out, m1, 10, on_pause4);
EXPECT_EQ(m1.size() / 10, on_pause4.removed_keys.size());
ASSERT_EQ(m1.size(), keys_out.size());
std::sort(keys_out.begin(), keys_out.end());
for (size_t i = 0; i < keys_out.size(); ++i) {
if (i) {
ASSERT_NE(keys_out[i-1], keys_out[i]) << "i=" << i;
}
ASSERT_TRUE(m1.seek(keys_out[i])) << "i=" << i;
}
}
inline uint64_t fmix64 (uint64_t k) {
k ^= k >> 33;
k *= 0xff51afd7ed558ccdULL;
k ^= k >> 33;
k *= 0xc4ceb9fe1a85ec53ULL;
k ^= k >> 33;
return k;
}
template <typename T>
struct PointerHasher {
size_t operator()(const T* p) const
{ return fmix64(reinterpret_cast<uint64_t>(p)); }
};
template <typename T>
struct PointerHasher2 {
size_t operator()(const T* p) const
{ return reinterpret_cast<uint64_t>(p); }
};
TEST_F(FlatMapTest, perf_cmp_with_map_storing_pointers) {
const size_t REP = 4;
int* ptr[2048];
for (size_t i = 0; i < ARRAY_SIZE(ptr); ++i) {
ptr[i] = new int;
}
std::set<int*> m1;
butil::FlatSet<int*, PointerHasher<int> > m2;
butil::hash_set<int*, PointerHasher<int> > m3;
std::vector<int*> r;
int sum;
butil::Timer tm;
r.reserve(ARRAY_SIZE(ptr)*REP);
ASSERT_EQ(0, m2.init(ARRAY_SIZE(ptr)));
for (size_t i = 0; i < ARRAY_SIZE(ptr); ++i) {
m1.insert(ptr[i]);
m2.insert(ptr[i]);
m3.insert(ptr[i]);
for (size_t j = 0; j < REP; ++j) {
r.push_back(ptr[i]);
}
}
ASSERT_EQ(m1.size(), m2.size());
ASSERT_EQ(m1.size(), m3.size());
std::random_shuffle(r.begin(), r.end());
sum = 0;
tm.start();
for (size_t i = 0; i < r.size(); ++i) {
sum += (m2.seek(r[i]) != NULL);
}
tm.stop();
LOG(INFO) << "FlatMap takes " << tm.n_elapsed()/r.size();
sum = 0;
tm.start();
for (size_t i = 0; i < r.size(); ++i) {
sum += (m1.find(r[i]) != m1.end());
}
tm.stop();
LOG(INFO) << "std::set takes " << tm.n_elapsed()/r.size();
sum = 0;
tm.start();
for (size_t i = 0; i < r.size(); ++i) {
sum += (m3.find(r[i]) != m3.end());
}
tm.stop();
LOG(INFO) << "std::set takes " << tm.n_elapsed()/r.size() << " sum=" << sum;
for (size_t i = 0; i < ARRAY_SIZE(ptr); ++i) {
delete ptr[i];
}
}
int n_con = 0;
int n_cp_con = 0;
int n_des = 0;
int n_cp = 0;
struct Value {
Value() : x_(0) { ++n_con; }
Value(int x) : x_(x) { ++ n_con; }
Value (const Value& rhs) : x_(rhs.x_) { ++ n_cp_con; }
~Value() { ++ n_des; }
Value& operator= (const Value& rhs) {
x_ = rhs.x_;
++ n_cp;
return *this;
}
bool operator== (const Value& rhs) const { return x_ == rhs.x_; }
bool operator!= (const Value& rhs) const { return x_ != rhs.x_; }
friend std::ostream& operator<< (std::ostream& os, const Value& v)
{ return os << v.x_; }
int x_;
};
int n_con_key = 0;
int n_cp_con_key = 0;
int n_des_key = 0;
struct Key {
Key() : x_(0) { ++n_con_key; }
Key(int x) : x_(x) { ++ n_con_key; }
Key(const Key& rhs) : x_(rhs.x_) { ++ n_cp_con_key; }
void operator=(const Key& rhs) { x_ = rhs.x_; }
~Key() { ++ n_des_key; }
int x_;
};
struct KeyHasher {
size_t operator()(const Key& k) const { return k.x_; }
};
struct KeyEqualTo {
size_t operator()(const Key& k1, const Key& k2) const
{ return k1.x_ == k2.x_; }
};
TEST_F(FlatMapTest, key_value_are_not_constructed_before_first_insertion) {
butil::FlatMap<Key, Value, KeyHasher, KeyEqualTo> m;
ASSERT_EQ(0, m.init(32));
ASSERT_EQ(0, n_con_key);
ASSERT_EQ(0, n_cp_con_key);
ASSERT_EQ(0, n_con);
ASSERT_EQ(0, n_cp_con);
const Key k1 = 1;
ASSERT_EQ(1, n_con_key);
ASSERT_EQ(0, n_cp_con_key);
ASSERT_EQ(NULL, m.seek(k1));
ASSERT_EQ(0u, m.erase(k1));
ASSERT_EQ(1, n_con_key);
ASSERT_EQ(0, n_cp_con_key);
ASSERT_EQ(0, n_con);
ASSERT_EQ(0, n_cp_con);
}
TEST_F(FlatMapTest, manipulate_uninitialized_map) {
butil::FlatMap<int, int> m;
ASSERT_FALSE(m.initialized());
for (butil::FlatMap<int,int>::iterator it = m.begin(); it != m.end(); ++it) {
LOG(INFO) << "nothing";
}
ASSERT_EQ(NULL, m.seek(1));
ASSERT_EQ(0u, m.erase(1));
ASSERT_EQ(0u, m.size());
ASSERT_TRUE(m.empty());
ASSERT_EQ(0u, m.bucket_count());
ASSERT_EQ(0u, m.load_factor());
}
TEST_F(FlatMapTest, perf_small_string_map) {
butil::Timer tm1;
butil::Timer tm2;
butil::Timer tm3;
butil::Timer tm4;
for (int i = 0; i < 10; ++i) {
tm3.start();
butil::PooledMap<std::string, std::string> m3;
m3["Content-type"] = "application/json";
m3["Request-Id"] = "true";
m3["Status-Code"] = "200";
tm3.stop();
tm4.start();
butil::CaseIgnoredFlatMap<std::string> m4;
m4.init(16);
m4["Content-type"] = "application/json";
m4["Request-Id"] = "true";
m4["Status-Code"] = "200";
tm4.stop();
tm1.start();
butil::FlatMap<std::string, std::string> m1;
m1.init(16);
m1["Content-type"] = "application/json";
m1["Request-Id"] = "true";
m1["Status-Code"] = "200";
tm1.stop();
tm2.start();
std::map<std::string, std::string> m2;
m2["Content-type"] = "application/json";
m2["Request-Id"] = "true";
m2["Status-Code"] = "200";
tm2.stop();
LOG(INFO) << "flatmap=" << tm1.n_elapsed()
<< " ci_flatmap=" << tm4.n_elapsed()
<< " map=" << tm2.n_elapsed()
<< " pooled_map=" << tm3.n_elapsed();
}
}
TEST_F(FlatMapTest, sanity) {
typedef butil::FlatMap<uint64_t, long> Map;
Map m;
ASSERT_FALSE(m.initialized());
m.init(1000, 70);
ASSERT_TRUE(m.initialized());
ASSERT_EQ(0UL, m.size());
ASSERT_TRUE(m.empty());
ASSERT_EQ(0UL, m._pool.count_allocated());
const uint64_t k1 = 1;
// hashed to the same bucket of k1.
const uint64_t k2 = k1 + m.bucket_count();
const uint64_t k3 = k1 + 1;
// Initial insertion
m[k1] = 10;
ASSERT_EQ(1UL, m.size());
ASSERT_FALSE(m.empty());
long* p = m.seek(k1);
ASSERT_TRUE(p && *p == 10);
ASSERT_EQ(0UL, m._pool.count_allocated());
ASSERT_EQ(NULL, m.seek(k2));
// Override
m[k1] = 100;
ASSERT_EQ(1UL, m.size());
ASSERT_FALSE(m.empty());
p = m.seek(k1);
ASSERT_TRUE(p && *p == 100);
// Insert another
m[k3] = 20;
ASSERT_EQ(2UL, m.size());
ASSERT_FALSE(m.empty());
p = m.seek(k3);
ASSERT_TRUE(p && *p == 20);
ASSERT_EQ(0UL, m._pool.count_allocated());
m[k2] = 30;
ASSERT_EQ(1UL, m._pool.count_allocated());
ASSERT_EQ(0UL, m._pool.count_free());
ASSERT_EQ(3UL, m.size());
ASSERT_FALSE(m.empty());
p = m.seek(k2);
ASSERT_TRUE(p && *p == 30);
ASSERT_EQ(NULL, m.seek(2049));
Map::iterator it = m.begin();
ASSERT_EQ(k1, it->first);
++it;
ASSERT_EQ(k2, it->first);
++it;
ASSERT_EQ(k3, it->first);
++it;
ASSERT_EQ(m.end(), it);
// Erase exist
ASSERT_EQ(1UL, m.erase(k1));
ASSERT_EQ(2UL, m.size());
ASSERT_FALSE(m.empty());
ASSERT_EQ(NULL, m.seek(k1));
ASSERT_EQ(30, *m.seek(k2));
ASSERT_EQ(20, *m.seek(k3));
ASSERT_EQ(1UL, m._pool.count_allocated());
ASSERT_EQ(1UL, m._pool.count_free());
// default constructed
ASSERT_EQ(0, m[k1]);
ASSERT_EQ(0, m[5]);
ASSERT_EQ(0, m[1029]);
ASSERT_EQ(0, m[2053]);
// Clear
m.clear();
ASSERT_EQ(m.size(), 0ul);
ASSERT_TRUE(m.empty());
ASSERT_EQ(NULL, m.seek(k1));
ASSERT_EQ(NULL, m.seek(k2));
ASSERT_EQ(NULL, m.seek(k3));
}
TEST_F(FlatMapTest, random_insert_erase) {
srand (0);
{
butil::hash_map<uint64_t, Value> ref[2];
typedef butil::FlatMap<uint64_t, Value> Map;
Map ht[2];
ht[0].init (40);
ht[1] = ht[0];
for (int j = 0; j < 30; ++j) {
// Make snapshot
ht[1] = ht[0];
ref[1] = ref[0];
for (int i = 0; i < 100000; ++i) {
int k = rand() % 0xFFFF;
int p = rand() % 1000;
if (p < 600) {
ht[0].insert(k, i);
ref[0][k] = i;
} else if(p < 999) {
ht[0].erase (k);
ref[0].erase (k);
} else {
ht[0].clear();
ref[0].clear();
}
}
LOG(INFO) << "Check j=" << j;
// bi-check
for (int i=0; i<2; ++i) {
for (Map::iterator it = ht[i].begin(); it != ht[i].end(); ++it)
{
butil::hash_map<uint64_t, Value>::iterator it2 = ref[i].find(it->first);
ASSERT_TRUE (it2 != ref[i].end());
ASSERT_EQ (it2->second, it->second);
}
for (butil::hash_map<uint64_t, Value>::iterator it = ref[i].begin();
it != ref[i].end(); ++it)
{
Value* p_value = ht[i].seek(it->first);
ASSERT_TRUE (p_value != NULL);
ASSERT_EQ (it->second, p_value->x_);
}
ASSERT_EQ (ht[i].size(), ref[i].size());
}
}
}
// cout << "ht[0] = " << show(ht[0]) << endl
// << "ht[1] = " << show(ht[1]) << endl;
//ASSERT_EQ (ht[0]._pool->alloc_num(), 0ul);
ASSERT_EQ (n_con + n_cp_con, n_des);
LOG(INFO) << "n_con:" << n_con << std::endl
<< "n_cp_con:" << n_cp_con << std::endl
<< "n_con+n_cp_con:" << n_con+n_cp_con << std::endl
<< "n_des:" << n_des << std::endl
<< "n_cp:" << n_cp;
}
template <typename T> void perf_insert_erase(bool random, const T& value)
{
size_t nkeys[] = { 100, 1000, 10000 };
const size_t NPASS = ARRAY_SIZE(nkeys);
std::vector<uint64_t> keys;
butil::FlatMap<uint64_t, T> id_map;
std::map<uint64_t, T> std_map;
butil::PooledMap<uint64_t, T> pooled_map;
butil::hash_map<uint64_t, T> hash_map;
butil::Timer id_tm, std_tm, pooled_tm, hash_tm;
size_t max_nkeys = 0;
for (size_t i = 0; i < NPASS; ++i) {
max_nkeys = std::max(max_nkeys, nkeys[i]);
}
id_map.init((size_t)(nkeys[NPASS-1] * 1.5));
// Make DS hot
for (size_t i = 0; i < max_nkeys; ++i) {
id_map[i] = value;
std_map[i] = value;
pooled_map[i] = value;
hash_map[i] = value;
}
id_map.clear();
std_map.clear();
pooled_map.clear();
hash_map.clear();
LOG(INFO) << "[ value = " << sizeof(T) << " bytes ]";
for (size_t pass = 0; pass < NPASS; ++pass) {
int start = rand();
keys.clear();
for (size_t i = 0; i < nkeys[pass]; ++i) {
keys.push_back(start + i);
}
if (random) {
random_shuffle(keys.begin(), keys.end());
}
id_map.clear();
id_tm.start();
for (size_t i = 0; i < keys.size(); ++i) {
id_map[keys[i]] = value;
}
id_tm.stop();
std_map.clear();
std_tm.start();
for (size_t i = 0; i < keys.size(); ++i) {
std_map[keys[i]] = value;
}
std_tm.stop();
pooled_map.clear();
pooled_tm.start();
for (size_t i = 0; i < keys.size(); ++i) {
pooled_map[keys[i]] = value;
}
pooled_tm.stop();
hash_map.clear();
hash_tm.start();
for (size_t i = 0; i < keys.size(); ++i) {
hash_map[keys[i]] = value;
}
hash_tm.stop();
LOG(INFO) << (random ? "Randomly" : "Sequentially")
<< " inserting " << keys.size()
<< " into FlatMap/std::map/butil::PooledMap/butil::hash_map takes "
<< id_tm.n_elapsed() / keys.size()
<< "/" << std_tm.n_elapsed() / keys.size()
<< "/" << pooled_tm.n_elapsed() / keys.size()
<< "/" << hash_tm.n_elapsed() / keys.size();
if (random) {
random_shuffle(keys.begin(), keys.end());
}
id_tm.start();
for (size_t i = 0; i < keys.size(); ++i) {
id_map.erase(keys[i]);
}
id_tm.stop();
std_tm.start();
for (size_t i = 0; i < keys.size(); ++i) {
std_map.erase(keys[i]);
}
std_tm.stop();
pooled_tm.start();
for (size_t i = 0; i < keys.size(); ++i) {
pooled_map.erase(keys[i]);
}
pooled_tm.stop();
hash_tm.start();
for (size_t i = 0; i < keys.size(); ++i) {
hash_map.erase(keys[i]);
}
hash_tm.stop();
LOG(INFO) << (random ? "Randomly" : "Sequentially")
<< " erasing " << keys.size()
<< " from FlatMap/std::map/butil::PooledMap/butil::hash_map takes "
<< id_tm.n_elapsed() / keys.size()
<< "/" << std_tm.n_elapsed() / keys.size()
<< "/" << pooled_tm.n_elapsed() / keys.size()
<< "/" << hash_tm.n_elapsed() / keys.size();
}
}
template <typename T> void perf_seek(const T& value) {
size_t nkeys[] = { 100, 1000, 10000 };
const size_t NPASS = ARRAY_SIZE(nkeys);
std::vector<uint64_t> keys;
std::vector<uint64_t> rkeys;
butil::FlatMap<uint64_t, T> id_map;
std::map<uint64_t, T> std_map;
butil::PooledMap<uint64_t, T> pooled_map;
butil::hash_map<uint64_t, T> hash_map;
butil::Timer id_tm, std_tm, pooled_tm, hash_tm;
id_map.init((size_t)(nkeys[NPASS-1] * 1.5));
LOG(INFO) << "[ value = " << sizeof(T) << " bytes ]";
for (size_t pass = 0; pass < NPASS; ++pass) {
int start = rand();
keys.clear();
for (size_t i = 0; i < nkeys[pass]; ++i) {
keys.push_back(start + i);
}
id_map.clear();
for (size_t i = 0; i < keys.size(); ++i) {
id_map[keys[i]] = value;
}
std_map.clear();
for (size_t i = 0; i < keys.size(); ++i) {
std_map[keys[i]] = value;
}
pooled_map.clear();
for (size_t i = 0; i < keys.size(); ++i) {
pooled_map[keys[i]] = value;
}
hash_map.clear();
for (size_t i = 0; i < keys.size(); ++i) {
hash_map[keys[i]] = value;
}
random_shuffle(keys.begin(), keys.end());
long sum = 0;
id_tm.start();
for (size_t i = 0; i < keys.size(); ++i) {
sum += *(long*)id_map.seek(keys[i]);
}
id_tm.stop();
std_tm.start();
for (size_t i = 0; i < keys.size(); ++i) {
sum += (long&)std_map.find(keys[i])->second;
}
std_tm.stop();
pooled_tm.start();
for (size_t i = 0; i < keys.size(); ++i) {
sum += (long&)pooled_map.find(keys[i])->second;
}
pooled_tm.stop();
hash_tm.start();
for (size_t i = 0; i < keys.size(); ++i) {
sum += (long&)hash_map.find(keys[i])->second;
}
hash_tm.stop();
LOG(INFO) << "Seeking " << keys.size()
<< " from FlatMap/std::map/butil::PooledMap/butil::hash_map takes "
<< id_tm.n_elapsed() / keys.size()
<< "/" << std_tm.n_elapsed() / keys.size()
<< "/" << pooled_tm.n_elapsed() / keys.size()
<< "/" << hash_tm.n_elapsed() / keys.size()
<< " sum=" << sum;
}
}
struct Dummy1 {
long data[4];
};
struct Dummy2 {
long data[16];
};
TEST_F(FlatMapTest, perf) {
perf_insert_erase<long>(false, 100);
perf_insert_erase<Dummy1>(false, Dummy1());
perf_insert_erase<Dummy2>(false, Dummy2());
perf_insert_erase<long>(true, 100);
perf_insert_erase<Dummy1>(true, Dummy1());
perf_insert_erase<Dummy2>(true, Dummy2());
perf_seek<long>(100);
perf_seek<Dummy1>(Dummy1());
perf_seek<Dummy2>(Dummy2());
perf_seek<long>(100);
perf_seek<Dummy1>(Dummy1());
perf_seek<Dummy2>(Dummy2());
}
TEST_F(FlatMapTest, copy) {
butil::FlatMap<int, int> m1;
butil::FlatMap<int, int> m2;
ASSERT_EQ(0, m1.init(32));
m1[1] = 1;
m1[2] = 2;
m2 = m1;
ASSERT_FALSE(m1.is_too_crowded(m1.size()));
ASSERT_FALSE(m2.is_too_crowded(m1.size()));
}
}