Skip to content

Commit 00d4576

Browse files
committed
[multimapcache] Add an efficient CRTP based write-once multimap cache that can be small.
The properties of this multimap cache are: 1. Values are stored (inline if Small) in a Vector and our map internally maps keys to (start, length) of slices of the Vector. This is done instead of storing arrays refs to ensure that if our array goes from small -> large, we do not have stale pointers. 2. Values are only allowed to be inserted all at once. This is ok, since this is a cache. 3. One is not storing individual small vectors in a map (or state storing SmallVectors). This can inadvertantly add up to using a lot of memory and is not needed for homogenous data.
1 parent 72a23dd commit 00d4576

File tree

3 files changed

+167
-0
lines changed

3 files changed

+167
-0
lines changed

include/swift/Basic/MultiMapCache.h

Lines changed: 105 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,105 @@
1+
//===--- MultiMapCache.h --------------------------------------------------===//
2+
//
3+
// This source file is part of the Swift.org open source project
4+
//
5+
// Copyright (c) 2014 - 2020 Apple Inc. and the Swift project authors
6+
// Licensed under Apache License v2.0 with Runtime Library Exception
7+
//
8+
// See https://swift.org/LICENSE.txt for license information
9+
// See https://swift.org/CONTRIBUTORS.txt for the list of Swift project authors
10+
//
11+
//===----------------------------------------------------------------------===//
12+
13+
#ifndef SWIFT_BASIC_MULTIMAPCACHE_H
14+
#define SWIFT_BASIC_MULTIMAPCACHE_H
15+
16+
#include "swift/Basic/LLVM.h"
17+
#include "llvm/ADT/DenseMap.h"
18+
19+
namespace swift {
20+
21+
/// A CRTP write-once multi-map cache that can be small. It uses a DenseMap
22+
/// internally, so it can be used as a cache without needing to be frozen like
23+
/// FrozenMultiMap (which uses only a vector internally). The Impl class
24+
/// implements the method constructValuesForKey that is used to compute the
25+
/// actual cache value.
26+
///
27+
/// NOTE: constructValuesForKeys is assumed to take a KeyTy and a
28+
/// SmallVectorImpl<ValueTy>. It must append all results to that accumulator and
29+
/// not read any contents of the accumulator.
30+
///
31+
/// NOTE: We store the (size, length) of each ArrayRef<ValueTy> instead of
32+
/// storing the ArrayRef to avoid data invalidation issues caused by SmallVector
33+
/// switching from small to large representations.
34+
///
35+
/// For an example of a subclass implementation see:
36+
/// unittests/Basic/MultiMapCacheTest.cpp.
37+
template <typename ImplType, typename KeyTy, typename ValueTy,
38+
typename MapTy =
39+
llvm::DenseMap<KeyTy, Optional<std::tuple<unsigned, unsigned>>>,
40+
typename VectorTy = std::vector<ValueTy>,
41+
typename VectorTyImpl = VectorTy>
42+
class MultiMapCache {
43+
MapTy valueToDataOffsetIndexMap;
44+
VectorTy data;
45+
46+
constexpr static unsigned ArrayStartOffset = 0;
47+
constexpr static unsigned ArrayLengthOffset = 1;
48+
49+
constexpr ImplType &asImpl() const {
50+
auto *self = const_cast<MultiMapCache *>(this);
51+
return reinterpret_cast<ImplType &>(*self);
52+
}
53+
54+
public:
55+
void clear() {
56+
valueToDataOffsetIndexMap.clear();
57+
data.clear();
58+
}
59+
60+
bool empty() const { return valueToDataOffsetIndexMap.empty(); }
61+
unsigned size() const { return valueToDataOffsetIndexMap.size(); }
62+
63+
Optional<ArrayRef<ValueTy>> get(const KeyTy &key) {
64+
auto iter = valueToDataOffsetIndexMap.try_emplace(key, None);
65+
66+
// If we already have a cached value, just return the cached value.
67+
if (!iter.second) {
68+
return iter.first->second.map(
69+
[&](std::tuple<unsigned, unsigned> startLengthRange) {
70+
return llvm::makeArrayRef(data).slice(
71+
std::get<ArrayStartOffset>(startLengthRange),
72+
std::get<ArrayLengthOffset>(startLengthRange));
73+
});
74+
}
75+
76+
// Otherwise, try to compute the value. If we failed conservatively, return
77+
// None. The iter value already had None by default set to it, this just
78+
// makes it so that we do not need to have a memory dependency and can just
79+
// exit.
80+
unsigned initialOffset = data.size();
81+
82+
// We assume that constructValuesForKey /only/ inserts to the end of data
83+
// and does not inspect any other values in the data array.
84+
if (!asImpl().constructValuesForKey(key, data)) {
85+
return None;
86+
}
87+
88+
// Otherwise, compute our our length, compute our initial ArrayRef<ValueTy>,
89+
// update the map with the start, length, and return the resulting ArrayRef.
90+
unsigned length = data.size() - initialOffset;
91+
iter.first->second = std::make_tuple(initialOffset, length);
92+
auto result = llvm::makeArrayRef(data).slice(initialOffset, length);
93+
return result;
94+
}
95+
};
96+
97+
template <typename ImplType, typename KeyTy, typename ValueTy>
98+
using SmallMultiMapCache = MultiMapCache<
99+
ImplType, KeyTy, ValueTy,
100+
llvm::SmallDenseMap<KeyTy, Optional<std::tuple<unsigned, unsigned>>, 8>,
101+
SmallVector<ValueTy, 32>, SmallVectorImpl<ValueTy>>;
102+
103+
} // namespace swift
104+
105+
#endif

unittests/Basic/CMakeLists.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@ add_swift_unittest(SwiftBasicTests
2020
JSONSerialization.cpp
2121
OptionSetTest.cpp
2222
OwnedStringTest.cpp
23+
MultiMapCacheTest.cpp
2324
PointerIntEnumTest.cpp
2425
PrefixMapTest.cpp
2526
RangeTest.cpp

unittests/Basic/MultiMapCacheTest.cpp

Lines changed: 61 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,61 @@
1+
//===--- MultiMapCacheTest.cpp --------------------------------------------===//
2+
//
3+
// This source file is part of the Swift.org open source project
4+
//
5+
// Copyright (c) 2014 - 2020 Apple Inc. and the Swift project authors
6+
// Licensed under Apache License v2.0 with Runtime Library Exception
7+
//
8+
// See https://swift.org/LICENSE.txt for license information
9+
// See https://swift.org/CONTRIBUTORS.txt for the list of Swift project authors
10+
//
11+
//===----------------------------------------------------------------------===//
12+
13+
#include "swift/Basic/MultiMapCache.h"
14+
#include "swift/Basic/Range.h"
15+
#include "gtest/gtest.h"
16+
#include <random>
17+
18+
using namespace swift;
19+
20+
namespace {
21+
22+
/// A multimap cache that caches the initial 4 powers of each key.
23+
struct PowerMultiMapCache
24+
: public MultiMapCache<PowerMultiMapCache, unsigned, unsigned> {
25+
bool constructValuesForKey(unsigned key, std::vector<unsigned> &data) {
26+
// Construct the first 3 powers of key.
27+
data.push_back(key);
28+
data.push_back(key * key);
29+
data.push_back(key * key * key);
30+
return true;
31+
}
32+
};
33+
34+
} // end anonymous namespace
35+
36+
TEST(MultiMapCache, powersTest) {
37+
PowerMultiMapCache cache;
38+
39+
EXPECT_TRUE(cache.empty());
40+
EXPECT_EQ(cache.size(), 0u);
41+
for (unsigned index : range(1, 256)) {
42+
auto array = *cache.get(index);
43+
for (unsigned power : array) {
44+
EXPECT_EQ(power % index, 0);
45+
}
46+
}
47+
EXPECT_FALSE(cache.empty());
48+
EXPECT_EQ(cache.size(), 255);
49+
for (unsigned index : range(1, 256)) {
50+
auto array = *cache.get(index);
51+
for (unsigned power : array) {
52+
EXPECT_EQ(power % index, 0);
53+
}
54+
}
55+
EXPECT_FALSE(cache.empty());
56+
EXPECT_EQ(cache.size(), 255);
57+
58+
cache.clear();
59+
EXPECT_TRUE(cache.empty());
60+
EXPECT_EQ(cache.size(), 0);
61+
}

0 commit comments

Comments
 (0)