Skip to content

Commit 8e447a4

Browse files
authored
Merge pull request swiftlang#30665 from gottesmm/pr-148512f9e52b5c768ddd3312717c2fffcd71a96e
[multimapcache] Add an efficient CRTP based write-once multimap cache that can be small.
2 parents 291f432 + 00d4576 commit 8e447a4

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)