-
Notifications
You must be signed in to change notification settings - Fork 2.6k
Improve combineApiRequests performance #2318
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Improve combineApiRequests performance #2318
Conversation
|
|
Thanks so much! I verified that this significantly reduces the number of gray screens using our new evals system. Here's a benchmark script and the results as well: // npx tsx optimizations/benchmarks/combineApiRequests-benchmark.ts
import { ClineMessage } from "../../src/shared/ExtensionMessage"
import { combineApiRequests as optimizedCombineApiRequests } from "../../src/shared/combineApiRequests"
function originalCombineApiRequests(messages: ClineMessage[]): ClineMessage[] {
const combinedApiRequests: ClineMessage[] = []
for (let i = 0; i < messages.length; i++) {
if (messages[i].type === "say" && messages[i].say === "api_req_started") {
let startedRequest = JSON.parse(messages[i].text || "{}")
let j = i + 1
while (j < messages.length) {
if (messages[j].type === "say" && messages[j].say === "api_req_finished") {
let finishedRequest = JSON.parse(messages[j].text || "{}")
let combinedRequest = { ...startedRequest, ...finishedRequest }
combinedApiRequests.push({
...messages[i],
text: JSON.stringify(combinedRequest),
})
i = j
break
}
j++
}
if (j === messages.length) {
combinedApiRequests.push(messages[i])
}
}
}
return messages
.filter((msg) => !(msg.type === "say" && msg.say === "api_req_finished"))
.map((msg) => {
if (msg.type === "say" && msg.say === "api_req_started") {
const combinedRequest = combinedApiRequests.find((req) => req.ts === msg.ts)
return combinedRequest || msg
}
return msg
})
}
function generateTestMessages(size: number, apiRatio: number = 0.4): ClineMessage[] {
const messages: ClineMessage[] = [];
let timestamp = 1000;
const createStartMessage = (request: string): ClineMessage => ({
type: "say",
say: "api_req_started",
text: `{"request":"${request}"}`,
ts: timestamp++
});
const createFinishMessage = (cost: number): ClineMessage => ({
type: "say",
say: "api_req_finished",
text: `{"cost":${cost}}`,
ts: timestamp++
});
const createRegularMessage = (content: string): ClineMessage => ({
type: "say",
say: "text",
text: content,
ts: timestamp++
});
// Fill the array with the specified ratio of API requests vs regular messages
for (let i = 0; i < size; i++) {
if (Math.random() < apiRatio) {
// Add a pair of API request messages
messages.push(createStartMessage(`GET /api/data/${i}`));
messages.push(createFinishMessage(0.001 * i));
} else {
// Add a regular message
messages.push(createRegularMessage(`Message ${i}`));
}
}
return messages;
}
function runBenchmark(messageCount: number, iterations: number = 100) {
console.log(`\nBenchmark with ${messageCount} messages (${iterations} iterations):`);
// Generate test data
const testMessages = generateTestMessages(messageCount);
let originalTotalTime = 0;
let optimizedTotalTime = 0;
// Run multiple iterations for more accurate results
for (let i = 0; i < iterations; i++) {
// Benchmark original implementation
const originalStart = process.hrtime.bigint();
originalCombineApiRequests(testMessages);
const originalEnd = process.hrtime.bigint();
const originalTime = Number(originalEnd - originalStart) / 1_000_000; // Convert to ms
originalTotalTime += originalTime;
// Benchmark optimized implementation
const optimizedStart = process.hrtime.bigint();
optimizedCombineApiRequests(testMessages);
const optimizedEnd = process.hrtime.bigint();
const optimizedTime = Number(optimizedEnd - optimizedStart) / 1_000_000; // Convert to ms
optimizedTotalTime += optimizedTime;
}
// Calculate average times
const originalAvg = originalTotalTime / iterations;
const optimizedAvg = optimizedTotalTime / iterations;
const improvement = (1 - (optimizedAvg / originalAvg)) * 100;
console.log(`Original implementation: ${originalAvg.toFixed(3)} ms`);
console.log(`Optimized implementation: ${optimizedAvg.toFixed(3)} ms`);
console.log(`Performance improvement: ${improvement.toFixed(2)}%`);
}
function main() {
console.log("Running combineApiRequests performance benchmarks");
console.log("===============================================");
runBenchmark(10, 10_000);
runBenchmark(100, 10_00);
runBenchmark(1_000, 100);
runBenchmark(10_000, 100);
runBenchmark(100_000, 10);
}
main();Results: |
cte
left a comment
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🔥
|
Thanks for cleaning that up @cte ! |
Context
I was trying to reproduce the "Blank chat window of death" issue by examining bottle necks for a huge chat history.
combineApiRequests was where it became unresponsive for my 200MB - 600m token test.
With this update, my unrealistic task history data loads properly, so I can now properly reproduce the Blank chat window of death if I drag the scrollbar too high and virtuoso freaks out. (the good news is that basic scrolling still worked well)
It's not the full fix for the unresponsive issue (Which I believe it contributed to by the bottom setting in Virtuoso for large tasks), but it's an improvement.
Implementation
I had Roo write up some tests for the original combineApiRequests, then write a new, better performing version of the function. (And reviewed of course)
Screenshots
Because I used Claude, it went wild and created some simple benchmarks:
For 100 API Requests: 1.45x faster
For 500 API Requests: 2.14x faster
For 1,000 API Requests: 3.44x faster
For 100,000 API Requests: 37x faster (this is essentially what my test task history contained)
How to Test
Load any task from task history
Important
Refactor
combineApiRequeststo improve performance and add comprehensive tests for various cases.combineApiRequestsincombineApiRequests.tsto improve performance from O(n^2) to O(n).api_req_startedmessages and combine them withapi_req_finishedmessages.api_req_startedandapi_req_finishedmessages into a single message with merged JSON data.api_req_finishedmessages from the result.combineApiRequests.test.tsto verify basic functionality, edge cases, and bug fixes.This description was created by
for 1418823. It will automatically update as commits are pushed.