[basic] Add a simple vector backed 2 stage multi map.

I have been using this in a bunch of places in the compiler and rather than
implement it by hand over and over (and maybe messing up), this commit just
commits a correct implementation.

This data structure is a map backed by a vector like data structure. It has two
phases:

1. An insertion phase when the map is mutable and one inserts (key, value) pairs
into the map. These are just appeneded into the storage array.

2. A frozen stage when the map is immutable and one can now perform map queries
on the multimap.

The map transitions from the mutable, thawed phase to the immutable, frozen
phase by performing a stable_sort of its internal storage by only the key. Since
this is a stable_sort, we know that the relative insertion order of values is
preserved if their keys equal. Thus the sorting will have created contiguous
regions in the array of values, all mapped to the same key, that are insertion
order. Thus by finding the lower_bound for a given key, we are guaranteed to get
the first element in that continguous range. We can then do a forward search to
find the end of the region, allowing us to then return an ArrayRef to these
internal values.

The reason why I keep on finding myself using this is that this map enables one
to map a key to an array of values without needing to store small vectors in a
map or use heap allocated memory, all key, value pairs are stored inline (in
potentially a single SmallVector given that one is using SmallFrozenMultiMap).
This commit is contained in:
Michael Gottesman
2020-01-06 12:03:20 -08:00
parent d6eebe9faa
commit 96a0c7931d
3 changed files with 375 additions and 0 deletions

View File

@@ -0,0 +1,187 @@
//===--- FrozenMultiMapTest.cpp -------------------------------------------===//
//
// This source file is part of the Swift.org open source project
//
// Copyright (c) 2014 - 2020 Apple Inc. and the Swift project authors
// Licensed under Apache License v2.0 with Runtime Library Exception
//
// See https://swift.org/LICENSE.txt for license information
// See https://swift.org/CONTRIBUTORS.txt for the list of Swift project authors
//
//===----------------------------------------------------------------------===//
#define DEBUG_TYPE "swift-frozen-multi-map-test"
#include "swift/Basic/FrozenMultiMap.h"
#include "swift/Basic/LLVM.h"
#include "swift/Basic/Lazy.h"
#include "swift/Basic/NullablePtr.h"
#include "swift/Basic/Range.h"
#include "swift/Basic/STLExtras.h"
#include "llvm/ADT/Optional.h"
#include "llvm/ADT/STLExtras.h"
#include "llvm/ADT/SmallString.h"
#include "llvm/ADT/StringExtras.h"
#include "llvm/Support/Debug.h"
#include "llvm/Support/raw_ostream.h"
#include "gtest/gtest.h"
#include <map>
#include <random>
#include <set>
using namespace swift;
namespace {
class Canary {
static unsigned currentID;
unsigned id;
public:
static void resetIDs() { currentID = 0; }
Canary(unsigned id) : id(id) {}
Canary() {
id = currentID;
++currentID;
}
unsigned getID() const { return id; }
bool operator<(const Canary &other) const { return id < other.id; }
bool operator==(const Canary &other) const { return id == other.id; }
bool operator!=(const Canary &other) const { return !(*this == other); }
};
unsigned Canary::currentID = 0;
} // namespace
TEST(FrozenMultiMapCustomTest, SimpleFind) {
Canary::resetIDs();
FrozenMultiMap<Canary, Canary> map;
auto key1 = Canary();
auto key2 = Canary();
map.insert(key1, Canary());
map.insert(key1, Canary());
map.insert(key1, Canary());
map.insert(key2, Canary());
map.insert(key2, Canary());
map.setFrozen();
EXPECT_EQ(map.size(), 5u);
{
auto r = map.find(key1);
EXPECT_TRUE(r.hasValue());
EXPECT_EQ(r->size(), 3u);
EXPECT_EQ((*r)[0].getID(), 2u);
EXPECT_EQ((*r)[1].getID(), 3u);
EXPECT_EQ((*r)[2].getID(), 4u);
}
{
auto r = map.find(key2);
EXPECT_TRUE(r.hasValue());
EXPECT_EQ(r->size(), 2u);
EXPECT_EQ((*r)[0].getID(), 5u);
EXPECT_EQ((*r)[1].getID(), 6u);
}
}
TEST(FrozenMultiMapCustomTest, SimpleIter) {
Canary::resetIDs();
FrozenMultiMap<Canary, Canary> map;
auto key1 = Canary();
auto key2 = Canary();
map.insert(key1, Canary());
map.insert(key1, Canary());
map.insert(key1, Canary());
map.insert(key2, Canary());
map.insert(key2, Canary());
map.setFrozen();
EXPECT_EQ(map.size(), 5u);
auto range = map.getRange();
EXPECT_EQ(std::distance(range.begin(), range.end()), 2);
auto iter = range.begin();
{
auto p = *iter;
EXPECT_EQ(p.first.getID(), key1.getID());
EXPECT_EQ(p.second.size(), 3u);
EXPECT_EQ(p.second[0].getID(), 2u);
EXPECT_EQ(p.second[1].getID(), 3u);
EXPECT_EQ(p.second[2].getID(), 4u);
}
++iter;
{
auto p = *iter;
EXPECT_EQ(p.first.getID(), key2.getID());
EXPECT_EQ(p.second.size(), 2u);
EXPECT_EQ(p.second[0].getID(), 5u);
EXPECT_EQ(p.second[1].getID(), 6u);
}
}
TEST(FrozenMultiMapCustomTest, RandomAgainstStdMultiMap) {
Canary::resetIDs();
FrozenMultiMap<unsigned, unsigned> map;
std::multimap<unsigned, unsigned> stdMultiMap;
auto seed =
std::chrono::high_resolution_clock::now().time_since_epoch().count();
std::mt19937 mt_rand(seed);
std::vector<unsigned> keyIdList;
for (unsigned i = 0; i < 1024; ++i) {
unsigned keyID = mt_rand() % 20;
keyIdList.push_back(keyID);
for (unsigned valueID = (mt_rand()) % 15; valueID < 15; ++valueID) {
map.insert(keyID, valueID);
stdMultiMap.emplace(keyID, valueID);
}
}
map.setFrozen();
// Then for each key.
for (unsigned i : keyIdList) {
// Make sure that we have the same elements in the same order for each key.
auto range = *map.find(i);
auto stdRange = stdMultiMap.equal_range(i);
EXPECT_EQ(std::distance(range.begin(), range.end()),
std::distance(stdRange.first, stdRange.second));
auto modernStdRange = llvm::make_range(stdRange.first, stdRange.second);
for (auto p : llvm::zip(range, modernStdRange)) {
unsigned lhs = std::get<0>(p);
unsigned rhs = std::get<1>(p).second;
EXPECT_EQ(lhs, rhs);
}
}
// Then check that when we iterate over both ranges, we get the same order.
{
auto range = map.getRange();
auto rangeIter = range.begin();
auto stdRangeIter = stdMultiMap.begin();
while (rangeIter != range.end()) {
auto rangeElt = *rangeIter;
for (unsigned i : indices(rangeElt.second)) {
EXPECT_EQ(rangeElt.first, stdRangeIter->first);
EXPECT_EQ(rangeElt.second[i], stdRangeIter->second);
++stdRangeIter;
}
++rangeIter;
}
}
}