Rollup merge of #119756 - notriddle:notriddle/reuse-map, r=GuillaumeGomez

rustdoc-search: reuse individual types in function signatures

Because `search.js` never mutates the function signature after loading it,
they can be safely and easily reused across functions.

This change doesn't change the format of the search index.
It only changes `search.js`.

Profiler output: https://notriddle.com/rustdoc-html-demo-9/fn-signature-opti2/index.html

<table><tr><th>benchmark<th>before<th>after

<tr><th>arti<td>

```
user: 002.228 s
sys:  000.315 s
wall: 001.663 s
child_RSS_high:     315668 KiB
group_mem_high:     285948 KiB
```

<td>

```
user: 001.805 s
sys:  000.231 s
wall: 001.398 s
child_RSS_high:     235864 KiB
group_mem_high:     203056 KiB
```

<tr><th>cortex-m<td>

```
user: 000.143 s
sys:  000.035 s
wall: 000.140 s
child_RSS_high:      59168 KiB
group_mem_high:      23000 KiB
```

<td>

```
user: 000.138 s
sys:  000.031 s
wall: 000.133 s
child_RSS_high:      58944 KiB
group_mem_high:      22220 KiB
```

<tr><th>sqlx<td>

```
user: 000.792 s
sys:  000.115 s
wall: 000.536 s
child_RSS_high:     156716 KiB
group_mem_high:     122948 KiB
```

<td>

```
user: 000.824 s
sys:  000.084 s
wall: 000.535 s
child_RSS_high:     136668 KiB
group_mem_high:     101792 KiB
```

<tr><th>stm32f4<td>

```
user: 006.665 s
sys:  003.533 s
wall: 008.624 s
child_RSS_high:    1037660 KiB
group_mem_high:    1022516 KiB
```

<td>

```
user: 005.997 s
sys:  003.185 s
wall: 007.987 s
child_RSS_high:     832068 KiB
group_mem_high:     810908 KiB
```

<tr><th>stm32f4xx-hal<td>

```
user: 000.317 s
sys:  000.051 s
wall: 000.203 s
child_RSS_high:      77060 KiB
group_mem_high:      41776 KiB
```

<td>

```
user: 000.287 s
sys:  000.046 s
wall: 000.180 s
child_RSS_high:      75216 KiB
group_mem_high:      39200 KiB
```

<tr><th>ripgrep<td>

```
user: 000.463 s
sys:  000.063 s
wall: 000.295 s
child_RSS_high:     101288 KiB
group_mem_high:      66364 KiB
```

<td>

```
user: 000.472 s
sys:  000.036 s
wall: 000.247 s
child_RSS_high:      82708 KiB
group_mem_high:      47056 KiB
```

</tr></table>
This commit is contained in:
Guillaume Gomez 2024-01-09 13:23:19 +01:00 committed by GitHub
commit f9cadb915f
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23

View File

@ -2717,9 +2717,33 @@ ${item.displayPath}<span class="${type}">${name}</span>\
* @return {Array<FunctionSearchType>} * @return {Array<FunctionSearchType>}
*/ */
function buildItemSearchTypeAll(types, lowercasePaths) { function buildItemSearchTypeAll(types, lowercasePaths) {
return types.map(type => buildItemSearchType(type, lowercasePaths)); return types.length > 0 ?
types.map(type => buildItemSearchType(type, lowercasePaths)) :
EMPTY_GENERICS_ARRAY;
} }
/**
* Empty, immutable map used in item search types with no bindings.
*
* @type {Map<number, Array<FunctionType>>}
*/
const EMPTY_BINDINGS_MAP = new Map();
/**
* Empty, immutable map used in item search types with no bindings.
*
* @type {Array<FunctionType>}
*/
const EMPTY_GENERICS_ARRAY = [];
/**
* Object pool for function types with no bindings or generics.
* This is reset after loading the index.
*
* @type {Map<number|null, FunctionType>}
*/
let TYPES_POOL = new Map();
/** /**
* Converts a single type. * Converts a single type.
* *
@ -2732,15 +2756,15 @@ ${item.displayPath}<span class="${type}">${name}</span>\
let pathIndex, generics, bindings; let pathIndex, generics, bindings;
if (typeof type === "number") { if (typeof type === "number") {
pathIndex = type; pathIndex = type;
generics = []; generics = EMPTY_GENERICS_ARRAY;
bindings = new Map(); bindings = EMPTY_BINDINGS_MAP;
} else { } else {
pathIndex = type[PATH_INDEX_DATA]; pathIndex = type[PATH_INDEX_DATA];
generics = buildItemSearchTypeAll( generics = buildItemSearchTypeAll(
type[GENERICS_DATA], type[GENERICS_DATA],
lowercasePaths lowercasePaths
); );
if (type.length > BINDINGS_DATA) { if (type.length > BINDINGS_DATA && type[BINDINGS_DATA].length > 0) {
bindings = new Map(type[BINDINGS_DATA].map(binding => { bindings = new Map(type[BINDINGS_DATA].map(binding => {
const [assocType, constraints] = binding; const [assocType, constraints] = binding;
// Associated type constructors are represented sloppily in rustdoc's // Associated type constructors are represented sloppily in rustdoc's
@ -2759,38 +2783,83 @@ ${item.displayPath}<span class="${type}">${name}</span>\
]; ];
})); }));
} else { } else {
bindings = new Map(); bindings = EMPTY_BINDINGS_MAP;
} }
} }
/**
* @type {FunctionType}
*/
let result;
if (pathIndex < 0) { if (pathIndex < 0) {
// types less than 0 are generic parameters // types less than 0 are generic parameters
// the actual names of generic parameters aren't stored, since they aren't API // the actual names of generic parameters aren't stored, since they aren't API
return { result = {
id: pathIndex, id: pathIndex,
ty: TY_GENERIC, ty: TY_GENERIC,
path: null, path: null,
generics, generics,
bindings, bindings,
}; };
} } else if (pathIndex === 0) {
if (pathIndex === 0) {
// `0` is used as a sentinel because it's fewer bytes than `null` // `0` is used as a sentinel because it's fewer bytes than `null`
return { result = {
id: null, id: null,
ty: null, ty: null,
path: null, path: null,
generics, generics,
bindings, bindings,
}; };
} else {
const item = lowercasePaths[pathIndex - 1];
result = {
id: buildTypeMapIndex(item.name, isAssocType),
ty: item.ty,
path: item.path,
generics,
bindings,
};
} }
const item = lowercasePaths[pathIndex - 1]; const cr = TYPES_POOL.get(result.id);
return { if (cr) {
id: buildTypeMapIndex(item.name, isAssocType), // Shallow equality check. Since this function is used
ty: item.ty, // to construct every type object, this should be mostly
path: item.path, // equivalent to a deep equality check, except if there's
generics, // a conflict, we don't keep the old one around, so it's
bindings, // not a fully precise implementation of hashcons.
}; if (cr.generics.length === result.generics.length &&
cr.generics !== result.generics &&
cr.generics.every((x, i) => result.generics[i] === x)
) {
result.generics = cr.generics;
}
if (cr.bindings.size === result.bindings.size && cr.bindings !== result.bindings) {
let ok = true;
for (const [k, v] of cr.bindings.entries()) {
const v2 = result.bindings.get(v);
if (!v2) {
ok = false;
break;
}
if (v !== v2 && v.length === v2.length && v.every((x, i) => v2[i] === x)) {
result.bindings.set(k, v);
} else if (v !== v2) {
ok = false;
break;
}
}
if (ok) {
result.bindings = cr.bindings;
}
}
if (cr.ty === result.ty && cr.path === result.path
&& cr.bindings === result.bindings && cr.generics === result.generics
&& cr.ty === result.ty
) {
return cr;
}
}
TYPES_POOL.set(result.id, result);
return result;
} }
/** /**
@ -2801,7 +2870,7 @@ ${item.displayPath}<span class="${type}">${name}</span>\
* object-based encoding so that the actual search code is more readable and easier to debug. * object-based encoding so that the actual search code is more readable and easier to debug.
* *
* The raw function search type format is generated using serde in * The raw function search type format is generated using serde in
* librustdoc/html/render/mod.rs: impl Serialize for IndexItemFunctionType * librustdoc/html/render/mod.rs: IndexItemFunctionType::write_to_string
* *
* @param {{ * @param {{
* string: string, * string: string,
@ -2970,8 +3039,8 @@ ${item.displayPath}<span class="${type}">${name}</span>\
const fb = { const fb = {
id: null, id: null,
ty: 0, ty: 0,
generics: [], generics: EMPTY_GENERICS_ARRAY,
bindings: new Map(), bindings: EMPTY_BINDINGS_MAP,
}; };
for (const [k, v] of type.bindings.entries()) { for (const [k, v] of type.bindings.entries()) {
fb.id = k; fb.id = k;
@ -3199,6 +3268,8 @@ ${item.displayPath}<span class="${type}">${name}</span>\
} }
currentIndex += itemTypes.length; currentIndex += itemTypes.length;
} }
// Drop the (rather large) hash table used for reusing function items
TYPES_POOL = new Map();
} }
/** /**