Add index-based set functionality to NonAllocatingMap.

This enables repeatedly setting a value based on index, which avoids a
linear scan of the entry table after the first SetKeyValue().

Bug: chromium:598854
Change-Id: I9964670a09dcd8ff76180d031a373f20990bf4d8
Reviewed-on: https://chromium-review.googlesource.com/757579
Reviewed-by: Mark Mentovai <mark@chromium.org>
This commit is contained in:
Robert Sesek 2017-11-07 18:24:26 -05:00
parent 3bbf3fb0db
commit 8a0edac9ab
2 changed files with 79 additions and 31 deletions

View File

@ -147,39 +147,42 @@ class NonAllocatingMap {
if (!key) if (!key)
return NULL; return NULL;
const Entry* entry = GetConstEntryForKey(key); size_t index = GetEntryIndexForKey(key);
if (!entry) if (index == num_entries)
return NULL; return NULL;
return entry->value; return entries_[index].value;
} }
// Stores |value| into |key|, replacing the existing value if |key| is // Stores |value| into |key|, replacing the existing value if |key| is
// already present. |key| must not be NULL. If |value| is NULL, the key is // already present. |key| must not be NULL. If |value| is NULL, the key is
// removed from the map. If there is no more space in the map, then the // removed from the map. If there is no more space in the map, then the
// operation silently fails. // operation silently fails. Returns an index into the map that can be used
void SetKeyValue(const char* key, const char* value) { // to quickly access the entry, or |num_entries| on failure or when clearing
// a key with a null value.
size_t SetKeyValue(const char* key, const char* value) {
if (!value) { if (!value) {
RemoveKey(key); RemoveKey(key);
return; return num_entries;
} }
assert(key); assert(key);
if (!key) if (!key)
return; return num_entries;
// Key must not be an empty string. // Key must not be an empty string.
assert(key[0] != '\0'); assert(key[0] != '\0');
if (key[0] == '\0') if (key[0] == '\0')
return; return num_entries;
Entry* entry = GetEntryForKey(key); size_t entry_index = GetEntryIndexForKey(key);
// If it does not yet exist, attempt to insert it. // If it does not yet exist, attempt to insert it.
if (!entry) { if (entry_index == num_entries) {
for (size_t i = 0; i < num_entries; ++i) { for (size_t i = 0; i < num_entries; ++i) {
if (!entries_[i].is_active()) { if (!entries_[i].is_active()) {
entry = &entries_[i]; entry_index = i;
Entry* entry = &entries_[i];
strncpy(entry->key, key, key_size); strncpy(entry->key, key, key_size);
entry->key[key_size - 1] = '\0'; entry->key[key_size - 1] = '\0';
@ -190,8 +193,8 @@ class NonAllocatingMap {
} }
// If the map is out of space, entry will be NULL. // If the map is out of space, entry will be NULL.
if (!entry) if (entry_index == num_entries)
return; return num_entries;
#ifndef NDEBUG #ifndef NDEBUG
// Sanity check that the key only appears once. // Sanity check that the key only appears once.
@ -203,28 +206,46 @@ class NonAllocatingMap {
assert(count == 1); assert(count == 1);
#endif #endif
strncpy(entries_[entry_index].value, value, value_size);
entries_[entry_index].value[value_size - 1] = '\0';
return entry_index;
}
// Sets a value for a key that has already been set with SetKeyValue(), using
// the index returned from that function.
void SetValueAtIndex(size_t index, const char* value) {
assert(index < num_entries);
if (index >= num_entries)
return;
Entry* entry = &entries_[index];
assert(entry->key[0] != '\0');
strncpy(entry->value, value, value_size); strncpy(entry->value, value, value_size);
entry->value[value_size - 1] = '\0'; entry->value[value_size - 1] = '\0';
} }
// Given |key|, removes any associated value. |key| must not be NULL. If // Given |key|, removes any associated value. |key| must not be NULL. If
// the key is not found, this is a noop. // the key is not found, this is a noop. This invalidates the index
// returned by SetKeyValue().
bool RemoveKey(const char* key) { bool RemoveKey(const char* key) {
assert(key); assert(key);
if (!key) if (!key)
return false; return false;
Entry* entry = GetEntryForKey(key); return RemoveAtIndex(GetEntryIndexForKey(key));
if (entry) {
entry->key[0] = '\0';
entry->value[0] = '\0';
return true;
} }
#ifndef NDEBUG // Removes a value and key using an index that was returned from
assert(GetEntryForKey(key) == NULL); // SetKeyValue(). After a call to this function, the index is invalidated.
#endif bool RemoveAtIndex(size_t index) {
if (index >= num_entries)
return false; return false;
entries_[index].key[0] = '\0';
entries_[index].value[0] = '\0';
return true;
} }
// Places a serialized version of the map into |map| and returns the size. // Places a serialized version of the map into |map| and returns the size.
@ -237,17 +258,13 @@ class NonAllocatingMap {
} }
private: private:
const Entry* GetConstEntryForKey(const char* key) const { size_t GetEntryIndexForKey(const char* key) const {
for (size_t i = 0; i < num_entries; ++i) { for (size_t i = 0; i < num_entries; ++i) {
if (strncmp(key, entries_[i].key, key_size) == 0) { if (strncmp(key, entries_[i].key, key_size) == 0) {
return &entries_[i]; return i;
} }
} }
return NULL; return num_entries;
}
Entry* GetEntryForKey(const char* key) {
return const_cast<Entry*>(GetConstEntryForKey(key));
} }
Entry entries_[NumEntries]; Entry entries_[NumEntries];

View File

@ -288,6 +288,37 @@ TEST(NonAllocatingMapTest, OutOfSpace) {
EXPECT_FALSE(map.GetValueForKey("c")); EXPECT_FALSE(map.GetValueForKey("c"));
} }
TEST(NonAllocatingMapTest, ByIndex) {
NonAllocatingMap<10, 10, 3> map;
size_t index1 = map.SetKeyValue("test", "one");
EXPECT_TRUE(index1 >= 0 && index1 <= map.num_entries);
size_t index2 = map.SetKeyValue("moo", "foo");
EXPECT_TRUE(index2 >= 0 && index2 <= map.num_entries);
EXPECT_NE(index1, index2);
size_t index3 = map.SetKeyValue("blob", "kebab");
EXPECT_TRUE(index3 >= 0 && index3 <= map.num_entries);
EXPECT_NE(index2, index3);
size_t index4 = map.SetKeyValue("nogo", "full");
EXPECT_TRUE(index4 == map.num_entries);
EXPECT_STREQ("one", map.GetValueForKey("test"));
EXPECT_STREQ("foo", map.GetValueForKey("moo"));
EXPECT_STREQ("kebab", map.GetValueForKey("blob"));
map.SetValueAtIndex(index2, "booo");
EXPECT_STREQ("booo", map.GetValueForKey("moo"));
EXPECT_TRUE(map.RemoveAtIndex(index1));
EXPECT_FALSE(map.GetValueForKey("test"));
EXPECT_FALSE(map.RemoveAtIndex(map.num_entries));
EXPECT_FALSE(map.RemoveAtIndex(9999));
}
#ifndef NDEBUG #ifndef NDEBUG
TEST(NonAllocatingMapTest, NullKey) { TEST(NonAllocatingMapTest, NullKey) {