Skip to content

Commit b21fc05

Browse files
authored
Merge pull request #13211 from xokdvium/pos-table
libexpr: Actually cache line information in PosTable
2 parents dc1a513 + 5ea81f5 commit b21fc05

File tree

3 files changed

+79
-25
lines changed

3 files changed

+79
-25
lines changed

src/libutil/include/nix/util/lru-cache.hh

Lines changed: 35 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,18 @@ private:
3333
Data data;
3434
LRU lru;
3535

36+
/**
37+
* Move this item to the back of the LRU list.
38+
*/
39+
void promote(LRU::iterator it)
40+
{
41+
/* Think of std::list iterators as stable pointers to the list node,
42+
* which never get invalidated. Thus, we can reuse the same lru list
43+
* element and just splice it to the back of the list without the need
44+
* to update its value in the key -> list iterator map. */
45+
lru.splice(/*pos=*/lru.end(), /*other=*/lru, it);
46+
}
47+
3648
public:
3749

3850
LRUCache(size_t capacity)
@@ -83,28 +95,40 @@ public:
8395
/**
8496
* Look up an item in the cache. If it exists, it becomes the most
8597
* recently used item.
86-
* */
98+
*
99+
* @returns corresponding cache entry, std::nullopt if it's not in the cache
100+
*/
87101
template<typename K>
88102
std::optional<Value> get(const K & key)
89103
{
90104
auto i = data.find(key);
91105
if (i == data.end())
92106
return {};
93107

94-
/**
95-
* Move this item to the back of the LRU list.
96-
*
97-
* Think of std::list iterators as stable pointers to the list node,
98-
* which never get invalidated. Thus, we can reuse the same lru list
99-
* element and just splice it to the back of the list without the need
100-
* to update its value in the key -> list iterator map.
101-
*/
102108
auto & [it, value] = i->second;
103-
lru.splice(/*pos=*/lru.end(), /*other=*/lru, it.it);
104-
109+
promote(it.it);
105110
return value;
106111
}
107112

113+
/**
114+
* Look up an item in the cache. If it exists, it becomes the most
115+
* recently used item.
116+
*
117+
* @returns mutable pointer to the corresponding cache entry, nullptr if
118+
* it's not in the cache
119+
*/
120+
template<typename K>
121+
Value * getOrNullptr(const K & key)
122+
{
123+
auto i = data.find(key);
124+
if (i == data.end())
125+
return nullptr;
126+
127+
auto & [it, value] = i->second;
128+
promote(it.it);
129+
return &value;
130+
}
131+
108132
size_t size() const noexcept
109133
{
110134
return data.size();

src/libutil/include/nix/util/pos-table.hh

Lines changed: 17 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44
#include <cstdint>
55
#include <vector>
66

7+
#include "nix/util/lru-cache.hh"
78
#include "nix/util/pos-idx.hh"
89
#include "nix/util/position.hh"
910
#include "nix/util/sync.hh"
@@ -37,10 +38,20 @@ public:
3738
};
3839

3940
private:
41+
/**
42+
* Vector of byte offsets (in the virtual input buffer) of initial line character's position.
43+
* Sorted by construction. Binary search over it allows for efficient translation of arbitrary
44+
* byte offsets in the virtual input buffer to its line + column position.
45+
*/
4046
using Lines = std::vector<uint32_t>;
47+
/**
48+
* Cache from byte offset in the virtual buffer of Origins -> @ref Lines in that origin.
49+
*/
50+
using LinesCache = LRUCache<uint32_t, Lines>;
4151

4252
std::map<uint32_t, Origin> origins;
43-
mutable Sync<std::map<uint32_t, Lines>> lines;
53+
54+
mutable Sync<LinesCache> linesCache;
4455

4556
const Origin * resolve(PosIdx p) const
4657
{
@@ -56,6 +67,11 @@ private:
5667
}
5768

5869
public:
70+
PosTable(std::size_t linesCacheCapacity = 65536)
71+
: linesCache(linesCacheCapacity)
72+
{
73+
}
74+
5975
Origin addOrigin(Pos::Origin origin, size_t size)
6076
{
6177
uint32_t offset = 0;

src/libutil/pos-table.cc

Lines changed: 27 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -15,21 +15,35 @@ Pos PosTable::operator[](PosIdx p) const
1515
const auto offset = origin->offsetOf(p);
1616

1717
Pos result{0, 0, origin->origin};
18-
auto lines = this->lines.lock();
19-
auto linesForInput = (*lines)[origin->offset];
20-
21-
if (linesForInput.empty()) {
22-
auto source = result.getSource().value_or("");
23-
const char * begin = source.data();
24-
for (Pos::LinesIterator it(source), end; it != end; it++)
25-
linesForInput.push_back(it->data() - begin);
26-
if (linesForInput.empty())
27-
linesForInput.push_back(0);
18+
auto linesCache = this->linesCache.lock();
19+
20+
/* Try the origin's line cache */
21+
const auto * linesForInput = linesCache->getOrNullptr(origin->offset);
22+
23+
auto fillCacheForOrigin = [](std::string_view content) {
24+
auto contentLines = Lines();
25+
26+
const char * begin = content.data();
27+
for (Pos::LinesIterator it(content), end; it != end; it++)
28+
contentLines.push_back(it->data() - begin);
29+
if (contentLines.empty())
30+
contentLines.push_back(0);
31+
32+
return contentLines;
33+
};
34+
35+
/* Calculate line offsets and fill the cache */
36+
if (!linesForInput) {
37+
auto originContent = result.getSource().value_or("");
38+
linesCache->upsert(origin->offset, fillCacheForOrigin(originContent));
39+
linesForInput = linesCache->getOrNullptr(origin->offset);
2840
}
29-
// as above: the first line starts at byte 0 and is always present
30-
auto lineStartOffset = std::prev(std::upper_bound(linesForInput.begin(), linesForInput.end(), offset));
3141

32-
result.line = 1 + (lineStartOffset - linesForInput.begin());
42+
assert(linesForInput);
43+
44+
// as above: the first line starts at byte 0 and is always present
45+
auto lineStartOffset = std::prev(std::upper_bound(linesForInput->begin(), linesForInput->end(), offset));
46+
result.line = 1 + (lineStartOffset - linesForInput->begin());
3347
result.column = 1 + (offset - *lineStartOffset);
3448
return result;
3549
}

0 commit comments

Comments
 (0)