Skip to content

Commit 0c28bab

Browse files
committed
Add a simple queue implementation with better performance than Array.shift
This lets us clean up the hack introduced in microsoft#49581
1 parent 71b5bdf commit 0c28bab

File tree

7 files changed

+80
-44
lines changed

7 files changed

+80
-44
lines changed

src/compiler/binder.ts

+7-6
Original file line numberDiff line numberDiff line change
@@ -3509,21 +3509,22 @@ namespace ts {
35093509

35103510
export function isExportsOrModuleExportsOrAlias(sourceFile: SourceFile, node: Expression): boolean {
35113511
let i = 0;
3512-
const q = [node];
3513-
while (q.length && i < 100) {
3512+
const q = createQueue<Expression>();
3513+
q.enqueue(node);
3514+
while (!q.isEmpty() && i < 100) {
35143515
i++;
3515-
node = q.shift()!;
3516+
node = q.dequeue();
35163517
if (isExportsIdentifier(node) || isModuleExportsAccessExpression(node)) {
35173518
return true;
35183519
}
35193520
else if (isIdentifier(node)) {
35203521
const symbol = lookupSymbolForName(sourceFile, node.escapedText);
35213522
if (!!symbol && !!symbol.valueDeclaration && isVariableDeclaration(symbol.valueDeclaration) && !!symbol.valueDeclaration.initializer) {
35223523
const init = symbol.valueDeclaration.initializer;
3523-
q.push(init);
3524+
q.enqueue(init);
35243525
if (isAssignmentExpression(init, /*excludeCompoundAssignment*/ true)) {
3525-
q.push(init.left);
3526-
q.push(init.right);
3526+
q.enqueue(init.left);
3527+
q.enqueue(init.right);
35273528
}
35283529
}
35293530
}

src/compiler/core.ts

+40
Original file line numberDiff line numberDiff line change
@@ -1492,6 +1492,46 @@ namespace ts {
14921492
return createMultiMap() as UnderscoreEscapedMultiMap<T>;
14931493
}
14941494

1495+
export function createQueue<T>(items?: readonly T[]): Queue<T> {
1496+
const elements: (T | undefined)[] = items?.slice() || [];
1497+
let headIndex = 0;
1498+
1499+
function isEmpty() {
1500+
return headIndex === elements.length;
1501+
}
1502+
1503+
function enqueue(...items: T[]) {
1504+
elements.push(...items);
1505+
}
1506+
1507+
function dequeue(): T {
1508+
if (isEmpty()) {
1509+
throw new Error("Queue is empty");
1510+
}
1511+
1512+
const result = elements[headIndex] as T;
1513+
elements[headIndex] = undefined; // Don't keep referencing dequeued item
1514+
headIndex++;
1515+
1516+
// If more than half of the queue is empty, copy the remaining elements to the
1517+
// front and shrink the array (unless we'd be saving fewer than 100 slots)
1518+
if (headIndex > 100 && headIndex > (elements.length >> 1)) {
1519+
const newLength = elements.length - headIndex;
1520+
elements.copyWithin(/*target*/ 0, /*&tart*/ headIndex);
1521+
elements.length = newLength;
1522+
headIndex = 0;
1523+
}
1524+
1525+
return result;
1526+
}
1527+
1528+
return {
1529+
enqueue,
1530+
dequeue,
1531+
isEmpty,
1532+
};
1533+
}
1534+
14951535
/**
14961536
* Creates a Set with custom equality and hash code functionality. This is useful when you
14971537
* want to use something looser than object identity - e.g. "has the same span".

src/compiler/types.ts

+7
Original file line numberDiff line numberDiff line change
@@ -8998,4 +8998,11 @@ namespace ts {
89988998
negative: boolean;
89998999
base10Value: string;
90009000
}
9001+
9002+
/* @internal */
9003+
export interface Queue<T> {
9004+
enqueue(...items: T[]): void;
9005+
dequeue(): T;
9006+
isEmpty(): boolean;
9007+
}
90019008
}

src/harness/client.ts

+3-3
Original file line numberDiff line numberDiff line change
@@ -36,15 +36,15 @@ namespace ts.server {
3636
export class SessionClient implements LanguageService {
3737
private sequence = 0;
3838
private lineMaps = new Map<string, number[]>();
39-
private messages: string[] = [];
39+
private messages = createQueue<string>();
4040
private lastRenameEntry: RenameEntry | undefined;
4141
private preferences: UserPreferences | undefined;
4242

4343
constructor(private host: SessionClientHost) {
4444
}
4545

4646
public onMessage(message: string): void {
47-
this.messages.push(message);
47+
this.messages.enqueue(message);
4848
}
4949

5050
private writeMessage(message: string): void {
@@ -95,7 +95,7 @@ namespace ts.server {
9595
let foundResponseMessage = false;
9696
let response!: T;
9797
while (!foundResponseMessage) {
98-
const lastMessage = this.messages.shift()!;
98+
const lastMessage = this.messages.dequeue()!;
9999
Debug.assert(!!lastMessage, "Did not receive any responses.");
100100
const responseBody = extractMessage(lastMessage);
101101
try {

src/server/session.ts

+10-22
Original file line numberDiff line numberDiff line change
@@ -504,18 +504,18 @@ namespace ts.server {
504504
// If `getResultsForPosition` returns results for a project, they go in here
505505
const resultsMap = new Map<Project, readonly TResult[]>();
506506

507-
const queue: ProjectAndLocation[] = [];
507+
const queue = createQueue<ProjectAndLocation>();
508508

509509
// In order to get accurate isDefinition values for `defaultProject`,
510510
// we need to ensure that it is searched from `initialLocation`.
511511
// The easiest way to do this is to search it first.
512-
queue.push({ project: defaultProject, location: initialLocation });
512+
queue.enqueue({ project: defaultProject, location: initialLocation });
513513

514514
// This will queue `defaultProject` a second time, but it will be dropped
515515
// as a dup when it is dequeued.
516516
forEachProjectInProjects(projects, initialLocation.fileName, (project, path) => {
517517
const location = { fileName: path!, pos: initialLocation.pos };
518-
queue.push({ project, location });
518+
queue.enqueue({ project, location });
519519
});
520520

521521
const projectService = defaultProject.projectService;
@@ -536,25 +536,13 @@ namespace ts.server {
536536
const searchedProjectKeys = new Set<string>();
537537

538538
onCancellation:
539-
while (queue.length) {
540-
while (queue.length) {
539+
while (!queue.isEmpty()) {
540+
while (!queue.isEmpty()) {
541541
if (cancellationToken.isCancellationRequested()) break onCancellation;
542542

543-
let skipCount = 0;
544-
for (; skipCount < queue.length && resultsMap.has(queue[skipCount].project); skipCount++);
545-
546-
if (skipCount === queue.length) {
547-
queue.length = 0;
548-
break;
549-
}
550-
551-
if (skipCount > 0) {
552-
queue.splice(0, skipCount);
553-
}
554-
555-
// NB: we may still skip if it's a project reference redirect
556-
const { project, location } = queue.shift()!;
543+
const { project, location } = queue.dequeue();
557544

545+
if (resultsMap.has(project)) continue;
558546
if (isLocationProjectReferenceRedirect(project, location)) continue;
559547

560548
const projectResults = searchPosition(project, location);
@@ -574,7 +562,7 @@ namespace ts.server {
574562
if (resultsMap.has(project)) return; // Can loop forever without this (enqueue here, dequeue above, repeat)
575563
const location = mapDefinitionInProject(defaultDefinition, project, getGeneratedDefinition, getSourceDefinition);
576564
if (location) {
577-
queue.push({ project, location });
565+
queue.enqueue({ project, location });
578566
}
579567
});
580568
}
@@ -604,7 +592,7 @@ namespace ts.server {
604592

605593
for (const project of originalScriptInfo.containingProjects) {
606594
if (!project.isOrphan() && !resultsMap.has(project)) { // Optimization: don't enqueue if will be discarded
607-
queue.push({ project, location: originalLocation });
595+
queue.enqueue({ project, location: originalLocation });
608596
}
609597
}
610598

@@ -613,7 +601,7 @@ namespace ts.server {
613601
symlinkedProjectsMap.forEach((symlinkedProjects, symlinkedPath) => {
614602
for (const symlinkedProject of symlinkedProjects) {
615603
if (!symlinkedProject.isOrphan() && !resultsMap.has(symlinkedProject)) { // Optimization: don't enqueue if will be discarded
616-
queue.push({ project: symlinkedProject, location: { fileName: symlinkedPath as string, pos: originalLocation.pos } });
604+
queue.enqueue({ project: symlinkedProject, location: { fileName: symlinkedPath as string, pos: originalLocation.pos } });
617605
}
618606
}
619607
});

src/services/findAllReferences.ts

+5-5
Original file line numberDiff line numberDiff line change
@@ -240,18 +240,18 @@ namespace ts.FindAllReferences {
240240
) {
241241
referenceEntries = entries && [...entries];
242242
}
243-
else {
244-
const queue = entries && [...entries];
243+
else if (entries) {
244+
const queue = createQueue(entries);
245245
const seenNodes = new Map<number, true>();
246-
while (queue && queue.length) {
247-
const entry = queue.shift() as NodeEntry;
246+
while (!queue.isEmpty()) {
247+
const entry = queue.dequeue() as NodeEntry;
248248
if (!addToSeen(seenNodes, getNodeId(entry.node))) {
249249
continue;
250250
}
251251
referenceEntries = append(referenceEntries, entry);
252252
const entries = getImplementationReferenceEntries(program, cancellationToken, sourceFiles, entry.node, entry.node.pos);
253253
if (entries) {
254-
queue.push(...entries);
254+
queue.enqueue(...entries);
255255
}
256256
}
257257
}

src/tsserver/nodeServer.ts

+8-8
Original file line numberDiff line numberDiff line change
@@ -194,7 +194,7 @@ namespace ts.server {
194194
}
195195
};
196196

197-
const pending: Buffer[] = [];
197+
const pending = createQueue<Buffer>();
198198
let canWrite = true;
199199

200200
if (useWatchGuard) {
@@ -334,7 +334,7 @@ namespace ts.server {
334334

335335
function writeMessage(buf: Buffer) {
336336
if (!canWrite) {
337-
pending.push(buf);
337+
pending.enqueue(buf);
338338
}
339339
else {
340340
canWrite = false;
@@ -344,8 +344,8 @@ namespace ts.server {
344344

345345
function setCanWriteFlagAndWriteMessageIfNecessary() {
346346
canWrite = true;
347-
if (pending.length) {
348-
writeMessage(pending.shift()!);
347+
if (!pending.isEmpty()) {
348+
writeMessage(pending.dequeue());
349349
}
350350
}
351351

@@ -430,7 +430,7 @@ namespace ts.server {
430430
private installer!: NodeChildProcess;
431431
private projectService!: ProjectService;
432432
private activeRequestCount = 0;
433-
private requestQueue: QueuedOperation[] = [];
433+
private requestQueue = createQueue<QueuedOperation>();
434434
private requestMap = new Map<string, QueuedOperation>(); // Maps operation ID to newest requestQueue entry with that ID
435435
/** We will lazily request the types registry on the first call to `isKnownTypesPackageName` and store it in `typesRegistryCache`. */
436436
private requestedRegistry = false;
@@ -567,7 +567,7 @@ namespace ts.server {
567567
if (this.logger.hasLevel(LogLevel.verbose)) {
568568
this.logger.info(`Deferring request for: ${operationId}`);
569569
}
570-
this.requestQueue.push(queuedRequest);
570+
this.requestQueue.enqueue(queuedRequest);
571571
this.requestMap.set(operationId, queuedRequest);
572572
}
573573
}
@@ -649,8 +649,8 @@ namespace ts.server {
649649
Debug.fail("Received too many responses");
650650
}
651651

652-
while (this.requestQueue.length > 0) {
653-
const queuedRequest = this.requestQueue.shift()!;
652+
while (!this.requestQueue.isEmpty()) {
653+
const queuedRequest = this.requestQueue.dequeue();
654654
if (this.requestMap.get(queuedRequest.operationId) === queuedRequest) {
655655
this.requestMap.delete(queuedRequest.operationId);
656656
this.scheduleRequest(queuedRequest);

0 commit comments

Comments
 (0)