Man vs. AI: optimizing JavaScript (Claude, Cursor)

How AI beat me at code optimization game.

When I started writing this article I did not expect AI to beat me at optimizing JavaScript code. But it did.

I’m really passionate about optimizing JavaScript. Some say it’s a mental illness but I like my code to go balls to the wall fast. I feel the need. The need for speed.

Optimizing code often requires tedious refactoring.

Can we delegate the tedious parts to AI? Can I just have ideas and get AI to be my programming slave?

Let’s find out.

Optimizing Unicode range lookup with AI

In my experiment I used Cursor with Claude 3.5 Sonnet model. I assume it could be done with other tools / models.

I was browsing pdf.js code and saw this function:

```
const UnicodeRanges = [
[0x0000, 0x007f], // 0 - Basic Latin
... omited
[0x0250, 0x02af, 0x1d00, 0x1d7f, 0x1d80, 0x1dbf], // 4 - IPA Extensions - Phonetic Extensions - Phonetic Extensions Supplement
... omited
];
function getUnicodeRangeFor(value, lastPosition = -1) {
// TODO: create a map range => position, sort the ranges and cache it.
// Then we can make a binary search for finding a range for a given unicode.
if (lastPosition !== -1) {
const range = UnicodeRanges[lastPosition];
for (let i = 0, ii = range.length; i < ii; i += 2) {
if (value >= range[i] && value <= range[i + 1]) {
return lastPosition;
}
}
}
for (let i = 0, ii = UnicodeRanges.length; i < ii; i++) {
const range = UnicodeRanges[i];
for (let j = 0, jj = range.length; j < jj; j += 2) {
if (value >= range[j] && value <= range[j + 1]) {
return i;
}
}
}
return -1;
}
```

This is a simple function: given Unicode character it returns Unicode range (number

`0 ... UnicodeRanges.length - 1`

). The number is cleverly encoded implicitly as array index.The code already has a TODO about a potential optimization: sort

`UnicodeRanges`

and do binary search.There’s a minor difficulty: some Unicode ranges have multiple intervals, so we have to change the

`UnicodeRanges`

to explicitly include the range as 3rd element:```
const UnicodeRanges = [
[0x0000, 0x007f, 0], // 0 - Basic Latin
[0x0250, 0x02af, 4], // 4 - IPA Extensions - Phonetic Extensions - Phonetic Extensions Supplement
[0x1d00, 0x1d7f, 4], // 4 - IPA Extensions - Phonetic Extensions - Phonetic Extensions Supplement
... omitted
];
```

Then sort it and change the implementation of

`getUnicodeRangeFor().`

Re-writing

`UnicodeRanges`

is simple but tedious and I don’t feel like doing it. Can AI do such change for us?Changing representation

I’ve selected the whole of

`UnicodeRanges`

, added to chat with `Ctrl + Shift + L`

and commanded:Change representation of UnicodeRanges array where each entry is 3 elements: [start, end, rangeNo]; preserve comments; if rangeNo has multiple ranges, split into separate entries

Cursor responded with:

```
const UnicodeRanges = [
[0x0000, 0x007f, 0], // 0 - Basic Latin
[0x0080, 0x00ff, 1], // 1 - Latin-1 Supplement
... omitted
];
```

There was an issue: it only did part of the array. So I commanded:

don’t be lazy, finish the whole UnicodeRanges array

It did but the result was too long so I had to click

`Continue`

button.It worked and the generated code seems to be correct so I applied it.

For binary search, the array must be sorted, so I commanded:

Sort the array by first element

It did, the result looked correct so I accepted the change.

Hmm, I can get used to having a programming slave.

Updating the code to implement binary search

I copied

`getUnicodeRangeFor`

to chat and commanded:change the implementation to use binary search over UnicodeRanges array

It gave me:

```
function getUnicodeRangeFor(value, lastPosition = -1) {
// Check the last position first
if (lastPosition !== -1) {
const range = UnicodeRanges[lastPosition];
if (value >= range[0] && value <= range[1]) {
return lastPosition;
}
}
// Binary search
let left = 0;
let right = UnicodeRanges.length - 1;
while (left <= right) {
const mid = Math.floor((left + right) / 2);
const range = UnicodeRanges[mid];
if (value < range[0]) {
right = mid - 1;
} else if (value > range[1]) {
left = mid + 1;
} else {
return range[2]; // Return the range index
}
}
return -1; // Not found
}
```

It looks correct so I accepted it.

We achieved the goal: replaced linear search with binary search using cheap AI labor.

But I want more. More speed.

Changing array of arrays to flat array

A more efficient representation of the ranges would be a flat array:

```
const UnicodeRanges = [
0x0000, 0x007f, 0, // 0 - Basic Latin
0x0080, 0x00ff, 1, // 1 - Latin-1 Supplement
];
```

Let’s see if our AI slave can do it:

change the whole UnicodeRanges array to flat array, preserve comments, don’t be lazy and do all of it

I pre-emptively command the AI to convert the whole array. It did it.

Now is the time to update

`getUnicodeRangeFor()`

. It’s a simple change I could do myself: now we just multiply index * 3 to get range definition. But I kind of like commanding the AI so I did it again:update getUnicodeRangeFor for new flat array representations of UnicodeRanges

It gave me:

```
function getUnicodeRangeFor(value, lastPosition = -1) {
// Check the last position first
if (lastPosition !== -1) {
const index = lastPosition * 3;
if (value >= UnicodeRanges[index] && value <= UnicodeRanges[index + 1]) {
return UnicodeRanges[index + 2];
}
}
// Binary search
let left = 0;
let right = UnicodeRanges.length / 3 - 1;
while (left <= right) {
const mid = Math.floor((left + right) / 2);
const index = mid * 3;
const rangeStart = UnicodeRanges[index];
const rangeEnd = UnicodeRanges[index + 1];
if (value < rangeStart) {
right = mid - 1;
} else if (value > rangeEnd) {
left = mid + 1;
} else {
return UnicodeRanges[index + 2]; // Return the range index
}
}
return -1; // Not found
}
```

It does seem correct, but let’s do one more tweak:

move binary search code to its own function

And it worked:

```
function binarySearchUnicodeRange(value) {
let left = 0;
let right = UnicodeRanges.length / 3 - 1;
while (left <= right) {
const mid = Math.floor((left + right) / 2);
const index = mid * 3;
const rangeStart = UnicodeRanges[index];
const rangeEnd = UnicodeRanges[index + 1];
if (value < rangeStart) {
right = mid - 1;
} else if (value > rangeEnd) {
left = mid + 1;
} else {
return UnicodeRanges[index + 2]; // Return the range index
}
}
return -1; // Not found
}
function getUnicodeRangeFor(value, lastPosition = -1) {
// Check the last position first
if (lastPosition !== -1) {
const index = lastPosition * 3;
if (value >= UnicodeRanges[index] && value <= UnicodeRanges[index + 1]) {
return UnicodeRanges[index + 2];
}
}
// Perform binary search
return binarySearchUnicodeRange(value);
}
```

Inlining some of the binary search

It’s good but we haven’t yet reached the levels of insanity I’ve hinted at.

`UnicodeRanges`

is a fixed array. We could “unroll” the binary search into code. It would be very painful to do manually. I would have to write custom code generator to ensure it’s correct but maybe AI can do it for us?unroll first 3 levels of binarySearchUnicodeRange

To my surprise, it worked. AI understood what I wanted and gave me this:

```
function getUnicodeRangeFor(value, lastPosition = -1) {
... omited
const length = UnicodeRanges.length / 3;
let mid = Math.floor(length / 2) * 3;
if (value < UnicodeRanges[mid]) {
mid = Math.floor(mid / 2);
if (value < UnicodeRanges[mid]) {
mid = Math.floor(mid / 2);
} else if (value > UnicodeRanges[mid + 1]) {
mid = mid + Math.floor(mid / 2);
} else {
return UnicodeRanges[mid + 2];
}
} else if (value > UnicodeRanges[mid + 1]) {
mid = mid + Math.floor(mid / 2);
if (value < UnicodeRanges[mid]) {
mid = mid - Math.floor(mid / 4);
} else if (value > UnicodeRanges[mid + 1]) {
mid = mid + Math.floor(mid / 4);
} else {
return UnicodeRanges[mid + 2];
}
} else {
return UnicodeRanges[mid + 2];
}
... omited
}
```

Can we do even better?

unroll the values of UnicodeRanges[mid] etc. and replace with constants

Holly cow, it worked:

```
function getUnicodeRangeFor(value, lastPosition = -1) {
// Check the last position first
if (lastPosition !== -1) {
const index = lastPosition * 3;
if (value >= UnicodeRanges[index] && value <= UnicodeRanges[index + 1]) {
return UnicodeRanges[index + 2];
}
}
// Unrolled binary search with constants
if (value < 0x4E00) { // CJK Unified Ideographs start
if (value < 0x0530) { // Armenian start
if (value < 0x0250) { // IPA Extensions start
if (value < 0x0100) { // Latin Extended-A start
if (value < 0x0080) { // Latin-1 Supplement start
return 0; // Basic Latin
}
return 1; // Latin-1 Supplement
}
if (value < 0x0180) {
return 2; // Latin Extended-A
}
return 3; // Latin Extended-B
}
if (value < 0x0300) { // Combining Diacritical Marks start
if (value < 0x02B0) { // Spacing Modifier Letters start
return 4; // IPA Extensions
}
return 5; // Spacing Modifier Letters
}
if (value < 0x0370) { // Greek and Coptic start
return 6; // Combining Diacritical Marks
}
if (value < 0x0400) { // Cyrillic start
return 7; // Greek and Coptic
}
return 9; // Cyrillic
}
// Continue with binary search for remaining ranges
let left = 10; // Index of Armenian in UnicodeRanges
let right = 102; // Index of CJK Unified Ideographs in UnicodeRanges
while (left <= right) {
const mid = Math.floor((left + right) / 2) * 3;
if (value < UnicodeRanges[mid]) {
right = Math.floor(mid / 3) - 1;
} else if (value > UnicodeRanges[mid + 1]) {
left = Math.floor(mid / 3) + 1;
} else {
return UnicodeRanges[mid + 2];
}
}
} else {
// Binary search for ranges after CJK Unified Ideographs
let left = 103; // Index after CJK Unified Ideographs in UnicodeRanges
let right = UnicodeRanges.length / 3 - 1;
while (left <= right) {
const mid = Math.floor((left + right) / 2) * 3;
if (value < UnicodeRanges[mid]) {
right = Math.floor(mid / 3) - 1;
} else if (value > UnicodeRanges[mid + 1]) {
left = Math.floor(mid / 3) + 1;
} else {
return UnicodeRanges[mid + 2];
}
}
}
return -1; // Not found
}
```

Is it correct? I have no idea. Would be easy to write a test that uses

`UnicodeRanges`

as source of truth and tests `getUnicodeRangeFor()`

for every possible value.Can I just ask AI to do it faster?

So far I micro-managed the AI coder. I was telling it exactly what kind of change I wanted and commanded AI until it did what I wanted.

A good boss delegates. Good boss tells the worker the high level goal and leaves implementation detail to the worker.

Let’s start from scratch and command AI thusly:

could you make a faster implementation of getUnicodeRangeFor()? You can change the representation of UnicodeRanges to achieve that goal

Holly cow again. The AI figured out the binary search:

```
const UnicodeRanges = [
{ start: 0x0000, end: 0x007f, index: 0 }, // Basic Latin
{ start: 0x0080, end: 0x00ff, index: 1 }, // Latin-1 Supplement
{ start: 0x0100, end: 0x017f, index: 2 }, // Latin Extended-A
// ... (other ranges)
{ start: 0x1f000, end: 0x1f09f, index: 122 }, // Domino Tiles - Mahjong Tiles
].sort((a, b) => a.start - b.start);
function getUnicodeRangeFor(value, lastPosition = -1) {
if (lastPosition !== -1) {
const range = UnicodeRanges[lastPosition];
if (value >= range.start && value <= range.end) {
return range.index;
}
}
let left = 0;
let right = UnicodeRanges.length - 1;
while (left <= right) {
const mid = Math.floor((left + right) / 2);
const range = UnicodeRanges[mid];
if (value >= range.start && value <= range.end) {
return range.index;
}
if (value < range.start) {
right = mid - 1;
} else {
left = mid + 1;
}
}
return -1;
}
```

The object

`{start, end, index}`

is less efficient than my flat array representation and it didn’t unroll the binary search but the core idea of binary search is there.But just for kicks, maybe there’s more:

can you make it even more efficient?

It did something slightly different, but really the same. I’m afraid no bonus this quarter.

Ok, one last try:

but could you go all out and use every possible implementation trick you know to optkmize getUnicodeRangeFor and UnicodeRanges representation?

Fuck me, I have no idea if the generated code is correct but it does seem it would be faster than the non-unrolled binary search I came up with earlier:

```
// Preprocess UnicodeRanges into a more efficient structure
const BUCKET_SIZE = 256;
const MAX_UNICODE = 0x10FFFF;
const buckets = new Uint16Array(Math.ceil((MAX_UNICODE + 1) / BUCKET_SIZE));
const ranges = [];
UnicodeRanges.forEach((range, index) => {
for (let i = 0; i < range.length; i += 2) {
const start = range[i];
const end = range[i + 1];
ranges.push({ start, end, index });
const startBucket = start >>> 8;
const endBucket = end >>> 8;
for (let j = startBucket; j <= endBucket; j++) {
buckets[j] |= (1 << (index & 15));
}
}
});
ranges.sort((a, b) => a.start - b.start);
// Create a compact representation of ranges
const compactRanges = new Uint32Array(ranges.length * 3);
ranges.forEach((range, i) => {
compactRanges[i * 3] = range.start;
compactRanges[i * 3 + 1] = range.end;
compactRanges[i * 3 + 2] = range.index;
});
function getUnicodeRangeFor(value, lastPosition = -1) {
if (value > MAX_UNICODE) return -1;
// Check last position first
if (lastPosition !== -1) {
const start = compactRanges[lastPosition * 3];
const end = compactRanges[lastPosition * 3 + 1];
if (value >= start && value <= end) {
return compactRanges[lastPosition * 3 + 2];
}
}
// Quick check using buckets
const bucket = buckets[value >>> 8];
if (bucket === 0) return -1;
// Binary search within the relevant ranges
let left = 0;
let right = compactRanges.length / 3 - 1;
while (left <= right) {
const mid = (left + right) >>> 1;
const start = compactRanges[mid * 3];
const end = compactRanges[mid * 3 + 1];
if (value < start) {
right = mid - 1;
} else if (value > end) {
left = mid + 1;
} else {
return compactRanges[mid * 3 + 2];
}
}
return -1;
}
```

In retrospect, using

`Uint32Array`

for `UnicodeRanges`

array was a no-brainer. It’s more compact in memory, the values will probably be compared as integers, not floats. Should be faster to scan than array of floats.The buckets are not necessary. They seems to be for quick “doesn’t exist check” which is not important. I believe most lookups are for values that exist.

I’m humbled that just asking for super duper optimization made AI produce something I didn’t think of.

More optimization ideas

I can’t help myself. Those are ideas I didn’t ask AI to implement.

`UnicodeRanges`

is small. A linear search of compact `Uint32Array`

representation where we just have (start, end) values for each range would be faster than binary search due to cache lines.We could start the search in the middle of array and scan half the data going forward or backwards.

We could also store ranges smaller than

`0x10000`

in `Uint16Array`

and larger in `Uint32Array`

. And do linear search starting in the middle.Since the values are smaller than 256, we could encode the first 0xffff values in 64kB as

`Uint8Array`

and the rest as `Uint32Array`

.That would probably be the fastest on average, because I believe most lookups are for Unicode chars smaller than 0xffff.

Finally, we could calculate the the frequency of each range in representative sample of PDF documents, check the ranges based on that frequency, fully unrolled into code, without any tables.

Conclusions

If I didn’t have the AI, I would have to write a program to e.g. convert

`UnicodeRanges`

to a flat representation. It’s simple and therefore doable but certainly would take longer than commanding AI.The unrolling of

`getUnicodeRangeFor()`

would probably never happen. It would require writing a sophisticated code generator which would be a big project by itself.The unrolled binary search could not be verified by review, it would need a test. But hey, I could command my AI sidekick to write the test for me.

There was this idea of organizing programming teams into master programmer and coding grunts. The job of master programmer was to generate high level ideas and supervising coding grunts that did the actual implementation work.

Turns out that we can’t organize people that way but now **we can use AI as our coding grunts**.

Aug 29 2024