BLI: New Edgehash and EdgeSet implementation
The new data structure uses open addressing instead of chaining to resolve collisions in the hash table. This new structure was never slower than the old implementation in my tests. Code that first inserts all edges and then iterates through all edges (e.g. to remove duplicates) benefits the most, because the `EdgeHashIterator` becomes a simple for loop over a continuous array. Reviewer: campbellbarton Differential Revision: D4050
This commit is contained in:
405
tests/gtests/blenlib/BLI_edgehash_test.cc
Normal file
405
tests/gtests/blenlib/BLI_edgehash_test.cc
Normal file
@@ -0,0 +1,405 @@
|
||||
/* Apache License, Version 2.0 */
|
||||
|
||||
#include "testing/testing.h"
|
||||
#include <vector>
|
||||
#include <algorithm>
|
||||
|
||||
extern "C" {
|
||||
#include "BLI_utildefines.h"
|
||||
#include "BLI_edgehash.h"
|
||||
}
|
||||
|
||||
#define VALUE_1 POINTER_FROM_INT(1)
|
||||
#define VALUE_2 POINTER_FROM_INT(2)
|
||||
#define VALUE_3 POINTER_FROM_INT(3)
|
||||
|
||||
TEST(edgehash, InsertIncreasesLength)
|
||||
{
|
||||
EdgeHash *eh = BLI_edgehash_new(__func__);
|
||||
|
||||
ASSERT_EQ(BLI_edgehash_len(eh), 0);
|
||||
BLI_edgehash_insert(eh, 1, 2, VALUE_1);
|
||||
ASSERT_EQ(BLI_edgehash_len(eh), 1);
|
||||
|
||||
BLI_edgehash_free(eh, nullptr);
|
||||
}
|
||||
|
||||
TEST(edgehash, ReinsertNewIncreasesLength)
|
||||
{
|
||||
EdgeHash *eh = BLI_edgehash_new(__func__);
|
||||
|
||||
ASSERT_EQ(BLI_edgehash_len(eh), 0);
|
||||
BLI_edgehash_reinsert(eh, 1, 2, VALUE_1);
|
||||
ASSERT_EQ(BLI_edgehash_len(eh), 1);
|
||||
|
||||
BLI_edgehash_free(eh, nullptr);
|
||||
}
|
||||
|
||||
TEST(edgehash, ReinsertExistingDoesNotIncreaseLength)
|
||||
{
|
||||
EdgeHash *eh = BLI_edgehash_new(__func__);
|
||||
|
||||
ASSERT_EQ(BLI_edgehash_len(eh), 0);
|
||||
BLI_edgehash_reinsert(eh, 1, 2, VALUE_1);
|
||||
ASSERT_EQ(BLI_edgehash_len(eh), 1);
|
||||
BLI_edgehash_reinsert(eh, 1, 2, VALUE_2);
|
||||
ASSERT_EQ(BLI_edgehash_len(eh), 1);
|
||||
BLI_edgehash_reinsert(eh, 2, 1, VALUE_2);
|
||||
ASSERT_EQ(BLI_edgehash_len(eh), 1);
|
||||
|
||||
BLI_edgehash_free(eh, nullptr);
|
||||
}
|
||||
|
||||
TEST(edgehash, ReinsertCanChangeValue)
|
||||
{
|
||||
EdgeHash *eh = BLI_edgehash_new(__func__);
|
||||
|
||||
BLI_edgehash_insert(eh, 1, 2, VALUE_1);
|
||||
ASSERT_EQ(BLI_edgehash_lookup(eh, 1, 2), VALUE_1);
|
||||
BLI_edgehash_reinsert(eh, 2, 1, VALUE_2);
|
||||
ASSERT_EQ(BLI_edgehash_lookup(eh, 1, 2), VALUE_2);
|
||||
BLI_edgehash_reinsert(eh, 1, 2, VALUE_3);
|
||||
ASSERT_EQ(BLI_edgehash_lookup(eh, 2, 1), VALUE_3);
|
||||
|
||||
BLI_edgehash_free(eh, nullptr);
|
||||
}
|
||||
|
||||
TEST(edgehash, LookupExisting)
|
||||
{
|
||||
EdgeHash *eh = BLI_edgehash_new(__func__);
|
||||
|
||||
BLI_edgehash_insert(eh, 1, 2, VALUE_1);
|
||||
ASSERT_EQ(BLI_edgehash_lookup(eh, 1, 2), VALUE_1);
|
||||
ASSERT_EQ(BLI_edgehash_lookup(eh, 2, 1), VALUE_1);
|
||||
|
||||
BLI_edgehash_free(eh, nullptr);
|
||||
}
|
||||
|
||||
TEST(edgehash, LookupNonExisting)
|
||||
{
|
||||
EdgeHash *eh = BLI_edgehash_new(__func__);
|
||||
|
||||
ASSERT_EQ(BLI_edgehash_lookup(eh, 1, 2), nullptr);
|
||||
|
||||
BLI_edgehash_free(eh, nullptr);
|
||||
}
|
||||
|
||||
TEST(edgehash, LookupNonExistingWithDefault)
|
||||
{
|
||||
EdgeHash *eh = BLI_edgehash_new(__func__);
|
||||
|
||||
ASSERT_EQ(BLI_edgehash_lookup_default(eh, 1, 2, VALUE_1), VALUE_1);
|
||||
|
||||
BLI_edgehash_free(eh, nullptr);
|
||||
}
|
||||
|
||||
TEST(edgehash, LookupExistingWithDefault)
|
||||
{
|
||||
EdgeHash *eh = BLI_edgehash_new(__func__);
|
||||
|
||||
BLI_edgehash_insert(eh, 1, 2, VALUE_1);
|
||||
ASSERT_EQ(BLI_edgehash_lookup_default(eh, 1, 2, VALUE_2), VALUE_1);
|
||||
|
||||
BLI_edgehash_free(eh, nullptr);
|
||||
}
|
||||
|
||||
TEST(edgehash, LookupPExisting)
|
||||
{
|
||||
EdgeHash *eh = BLI_edgehash_new(__func__);
|
||||
|
||||
void *value = VALUE_1;
|
||||
BLI_edgehash_insert(eh, 1, 2, value);
|
||||
void **value_p = BLI_edgehash_lookup_p(eh, 1, 2);
|
||||
ASSERT_EQ(*value_p, VALUE_1);
|
||||
*value_p = VALUE_2;
|
||||
ASSERT_EQ(BLI_edgehash_lookup(eh, 1, 2), VALUE_2);
|
||||
|
||||
BLI_edgehash_free(eh, nullptr);
|
||||
}
|
||||
|
||||
TEST(edgehash, LookupPNonExisting)
|
||||
{
|
||||
EdgeHash *eh = BLI_edgehash_new(__func__);
|
||||
|
||||
ASSERT_EQ(BLI_edgehash_lookup_p(eh, 1, 2), nullptr);
|
||||
|
||||
BLI_edgehash_free(eh, nullptr);
|
||||
}
|
||||
|
||||
TEST(edgehash, EnsurePNonExisting)
|
||||
{
|
||||
EdgeHash *eh = BLI_edgehash_new(__func__);
|
||||
|
||||
void **value_p;
|
||||
bool existed = BLI_edgehash_ensure_p(eh, 1, 2, &value_p);
|
||||
ASSERT_FALSE(existed);
|
||||
*value_p = VALUE_1;
|
||||
ASSERT_EQ(BLI_edgehash_lookup(eh, 1, 2), VALUE_1);
|
||||
|
||||
BLI_edgehash_free(eh, nullptr);
|
||||
}
|
||||
|
||||
TEST(edgehash, EnsurePExisting)
|
||||
{
|
||||
EdgeHash *eh = BLI_edgehash_new(__func__);
|
||||
|
||||
BLI_edgehash_insert(eh, 1, 2, VALUE_1);
|
||||
void **value_p;
|
||||
bool existed = BLI_edgehash_ensure_p(eh, 1, 2, &value_p);
|
||||
ASSERT_TRUE(existed);
|
||||
ASSERT_EQ(*value_p, VALUE_1);
|
||||
*value_p = VALUE_2;
|
||||
ASSERT_EQ(BLI_edgehash_lookup(eh, 1, 2), VALUE_2);
|
||||
|
||||
BLI_edgehash_free(eh, nullptr);
|
||||
}
|
||||
|
||||
TEST(edgehash, RemoveExistingDecreasesLength)
|
||||
{
|
||||
EdgeHash *eh = BLI_edgehash_new(__func__);
|
||||
|
||||
BLI_edgehash_insert(eh, 1, 2, VALUE_1);
|
||||
ASSERT_EQ(BLI_edgehash_len(eh), 1);
|
||||
bool has_been_removed = BLI_edgehash_remove(eh, 1, 2, nullptr);
|
||||
ASSERT_EQ(BLI_edgehash_len(eh), 0);
|
||||
ASSERT_TRUE(has_been_removed);
|
||||
|
||||
BLI_edgehash_free(eh, nullptr);
|
||||
}
|
||||
|
||||
TEST(edgehash, RemoveNonExistingDoesNotDecreaseLength)
|
||||
{
|
||||
EdgeHash *eh = BLI_edgehash_new(__func__);
|
||||
|
||||
BLI_edgehash_insert(eh, 1, 2, VALUE_1);
|
||||
ASSERT_EQ(BLI_edgehash_len(eh), 1);
|
||||
bool has_been_removed = BLI_edgehash_remove(eh, 4, 5, nullptr);
|
||||
ASSERT_EQ(BLI_edgehash_len(eh), 1);
|
||||
ASSERT_FALSE(has_been_removed);
|
||||
|
||||
BLI_edgehash_free(eh, nullptr);
|
||||
}
|
||||
|
||||
TEST(edgehash, PopKeyTwice)
|
||||
{
|
||||
EdgeHash *eh = BLI_edgehash_new(__func__);
|
||||
|
||||
BLI_edgehash_insert(eh, 1, 2, VALUE_1);
|
||||
ASSERT_EQ(BLI_edgehash_popkey(eh, 1, 2), VALUE_1);
|
||||
ASSERT_EQ(BLI_edgehash_popkey(eh, 1, 2), nullptr);
|
||||
|
||||
BLI_edgehash_free(eh, nullptr);
|
||||
}
|
||||
|
||||
TEST(edgehash, LookupInvertedIndices)
|
||||
{
|
||||
EdgeHash *eh = BLI_edgehash_new(__func__);
|
||||
|
||||
BLI_edgehash_insert(eh, 1, 2, VALUE_1);
|
||||
ASSERT_EQ(BLI_edgehash_lookup(eh, 2, 1), VALUE_1);
|
||||
|
||||
BLI_edgehash_free(eh, nullptr);
|
||||
}
|
||||
|
||||
TEST(edgehash, HasKeyExisting)
|
||||
{
|
||||
EdgeHash *eh = BLI_edgehash_new(__func__);
|
||||
|
||||
BLI_edgehash_insert(eh, 1, 2, VALUE_1);
|
||||
ASSERT_TRUE(BLI_edgehash_haskey(eh, 1, 2));
|
||||
ASSERT_TRUE(BLI_edgehash_haskey(eh, 2, 1));
|
||||
|
||||
BLI_edgehash_free(eh, nullptr);
|
||||
}
|
||||
|
||||
TEST(edgehash, HasKeyNonExisting)
|
||||
{
|
||||
EdgeHash *eh = BLI_edgehash_new(__func__);
|
||||
|
||||
ASSERT_FALSE(BLI_edgehash_haskey(eh, 1, 2));
|
||||
|
||||
BLI_edgehash_free(eh, nullptr);
|
||||
}
|
||||
|
||||
TEST(edgehash, ClearSetsLengthToZero)
|
||||
{
|
||||
EdgeHash *eh = BLI_edgehash_new(__func__);
|
||||
|
||||
BLI_edgehash_insert(eh, 1, 2, VALUE_1);
|
||||
BLI_edgehash_insert(eh, 1, 2, VALUE_2);
|
||||
ASSERT_EQ(BLI_edgehash_len(eh), 2);
|
||||
BLI_edgehash_clear(eh, nullptr);
|
||||
ASSERT_EQ(BLI_edgehash_len(eh), 0);
|
||||
|
||||
BLI_edgehash_free(eh, nullptr);
|
||||
}
|
||||
|
||||
TEST(edgehash, IteratorFindsAllValues)
|
||||
{
|
||||
EdgeHash *eh = BLI_edgehash_new(__func__);
|
||||
|
||||
BLI_edgehash_insert(eh, 1, 2, VALUE_1);
|
||||
BLI_edgehash_insert(eh, 1, 3, VALUE_2);
|
||||
BLI_edgehash_insert(eh, 1, 4, VALUE_3);
|
||||
|
||||
EdgeHashIterator *ehi = BLI_edgehashIterator_new(eh);
|
||||
auto a = BLI_edgehashIterator_getValue(ehi);
|
||||
BLI_edgehashIterator_step(ehi);
|
||||
auto b = BLI_edgehashIterator_getValue(ehi);
|
||||
BLI_edgehashIterator_step(ehi);
|
||||
auto c = BLI_edgehashIterator_getValue(ehi);
|
||||
BLI_edgehashIterator_step(ehi);
|
||||
|
||||
ASSERT_NE(a, b);
|
||||
ASSERT_NE(b, c);
|
||||
ASSERT_NE(a, c);
|
||||
ASSERT_TRUE(ELEM(a, VALUE_1, VALUE_2, VALUE_3));
|
||||
ASSERT_TRUE(ELEM(b, VALUE_1, VALUE_2, VALUE_3));
|
||||
ASSERT_TRUE(ELEM(c, VALUE_1, VALUE_2, VALUE_3));
|
||||
|
||||
BLI_edgehashIterator_free(ehi);
|
||||
BLI_edgehash_free(eh, nullptr);
|
||||
}
|
||||
|
||||
TEST(edgehash, IterateIsDone)
|
||||
{
|
||||
EdgeHash *eh = BLI_edgehash_new(__func__);
|
||||
|
||||
BLI_edgehash_insert(eh, 1, 2, VALUE_1);
|
||||
BLI_edgehash_insert(eh, 1, 3, VALUE_2);
|
||||
BLI_edgehash_insert(eh, 1, 4, VALUE_3);
|
||||
|
||||
EdgeHashIterator *ehi = BLI_edgehashIterator_new(eh);
|
||||
ASSERT_FALSE(BLI_edgehashIterator_isDone(ehi));
|
||||
BLI_edgehashIterator_step(ehi);
|
||||
ASSERT_FALSE(BLI_edgehashIterator_isDone(ehi));
|
||||
BLI_edgehashIterator_step(ehi);
|
||||
ASSERT_FALSE(BLI_edgehashIterator_isDone(ehi));
|
||||
BLI_edgehashIterator_step(ehi);
|
||||
ASSERT_TRUE(BLI_edgehashIterator_isDone(ehi));
|
||||
|
||||
BLI_edgehashIterator_free(ehi);
|
||||
BLI_edgehash_free(eh, nullptr);
|
||||
}
|
||||
|
||||
TEST(edgehash, DoubleRemove)
|
||||
{
|
||||
EdgeHash *eh = BLI_edgehash_new(__func__);
|
||||
|
||||
BLI_edgehash_insert(eh, 1, 2, VALUE_1);
|
||||
BLI_edgehash_insert(eh, 1, 3, VALUE_2);
|
||||
BLI_edgehash_insert(eh, 1, 4, VALUE_3);
|
||||
ASSERT_EQ(BLI_edgehash_len(eh), 3);
|
||||
|
||||
BLI_edgehash_remove(eh, 1, 2, nullptr);
|
||||
BLI_edgehash_remove(eh, 1, 3, nullptr);
|
||||
ASSERT_EQ(BLI_edgehash_len(eh), 1);
|
||||
|
||||
BLI_edgehash_free(eh, nullptr);
|
||||
}
|
||||
|
||||
struct Edge {
|
||||
uint v1, v2;
|
||||
};
|
||||
|
||||
TEST(edgehash, StressTest)
|
||||
{
|
||||
std::srand(0);
|
||||
int amount = 10000;
|
||||
|
||||
std::vector<Edge> edges;
|
||||
for (int i = 0; i < amount; i++) {
|
||||
edges.push_back({(uint)i, amount + (uint)std::rand() % 12345});
|
||||
}
|
||||
|
||||
EdgeHash *eh = BLI_edgehash_new(__func__);
|
||||
|
||||
/* first insert all the edges */
|
||||
for (int i = 0; i < edges.size(); i++) {
|
||||
BLI_edgehash_insert(eh, edges[i].v1, edges[i].v2, POINTER_FROM_INT(i));
|
||||
}
|
||||
|
||||
std::vector<Edge> shuffled = edges;
|
||||
std::random_shuffle(shuffled.begin(), shuffled.end());
|
||||
|
||||
/* then remove half of them */
|
||||
int remove_until = shuffled.size() / 2;
|
||||
for (int i = 0; i < remove_until; i++) {
|
||||
BLI_edgehash_remove(eh, shuffled[i].v2, shuffled[i].v1, nullptr);
|
||||
}
|
||||
|
||||
ASSERT_EQ(BLI_edgehash_len(eh), edges.size() - remove_until);
|
||||
|
||||
/* check if the right ones have been removed */
|
||||
for (int i = 0; i < shuffled.size(); i++) {
|
||||
bool haskey = BLI_edgehash_haskey(eh, shuffled[i].v1, shuffled[i].v2);
|
||||
if (i < remove_until) ASSERT_FALSE(haskey);
|
||||
else ASSERT_TRUE(haskey);
|
||||
}
|
||||
|
||||
/* reinsert all edges */
|
||||
for (int i = 0; i < edges.size(); i++) {
|
||||
BLI_edgehash_reinsert(eh, edges[i].v1, edges[i].v2, POINTER_FROM_INT(i));
|
||||
}
|
||||
|
||||
ASSERT_EQ(BLI_edgehash_len(eh), edges.size());
|
||||
|
||||
/* pop all edges */
|
||||
for (int i = 0; i < edges.size(); i++) {
|
||||
int value = POINTER_AS_INT(BLI_edgehash_popkey(eh, edges[i].v1, edges[i].v2));
|
||||
ASSERT_EQ(i, value);
|
||||
}
|
||||
|
||||
ASSERT_EQ(BLI_edgehash_len(eh), 0);
|
||||
|
||||
BLI_edgehash_free(eh, nullptr);
|
||||
}
|
||||
|
||||
TEST(edgeset, AddNonExistingIncreasesLength)
|
||||
{
|
||||
EdgeSet *es = BLI_edgeset_new(__func__);
|
||||
|
||||
ASSERT_EQ(BLI_edgeset_len(es), 0);
|
||||
BLI_edgeset_add(es, 1, 2);
|
||||
ASSERT_EQ(BLI_edgeset_len(es), 1);
|
||||
BLI_edgeset_add(es, 1, 3);
|
||||
ASSERT_EQ(BLI_edgeset_len(es), 2);
|
||||
BLI_edgeset_add(es, 1, 4);
|
||||
ASSERT_EQ(BLI_edgeset_len(es), 3);
|
||||
|
||||
BLI_edgeset_free(es);
|
||||
}
|
||||
|
||||
TEST(edgeset, AddExistingDoesNotIncreaseLength)
|
||||
{
|
||||
EdgeSet *es = BLI_edgeset_new(__func__);
|
||||
|
||||
ASSERT_EQ(BLI_edgeset_len(es), 0);
|
||||
BLI_edgeset_add(es, 1, 2);
|
||||
ASSERT_EQ(BLI_edgeset_len(es), 1);
|
||||
BLI_edgeset_add(es, 2, 1);
|
||||
ASSERT_EQ(BLI_edgeset_len(es), 1);
|
||||
BLI_edgeset_add(es, 1, 2);
|
||||
ASSERT_EQ(BLI_edgeset_len(es), 1);
|
||||
|
||||
BLI_edgeset_free(es);
|
||||
}
|
||||
|
||||
TEST(edgeset, HasKeyNonExisting)
|
||||
{
|
||||
EdgeSet *es = BLI_edgeset_new(__func__);
|
||||
|
||||
ASSERT_FALSE(BLI_edgeset_haskey(es, 1, 2));
|
||||
|
||||
BLI_edgeset_free(es);
|
||||
}
|
||||
|
||||
TEST(edgeset, HasKeyExisting)
|
||||
{
|
||||
EdgeSet *es = BLI_edgeset_new(__func__);
|
||||
|
||||
BLI_edgeset_insert(es, 1, 2);
|
||||
ASSERT_TRUE(BLI_edgeset_haskey(es, 1, 2));
|
||||
|
||||
BLI_edgeset_free(es);
|
||||
}
|
@@ -44,6 +44,7 @@ endif()
|
||||
BLENDER_TEST(BLI_array_store "bf_blenlib")
|
||||
BLENDER_TEST(BLI_array_utils "bf_blenlib")
|
||||
BLENDER_TEST(BLI_expr_pylike_eval "bf_blenlib")
|
||||
BLENDER_TEST(BLI_edgehash "bf_blenlib")
|
||||
BLENDER_TEST(BLI_ghash "bf_blenlib")
|
||||
BLENDER_TEST(BLI_hash_mm2a "bf_blenlib")
|
||||
BLENDER_TEST(BLI_heap "bf_blenlib")
|
||||
|
Reference in New Issue
Block a user