mirror of
https://github.com/microsoft/TypeScript.git
synced 2026-02-04 21:53:42 -06:00
Add a simple queue implementation with better performance than Array.shift (#49623)
* Add a simple queue implementation with better performance than `Array.shift` This lets us clean up the hack introduced in #49581 * Correct typo Co-authored-by: Mateusz Burzyński <mateuszburzynski@gmail.com> Co-authored-by: Mateusz Burzyński <mateuszburzynski@gmail.com>
This commit is contained in:
parent
b24b6a1125
commit
020ef41543
@ -3509,10 +3509,11 @@ namespace ts {
|
||||
|
||||
export function isExportsOrModuleExportsOrAlias(sourceFile: SourceFile, node: Expression): boolean {
|
||||
let i = 0;
|
||||
const q = [node];
|
||||
while (q.length && i < 100) {
|
||||
const q = createQueue<Expression>();
|
||||
q.enqueue(node);
|
||||
while (!q.isEmpty() && i < 100) {
|
||||
i++;
|
||||
node = q.shift()!;
|
||||
node = q.dequeue();
|
||||
if (isExportsIdentifier(node) || isModuleExportsAccessExpression(node)) {
|
||||
return true;
|
||||
}
|
||||
@ -3520,10 +3521,10 @@ namespace ts {
|
||||
const symbol = lookupSymbolForName(sourceFile, node.escapedText);
|
||||
if (!!symbol && !!symbol.valueDeclaration && isVariableDeclaration(symbol.valueDeclaration) && !!symbol.valueDeclaration.initializer) {
|
||||
const init = symbol.valueDeclaration.initializer;
|
||||
q.push(init);
|
||||
q.enqueue(init);
|
||||
if (isAssignmentExpression(init, /*excludeCompoundAssignment*/ true)) {
|
||||
q.push(init.left);
|
||||
q.push(init.right);
|
||||
q.enqueue(init.left);
|
||||
q.enqueue(init.right);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -1492,6 +1492,47 @@ namespace ts {
|
||||
return createMultiMap() as UnderscoreEscapedMultiMap<T>;
|
||||
}
|
||||
|
||||
export function createQueue<T>(items?: readonly T[]): Queue<T> {
|
||||
const elements: (T | undefined)[] = items?.slice() || [];
|
||||
let headIndex = 0;
|
||||
|
||||
function isEmpty() {
|
||||
return headIndex === elements.length;
|
||||
}
|
||||
|
||||
function enqueue(...items: T[]) {
|
||||
elements.push(...items);
|
||||
}
|
||||
|
||||
function dequeue(): T {
|
||||
if (isEmpty()) {
|
||||
throw new Error("Queue is empty");
|
||||
}
|
||||
|
||||
const result = elements[headIndex] as T;
|
||||
elements[headIndex] = undefined; // Don't keep referencing dequeued item
|
||||
headIndex++;
|
||||
|
||||
// If more than half of the queue is empty, copy the remaining elements to the
|
||||
// front and shrink the array (unless we'd be saving fewer than 100 slots)
|
||||
if (headIndex > 100 && headIndex > (elements.length >> 1)) {
|
||||
const newLength = elements.length - headIndex;
|
||||
elements.copyWithin(/*target*/ 0, /*start*/ headIndex);
|
||||
|
||||
elements.length = newLength;
|
||||
headIndex = 0;
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
return {
|
||||
enqueue,
|
||||
dequeue,
|
||||
isEmpty,
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a Set with custom equality and hash code functionality. This is useful when you
|
||||
* want to use something looser than object identity - e.g. "has the same span".
|
||||
|
||||
@ -8998,4 +8998,11 @@ namespace ts {
|
||||
negative: boolean;
|
||||
base10Value: string;
|
||||
}
|
||||
|
||||
/* @internal */
|
||||
export interface Queue<T> {
|
||||
enqueue(...items: T[]): void;
|
||||
dequeue(): T;
|
||||
isEmpty(): boolean;
|
||||
}
|
||||
}
|
||||
|
||||
@ -36,7 +36,7 @@ namespace ts.server {
|
||||
export class SessionClient implements LanguageService {
|
||||
private sequence = 0;
|
||||
private lineMaps = new Map<string, number[]>();
|
||||
private messages: string[] = [];
|
||||
private messages = createQueue<string>();
|
||||
private lastRenameEntry: RenameEntry | undefined;
|
||||
private preferences: UserPreferences | undefined;
|
||||
|
||||
@ -44,7 +44,7 @@ namespace ts.server {
|
||||
}
|
||||
|
||||
public onMessage(message: string): void {
|
||||
this.messages.push(message);
|
||||
this.messages.enqueue(message);
|
||||
}
|
||||
|
||||
private writeMessage(message: string): void {
|
||||
@ -95,7 +95,7 @@ namespace ts.server {
|
||||
let foundResponseMessage = false;
|
||||
let response!: T;
|
||||
while (!foundResponseMessage) {
|
||||
const lastMessage = this.messages.shift()!;
|
||||
const lastMessage = this.messages.dequeue()!;
|
||||
Debug.assert(!!lastMessage, "Did not receive any responses.");
|
||||
const responseBody = extractMessage(lastMessage);
|
||||
try {
|
||||
|
||||
@ -504,18 +504,18 @@ namespace ts.server {
|
||||
// If `getResultsForPosition` returns results for a project, they go in here
|
||||
const resultsMap = new Map<Project, readonly TResult[]>();
|
||||
|
||||
const queue: ProjectAndLocation[] = [];
|
||||
const queue = createQueue<ProjectAndLocation>();
|
||||
|
||||
// In order to get accurate isDefinition values for `defaultProject`,
|
||||
// we need to ensure that it is searched from `initialLocation`.
|
||||
// The easiest way to do this is to search it first.
|
||||
queue.push({ project: defaultProject, location: initialLocation });
|
||||
queue.enqueue({ project: defaultProject, location: initialLocation });
|
||||
|
||||
// This will queue `defaultProject` a second time, but it will be dropped
|
||||
// as a dup when it is dequeued.
|
||||
forEachProjectInProjects(projects, initialLocation.fileName, (project, path) => {
|
||||
const location = { fileName: path!, pos: initialLocation.pos };
|
||||
queue.push({ project, location });
|
||||
queue.enqueue({ project, location });
|
||||
});
|
||||
|
||||
const projectService = defaultProject.projectService;
|
||||
@ -536,25 +536,13 @@ namespace ts.server {
|
||||
const searchedProjectKeys = new Set<string>();
|
||||
|
||||
onCancellation:
|
||||
while (queue.length) {
|
||||
while (queue.length) {
|
||||
while (!queue.isEmpty()) {
|
||||
while (!queue.isEmpty()) {
|
||||
if (cancellationToken.isCancellationRequested()) break onCancellation;
|
||||
|
||||
let skipCount = 0;
|
||||
for (; skipCount < queue.length && resultsMap.has(queue[skipCount].project); skipCount++);
|
||||
|
||||
if (skipCount === queue.length) {
|
||||
queue.length = 0;
|
||||
break;
|
||||
}
|
||||
|
||||
if (skipCount > 0) {
|
||||
queue.splice(0, skipCount);
|
||||
}
|
||||
|
||||
// NB: we may still skip if it's a project reference redirect
|
||||
const { project, location } = queue.shift()!;
|
||||
const { project, location } = queue.dequeue();
|
||||
|
||||
if (resultsMap.has(project)) continue;
|
||||
if (isLocationProjectReferenceRedirect(project, location)) continue;
|
||||
|
||||
const projectResults = searchPosition(project, location);
|
||||
@ -574,7 +562,7 @@ namespace ts.server {
|
||||
if (resultsMap.has(project)) return; // Can loop forever without this (enqueue here, dequeue above, repeat)
|
||||
const location = mapDefinitionInProject(defaultDefinition, project, getGeneratedDefinition, getSourceDefinition);
|
||||
if (location) {
|
||||
queue.push({ project, location });
|
||||
queue.enqueue({ project, location });
|
||||
}
|
||||
});
|
||||
}
|
||||
@ -604,7 +592,7 @@ namespace ts.server {
|
||||
|
||||
for (const project of originalScriptInfo.containingProjects) {
|
||||
if (!project.isOrphan() && !resultsMap.has(project)) { // Optimization: don't enqueue if will be discarded
|
||||
queue.push({ project, location: originalLocation });
|
||||
queue.enqueue({ project, location: originalLocation });
|
||||
}
|
||||
}
|
||||
|
||||
@ -613,7 +601,7 @@ namespace ts.server {
|
||||
symlinkedProjectsMap.forEach((symlinkedProjects, symlinkedPath) => {
|
||||
for (const symlinkedProject of symlinkedProjects) {
|
||||
if (!symlinkedProject.isOrphan() && !resultsMap.has(symlinkedProject)) { // Optimization: don't enqueue if will be discarded
|
||||
queue.push({ project: symlinkedProject, location: { fileName: symlinkedPath as string, pos: originalLocation.pos } });
|
||||
queue.enqueue({ project: symlinkedProject, location: { fileName: symlinkedPath as string, pos: originalLocation.pos } });
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
@ -240,18 +240,18 @@ namespace ts.FindAllReferences {
|
||||
) {
|
||||
referenceEntries = entries && [...entries];
|
||||
}
|
||||
else {
|
||||
const queue = entries && [...entries];
|
||||
else if (entries) {
|
||||
const queue = createQueue(entries);
|
||||
const seenNodes = new Map<number, true>();
|
||||
while (queue && queue.length) {
|
||||
const entry = queue.shift() as NodeEntry;
|
||||
while (!queue.isEmpty()) {
|
||||
const entry = queue.dequeue() as NodeEntry;
|
||||
if (!addToSeen(seenNodes, getNodeId(entry.node))) {
|
||||
continue;
|
||||
}
|
||||
referenceEntries = append(referenceEntries, entry);
|
||||
const entries = getImplementationReferenceEntries(program, cancellationToken, sourceFiles, entry.node, entry.node.pos);
|
||||
if (entries) {
|
||||
queue.push(...entries);
|
||||
queue.enqueue(...entries);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -194,7 +194,7 @@ namespace ts.server {
|
||||
}
|
||||
};
|
||||
|
||||
const pending: Buffer[] = [];
|
||||
const pending = createQueue<Buffer>();
|
||||
let canWrite = true;
|
||||
|
||||
if (useWatchGuard) {
|
||||
@ -334,7 +334,7 @@ namespace ts.server {
|
||||
|
||||
function writeMessage(buf: Buffer) {
|
||||
if (!canWrite) {
|
||||
pending.push(buf);
|
||||
pending.enqueue(buf);
|
||||
}
|
||||
else {
|
||||
canWrite = false;
|
||||
@ -344,8 +344,8 @@ namespace ts.server {
|
||||
|
||||
function setCanWriteFlagAndWriteMessageIfNecessary() {
|
||||
canWrite = true;
|
||||
if (pending.length) {
|
||||
writeMessage(pending.shift()!);
|
||||
if (!pending.isEmpty()) {
|
||||
writeMessage(pending.dequeue());
|
||||
}
|
||||
}
|
||||
|
||||
@ -430,7 +430,7 @@ namespace ts.server {
|
||||
private installer!: NodeChildProcess;
|
||||
private projectService!: ProjectService;
|
||||
private activeRequestCount = 0;
|
||||
private requestQueue: QueuedOperation[] = [];
|
||||
private requestQueue = createQueue<QueuedOperation>();
|
||||
private requestMap = new Map<string, QueuedOperation>(); // Maps operation ID to newest requestQueue entry with that ID
|
||||
/** We will lazily request the types registry on the first call to `isKnownTypesPackageName` and store it in `typesRegistryCache`. */
|
||||
private requestedRegistry = false;
|
||||
@ -567,7 +567,7 @@ namespace ts.server {
|
||||
if (this.logger.hasLevel(LogLevel.verbose)) {
|
||||
this.logger.info(`Deferring request for: ${operationId}`);
|
||||
}
|
||||
this.requestQueue.push(queuedRequest);
|
||||
this.requestQueue.enqueue(queuedRequest);
|
||||
this.requestMap.set(operationId, queuedRequest);
|
||||
}
|
||||
}
|
||||
@ -649,8 +649,8 @@ namespace ts.server {
|
||||
Debug.fail("Received too many responses");
|
||||
}
|
||||
|
||||
while (this.requestQueue.length > 0) {
|
||||
const queuedRequest = this.requestQueue.shift()!;
|
||||
while (!this.requestQueue.isEmpty()) {
|
||||
const queuedRequest = this.requestQueue.dequeue();
|
||||
if (this.requestMap.get(queuedRequest.operationId) === queuedRequest) {
|
||||
this.requestMap.delete(queuedRequest.operationId);
|
||||
this.scheduleRequest(queuedRequest);
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user