Skip to content

Commit 2d23171

Browse files
committed
implement a basic symbol graph optimization described in the comments of SymbolQueries.swift
1 parent 02067b9 commit 2d23171

File tree

4 files changed

+79
-35
lines changed

4 files changed

+79
-35
lines changed
Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
import SymbolGraphs
2+
3+
extension SSGC.Outliner.Cache
4+
{
5+
struct Outputs
6+
{
7+
private(set)
8+
var outlines:[SymbolGraph.Outline]
9+
private
10+
var indices:[SymbolGraph.Outline: Int]
11+
12+
init()
13+
{
14+
self.outlines = []
15+
self.indices = [:]
16+
}
17+
}
18+
}
19+
extension SSGC.Outliner.Cache.Outputs
20+
{
21+
mutating
22+
func add(outline:SymbolGraph.Outline) -> Int
23+
{
24+
{
25+
if let index:Int = $0
26+
{
27+
return index
28+
}
29+
else
30+
{
31+
let next:Int = self.outlines.endIndex
32+
self.outlines.append(outline)
33+
$0 = next
34+
return next
35+
}
36+
} (&self.indices[outline])
37+
}
38+
}
Lines changed: 16 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,3 @@
1-
import MarkdownAST
21
import SymbolGraphs
32

43
extension SSGC.Outliner
@@ -10,33 +9,32 @@ extension SSGC.Outliner
109
struct Cache
1110
{
1211
private
13-
var references:[String: Int]
12+
var outlined:Outputs
1413
private
15-
var outlines:[SymbolGraph.Outline]
14+
var entries:[String: Int]
1615

1716
init()
1817
{
19-
self.references = [:]
20-
self.outlines = []
18+
self.outlined = .init()
19+
self.entries = [:]
2120
}
2221
}
2322
}
2423
extension SSGC.Outliner.Cache
2524
{
26-
var fold:Int { self.outlines.endIndex }
25+
var fold:Int { self.outlined.outlines.endIndex }
2726

2827
mutating
29-
func append(outline:SymbolGraph.Outline) -> Int
28+
func add(outline:SymbolGraph.Outline) -> Int
3029
{
31-
defer { self.outlines.append(outline) }
32-
return self.outlines.endIndex
30+
self.outlined.add(outline: outline)
3331
}
3432

3533
mutating
3634
func clear() -> [SymbolGraph.Outline]
3735
{
3836
defer { self = .init() }
39-
return self.outlines
37+
return self.outlined.outlines
4038
}
4139

4240
mutating
@@ -49,17 +47,19 @@ extension SSGC.Outliner.Cache
4947
{
5048
return reference
5149
}
52-
else if let outline:SymbolGraph.Outline = try populate()
50+
else if
51+
let outline:SymbolGraph.Outline = try populate()
5352
{
54-
let next:Int = self.outlines.endIndex
55-
self.outlines.append(outline)
56-
$0 = next
57-
return next
53+
// Sometimes we get the same outline from different keys. As an optimization,
54+
// we can reuse an existing outline.
55+
let outline:Int = self.outlined.add(outline: outline)
56+
$0 = outline
57+
return outline
5858
}
5959
else
6060
{
6161
return nil
6262
}
63-
} (&self.references[key])
63+
} (&self.entries[key])
6464
}
6565
}

Sources/SymbolGraphLinker/Resolution/SSGC.Outliner.swift

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -164,7 +164,7 @@ extension SSGC.Outliner
164164

165165
if let resource:SSGC.Resource = self.locate(resource: name.string)
166166
{
167-
return self.cache.append(outline: .vertex(resource.id, text: name.string))
167+
return self.cache.add(outline: .vertex(resource.id, text: name.string))
168168
}
169169

170170
self.resolver.diagnostics[name.source] = SSGC.ResourceError.fileNotFound(name.string)
@@ -174,7 +174,7 @@ extension SSGC.Outliner
174174
private mutating
175175
func outline(translating link:Markdown.SourceString, to url:Substring) -> Int?
176176
{
177-
self.cache.append(outline: .unresolved(web: String.init(url),
177+
self.cache.add(outline: .unresolved(web: String.init(url),
178178
location: link.source.start))
179179
}
180180

Sources/UnidocQueryTests/Tests/SymbolQueries.swift

Lines changed: 23 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -237,23 +237,23 @@ struct SymbolQueries:UnidocDatabaseTestBattery
237237
tests / "Barbie" / "Dreamhouse" as TestGroup?,
238238
["barbiecore", "barbie", "dreamhouse"][...],
239239
[
240-
"Int": [1],
241-
"Int max": [2],
242-
"ID": [1],
243-
"Barbie": [1],
244-
"Barbie ID": [2],
245-
"BarbieCore Barbie ID": [3],
246-
] as [String: [Int]]
240+
"Int": 1,
241+
"Int max": 2,
242+
"ID": 1,
243+
"Barbie": 1,
244+
"Barbie ID": 2,
245+
"BarbieCore Barbie ID": 3,
246+
] as [String: Int]
247247
),
248248
(
249249
tests / "Barbie" / "Dreamhouse" / "Keys" as TestGroup?,
250250
["barbiecore", "barbie", "dreamhouse", "keys"][...],
251251
[
252-
"Int": [1],
253-
"max": [2], // expands to two components because it is a vector symbol
254-
"ID": [1, 1, 1],
255-
"Barbie": [1],
256-
] as [String: [Int]]
252+
"Int": 1,
253+
"max": 2, // expands to two components because it is a vector symbol
254+
"ID": 1,
255+
"Barbie": 1,
256+
] as [String: Int]
257257
),
258258
]
259259
{
@@ -269,8 +269,9 @@ struct SymbolQueries:UnidocDatabaseTestBattery
269269
let overview:Unidoc.Passage = tests.expect(
270270
value: vertex.overview)
271271
{
272-
// This checks that we cached the two instances of `Barbie.ID`
273-
tests.expect(overview.outlines.count ==? 6)
272+
// This checks that we cached the two instances of `Barbie.ID`, and
273+
// additionally that we optimized away a third reference to it.
274+
tests.expect(overview.outlines.count ==? expected.count)
274275
tests.expect(tree.rows ..?
275276
[
276277
.init(
@@ -311,7 +312,7 @@ struct SymbolQueries:UnidocDatabaseTestBattery
311312
])
312313

313314
let secondaries:Set<Unidoc.Scalar> = .init(output.vertices.lazy.map(\.id))
314-
let lengths:[String: [Int]] = overview.outlines.reduce(into: [:])
315+
let lengths:[String: Int] = overview.outlines.reduce(into: [:])
315316
{
316317
guard
317318
case .path(let words, let path) = $1
@@ -325,7 +326,10 @@ struct SymbolQueries:UnidocDatabaseTestBattery
325326
tests.expect(true: secondaries.contains(id))
326327
}
327328

328-
$0[words, default: []].append(path.count)
329+
{
330+
tests.expect(nil: $0)
331+
$0 = path.count
332+
} (&$0[words])
329333
}
330334
// The ``Int.max`` test case is especially valuable because not only is it
331335
// a multi-component cross-package reference, but the `max` member is also
@@ -364,7 +368,9 @@ struct SymbolQueries:UnidocDatabaseTestBattery
364368
let prose:Unidoc.Passage = tests.expect(
365369
value: vertex.overview)
366370
{
367-
// TODO: We should be optimizing away duplicate outlines.
371+
// Note: the duplicate outline was not optimized away because external
372+
// links are resolved dynamically, and this means their representation
373+
// within symbol graphs encodes their source locations, for diagnostics.
368374
tests.expect(prose.outlines.count ==? 4)
369375
tests.expect(prose.outlines[..<3] ..? [
370376
.link(https: "en.wikipedia.org/wiki/Main_Page", safe: true),

0 commit comments

Comments
 (0)