mirror of
https://github.com/stablyai/orca.git
synced 2026-09-22 16:02:32 +00:00
perf(orchestration): bound the worker terminal archive in linear time (#18622)
boundArchiveLines built its kept-lines array with kept.unshift() per line, which is O(n) per call. The 256KB char budget admits ~262k lines when they are short, so a blank-line-heavy terminal tail turned the truncation into a quadratic main-process stall: 4.3s for a 300k-line input here, versus 6ms after collecting newest-first and reversing once. Order and truncation boundary are unchanged.
This commit is contained in:
@@ -0,0 +1,41 @@
|
||||
import { describe, expect, it } from 'vitest'
|
||||
import { boundArchiveLines } from './worker-output-archive'
|
||||
|
||||
const TERMINAL_ARCHIVE_MAX_CHARS = 262_144
|
||||
|
||||
function totalCost(lines: string[]): number {
|
||||
return lines.reduce((sum, line) => sum + line.length + 1, 0)
|
||||
}
|
||||
|
||||
describe('boundArchiveLines', () => {
|
||||
it('returns the original array untouched when the tail already fits', () => {
|
||||
const lines = ['one', 'two', 'three']
|
||||
const bounded = boundArchiveLines(lines)
|
||||
expect(bounded.truncated).toBe(false)
|
||||
expect(bounded.lines).toBe(lines)
|
||||
})
|
||||
|
||||
it('keeps the newest lines in order and reports truncation', () => {
|
||||
const lines = Array.from({ length: 40_000 }, (_, index) => `line ${index}`)
|
||||
const bounded = boundArchiveLines(lines)
|
||||
expect(bounded.truncated).toBe(true)
|
||||
expect(totalCost(bounded.lines)).toBeLessThanOrEqual(TERMINAL_ARCHIVE_MAX_CHARS)
|
||||
expect(bounded.lines.at(-1)).toBe(lines.at(-1))
|
||||
expect(bounded.lines).toEqual(lines.slice(lines.length - bounded.lines.length))
|
||||
})
|
||||
|
||||
it('truncates a single oversized line from its tail', () => {
|
||||
const bounded = boundArchiveLines(['x'.repeat(TERMINAL_ARCHIVE_MAX_CHARS * 2)])
|
||||
expect(bounded.truncated).toBe(true)
|
||||
expect(bounded.lines).toHaveLength(1)
|
||||
expect(bounded.lines[0]).toHaveLength(TERMINAL_ARCHIVE_MAX_CHARS - 1)
|
||||
})
|
||||
|
||||
it('bounds a blank-line flood in linear time', () => {
|
||||
// The char budget admits ~262k blank lines; an unshift-per-line build was ~4.3s here.
|
||||
const startedAt = performance.now()
|
||||
const bounded = boundArchiveLines(Array.from({ length: 300_000 }, () => ''))
|
||||
expect(bounded.lines).toHaveLength(TERMINAL_ARCHIVE_MAX_CHARS)
|
||||
expect(performance.now() - startedAt).toBeLessThan(500)
|
||||
})
|
||||
})
|
||||
@@ -114,7 +114,7 @@ export async function captureWorkerOutputArchive(args: {
|
||||
}
|
||||
}
|
||||
|
||||
function boundArchiveLines(lines: string[]): { lines: string[]; truncated: boolean } {
|
||||
export function boundArchiveLines(lines: string[]): { lines: string[]; truncated: boolean } {
|
||||
let total = 0
|
||||
for (const line of lines) {
|
||||
total += line.length + 1
|
||||
@@ -122,18 +122,21 @@ function boundArchiveLines(lines: string[]): { lines: string[]; truncated: boole
|
||||
if (total <= TERMINAL_ARCHIVE_MAX_CHARS) {
|
||||
return { lines, truncated: false }
|
||||
}
|
||||
const kept: string[] = []
|
||||
// Collected newest-first and reversed once: unshift per line is O(n^2) and the
|
||||
// char budget admits ~260k blank lines.
|
||||
const keptReversed: string[] = []
|
||||
let budget = TERMINAL_ARCHIVE_MAX_CHARS
|
||||
for (let index = lines.length - 1; index >= 0; index -= 1) {
|
||||
const cost = lines[index].length + 1
|
||||
if (cost > budget) {
|
||||
if (kept.length === 0 && budget > 1) {
|
||||
kept.unshift(lines[index].slice(-(budget - 1)))
|
||||
if (keptReversed.length === 0 && budget > 1) {
|
||||
keptReversed.push(lines[index].slice(-(budget - 1)))
|
||||
}
|
||||
break
|
||||
}
|
||||
kept.unshift(lines[index])
|
||||
keptReversed.push(lines[index])
|
||||
budget -= cost
|
||||
}
|
||||
return { lines: kept, truncated: true }
|
||||
keptReversed.reverse()
|
||||
return { lines: keptReversed, truncated: true }
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user