MoreRSS

site iconDaniel LemireModify

Computer science professor at the University of Quebec (TELUQ), open-source hacker, and long-time blogger.
Please copy the RSS to your reader, or quickly subscribe to:

Inoreader Feedly Follow Feedbin Local Reader

Rss preview of Blog of Daniel Lemire

How many strings can you create per second?

2026-09-25 09:30:49

How many strings can you create per second?

We create new strings all the time. How quickly can you produce short strings in your programming languge? To create a meaningful string, we convert an integer to a string. In Python, that is str(i). The loop stores each new string in a small ring buffer of 1024 slots, so that the engine cannot simply discard the work.

def from_int(n):
    b = buf
    for i in range(n):
        b[i & 1023] = str(i)

In JavaScript (Node.js and Bun), I use String(i):

function from_int(n) {
  for (let i = 0; i < n; i++) buf[i & 1023] = String(i);
}

In C++, I use std::to_string:

void from_int(uint64_t n) {
  for (uint64_t i = 0; i < n; i++) buf[i & 1023] = std::to_string(i);
}

In Rust, I use the standard to_string() and, as an alternative, the popular itoa crate, which is designed for fast integer formatting:

fn std_to_string(buf: &mut [String], n: u64) {
    for i in 0..n {
        buf[(i & 1023) as usize] = i.to_string();
    }
}
fn itoa_to_string(buf: &mut [String], n: u64) {
    let mut b = itoa::Buffer::new();
    for i in 0..n {
        buf[(i & 1023) as usize] = b.format(i).to_owned();
    }
}

In Go, I use strconv.Itoa:

func fromInt(n int) {
    for i := 0; i < n; i++ {
        buf[i&1023] = strconv.Itoa(i)
    }
}

In Nim, I use the $ operator:

proc fromInt(n: int) =
  for i in 0 ..< n:
    buf[i and 1023] = $i

The integers go from 0 to 100 million (10 million in Python), so each string has up to eight digits. I report the best of five runs on an Apple M4 Max. C++ is compiled with -O3, Rust in release mode, Nim with -d:danger.

Time to convert an integer to a new string on an Apple M4 Max

Millions of strings per second:

million strings per second
C++ std::to_string 183.8
Nim $i 85.9
Go strconv.Itoa 84.3
Node.js String(i) 73.1
Rust itoa 71.0
Bun String(i) 68.5
Rust to_string() 63.6
Python str(i) 22.9

Python is the slowest at 44 ns per string, about three times slower than JavaScript and eight times slower than C++.

The compiled languages that allocate each string on the heap (Rust, Go, Nim) end up in the same range as JavaScript: 12 to 16 ns per string. Rust with its standard to_string() is even a bit slower than Node.js and Bun. Garbage-collected runtimes like Go and JavaScript are very good at allocating many small, short-lived objects.

C++ wins by a wide margin at 5.4 ns per string. The trick is the small string optimization: a std::string stores short strings, directly inside the object. Our strings have at most eight digits, so C++ never calls the memory allocator.

Versions used: macOS 15.7.7, CPython 3.14.5, Node.js 25.9.0, Bun 1.4.2, Apple clang 17.0.0 (clang-1700.6.4.2) with libc++, rustc 1.94.1 with itoa 1.0.18, Go 1.24.3, Nim 2.2.12.

Source code.

If you don’t have the factories, you lose the expertise

2026-09-24 22:09:39

Mainstream economists have been advocating for the marvellous effects of global trade for decades. Who needs these factory jobs anyway? We’ll be designing the robots and the nuclear rockets.

Except that, no. It does not work like that.

If you have the factories, sooner or later, you get the designers. If you don’t have the factories, you lose the expertise. Or you never get it.

There aren’t that many people left in the Silicon Valley capable of working on silicon. You can design a chip from California. Making one is done where the fab is.

You can get a PhD in robotics in Quebec City, but you won’t be designing robots unless you fly over to where they are made. Zoom calls won’t cut it at scale.

Jensen Huang’s account, as Joseph Steinberg reported it, is that American manufacturing jobs declined because the work was outsourced. Steinberg says this is flatly wrong, and that technology accounts for the vast majority of the decline in manufacturing’s share of employment.

U.S. manufacturing employment, millions of jobs

In 2000 there were 17.3 million manufacturing jobs in the United States. The peak was 19.6 million, in June 1979. From the early 1980s to 2000 the count stayed high, apart from the recessions. China joined the WTO in December 2001. By 2010 manufacturing employment was 11.5 million. In August 2026 it was 12.6 million.

That is 5.8 million jobs gone in a decade. About a million have come back since the bottom. The rest have not.

If technology were the main driver, output should have kept rising while the jobs fell. The fifteen years before 2000 are what that looks like. Manufacturing output nearly doubled, from an index of 51 to an index of 93 (2017 = 100). Employment went from 17.8 million to 17.3 million. The robots were already here in 1985. Employment did not collapse.

U.S. industrial production, index 2017 = 100

After 2000, production stalled. Manufacturing output peaked just under 107 in December 2007. In August 2026 the index was 99.1. Total industrial production was 103.1. American factories are not turning out a flood of extra goods. They are turning out roughly what they turned out twenty years ago.

Output per hour did rise while the jobs were disappearing. The BLS index of manufacturing labor productivity went from about 70 in 2000 to about 100 in 2010. Then it stopped. In early 2026 it was still about 100. Flat output, fewer workers, a higher ratio. The ratio has been flat for fifteen years.

Year Jobs (millions) Manufacturing output (2017 = 100)
1985 17.8 51
2000 17.3 93
2007 13.9 105
2010 11.5 93
August 2026 12.6 99.1

The problem after 2000 was not that American factories became too productive. What changed after 2000 was where the goods were made.

And now, often, we don’t know anymore how to make things. Human expertise matters, and you maintain it by building stuff locally. You are not going to design microprocessors in Maine. It just won’t happen.

 

A summer of AI optimization

2026-09-22 10:27:44

A summer of AI optimization

I maintain and comaintain several open-source libraries. Some of them are widely used: ada parses URLs in Node.js, fast_float parses numbers in GCC’s standard library and in Chromium, simdjson parses JSON in Node.js, simdutf validates and transcodes Unicode in Node.js, and the Roaring bitmap libraries sit inside many database engines.

These libraries are mature. They have been optimized for years, by me and by others. For a long time, their performance was flat. Not because nobody cared, but because the remaining gains were expensive: each one required a few days of careful work, and nobody had the days.

Then, in 2026, six of them got much faster, most of it in a few weeks of summer.

To formalize my feeling, I rebuilt every commit of each library from scratch and benchmarked it on one machine (an Intel Xeon Gold 6548N). I track the speedup over time relative to August 2024. Thus the value 1.0 means no speedup. Whereas 2.0 means that the performance doubled. The lines are steps because performance only changes at a commit.

I should say that I cannot know how much AI was involved in each instance. I don’t ask how people arrived at their code. All I ask is that it be good. As for myself, I code with Claude (Opus 5), Grok and DeepSeek (V4 Pro). I was an early adopter of Grok for coding, and it got really good over time.

1. roaring (compressed bitmaps, Go)

roaring: speedup over time

The roaring library is the Go version of the Roaring index data structure. Decoding to an array got 2.5 times faster, the multi-way union FastOr got 3.1 times faster on one data set, the many-value iterator got 4.5 to 5.9 times faster, and the intersection cardinality gained 10%.

One of the contributors is an AI, actually. It is perfloop. (Disclosure: I am an advisor for perfloop.)

I did a lot of work. We also got help from Philipp Klose who declared using Claude.

2. ada (URL parsing)

ada: speedup over time

The ada library is a standard compliant URL parser. From August 2024 to July 2026, about 550 commits went in and the throughput on a corpus of 100,000 URLs stayed at 0.54 GB/s. Then, in six weeks, it went to 1.28 GB/s: 2.4 times faster, about 15 million URLs per second on one core.

Most of the optimizations were done by Yagiz Nizipli, my long-time co-author. Yagiz works at SpaceX and uses Cursor (presumably with a grok model). Abdul Rawoof Khan and Dillon Mulroy also contributed an optimization each. I worked at optimizing IP address parsing, but it won’t show in this particular benchmark.

3. fast_float (number parsing)

fast_float: speedup over time

The fast_float library parses floating-point numbers from text. It is part of GCC and most browsers. Performance was flat for fifteen months. Then, from March to July 2026, it gained 43% on one file (canada.txt, long coordinates) and 70% on another (mesh.txt, short coordinates). The optimizations should be credited to Koleman Nix and Filipe Oliveira.

4. simdjson (JSON serialization and deserialization with C++26 reflection)

simdjson: serialization and deserialization speedup over time

The simdjson library recently gained support for C++26 static reflection: you serialize and parse your own structs directly, with no glue code. Since February 2026, serialization is 1.6 times faster on twitter.json and 2.1 times faster on citm_catalog.json. Deserialization, JSON straight into a struct, gained a more modest 10% and 14% (the second panel). (The reflection code only exists since early 2026.) The number of instructions per byte fell by almost exactly the same ratio as the throughput rose: from 6.1 to 3.1 instructions per byte on citm_catalog.json serialization.

Francisco Geiman Thiesen (Microsoft) did most of the work on the serialization side while I mostly helped improve our parsing. Francisco uses Claude.

5. simdutf (Unicode validation and transcoding)

simdutf: speedup over time

The simdutf library validates and transcodes UTF-8, UTF-16 and UTF-32, and encodes and decodes base64. ASCII validation went from 83 GB/s to 160 GB/s. UTF-16 validation went from 62 GB/s to 102 GB/s. Base64 decoding gained 17%.

The work was done by Yagiz Nizipli (again) and myself.

The library got other amazing optimizations that do not show up on this benchmark by Gaspard Petit and Shreesh Adiga.

6. CRoaring (compressed bitmaps, C)

CRoaring: speedup over time

CRoaring implements Roaring bitmaps in C. On the real data sets from the repository, membership tests (contains) got 2.4 times faster, the cardinality of 64-bit bitmaps got 4.9 times faster, iterating over a 64-bit bitmap got 1.9 times faster, decoding a dense bitmap to an array got 2.2 times faster. Unions gained a more modest 13% to 16%.

The authors were Andrei Gudkov and myself.

What happened

The techniques used are all well-known. So why all these optimizations all of a sudden? Simply put, in my view, because it got cheap to try new ideas.

There is a lot of talk about the risks of AI in software. Human beings tend to be susceptible to the one-sided bet fallacy: when we see the downsides, we tend to ignore the benefits. Cars kill people, but ambulances save them.

In this instance, the benefits are concrete. Millions of people run these libraries, and this summer, they got faster.

More than a taken branch per cycle?

2026-09-22 02:36:05

Our processors can execute many instructions per cycle; they are superscalar. But not all instructions are equal.

Branches are particularly tricky. A branch occurs often when you use an if-then clause or a loop. We distinguish between a taken branch and a not taken branch.

A not-taken branch is often cheap. The processor just keeps going.

A taken branch jumps to a new location. A taken branch can be more expensive.

You will often hear that processors are limited to one taken branch per cycle.

I decided to test it out with a loop with an if inside it. Here is the function I tested in Go.

func lastHit(p []byte, thresh byte, last *byte) {
    n := len(p)
    if n == 0 {
        return
    }
    i := 0
    for {
        v := p[i]
        if v > thresh {
            *last = v
        }
        i++
        if i == n {
            break
        }
    }
}

Look at the main loop. We load a value from an array, we compare it with a threshold. If it is greater than the threshold, then we assign it to the last pointer. So the function effectively records the last seen value that is greater than the threshold. That’s pretty reasonable code.

Consider the case where you always miss. The values are always smaller than or equal to the threshold. In these cases, we get two taken branches in close proximity, but no store. (It is a bit confusing but that’s how the Go compiler does it.)

Cycles per iteration when the if always misses

The processor that struggles the most is the AMD Zen 4 processor. But AMD Zen 5 is much better.

So two processors are able to take two branches in less than 2 cycles on average in this test: the Apple processor (M4 Max) and the Granite Rapids processor.

This means that, yes, modern processors can execute more than one taken branch per cycle under some conditions.

The code is under benchmark/experiments/ifloop in the GitHub repo.

The data does not show mass unemployment

2026-09-21 23:04:08

If AI is causing mass unemployment among software developers, it is not showing up in the data yet.
 
The USA had more software developers as a percentage of the population in 2025 than in 2021.
 
On average salaries are slightly up (average of 148k$ a year). The 90th percentile is up from 2021 (215k$US a year).

AI is breaking the academic sorting machine

2026-09-21 05:47:46

I see the mathematicians panicking at what we can do with agentic AI.

Let us be clear on what we are talking about. In a few weeks, someone who is not a high-level mathematician can, with AI, produce the equivalent of a good PhD thesis in math. I could see a top 1% high school student, given enough of an AI token budget, just write the equivalent of a PhD thesis as a hobby.

If Joe Smith completed a PhD thesis in math back in 2022, it is now possible for a really smart high schooler to generate the same output while playing video games. Joe must not be too happy about it, especially if Joe is still looking for a prestigious faculty position.

Notice how we are not so excited. I mean, why aren’t we celebrating this incredible breakthrough? Finally, all the math problems we have can be solved faster! It is worth reflecting on why we don’t care.

Part of the issue is that most of academic research lost its customers years ago.

In part thanks to the arrival of “peer review” in the 1970s, we have long ago closed the research world into siloed communities. The purpose of the academic output is to sort people out for jobs. Write papers that are impressive and you may get a good professorship. If you don’t, then you will be flipping burgers. There is an incredible glut of people with academic credentials, but only so many jobs at the top. The glut has been continuously, and somewhat deliberately, increasing.

If you hold a PhD today, your chance of having a tenure-track or tenured position is about 10% and falling. Let me be clear. Any academic who shows worry for what the PhDs might do now should look in a mirror because you have been training too many for decades. And, also, if fewer people decide to go for the PhD that might be more than fine.

AI disrupts the sorting mechanism in mathematics… But do you think for a minute that it does not apply to mechanical engineering, sociology, etc.? Is that bad news? No. I think that it is excellent news and I have been saying so for years.

Here is what I wrote in 2024…

« AI’s ability to generate vast amounts of text raises concerns about a potential flood of irrelevant theoretical papers, further straining the evaluation system. Stonebraker’s (2018) call for rewarding problem-solving over publication needs revisiting. Perhaps the emphasis should be on the impact and significance of research, not just its passage through peer review—a skill replicable by AI. AI can pave the way for a “golden age” of scientific progress if we can develop new evaluation methods focused on problem-solving and real-world impact. The scientific community must adapt to the evolving landscape. By recognizing the limitations of peer review and prioritizing the pursuit of meaningful solutions, we can ensure that AI becomes a catalyst for scientific advancement, not a detriment. »

I predict that, over time, the focus will move away from “papers as the final output.”

Nobody wants a paper about cancer, we want to eradicate cancer. We should reward people who get results, no matter which tools they use. And solving a problem because it is impressive to do so is not enough.

« But Daniel, Mathematicians can’t cure cancer or give us antigravity. »

Maybe it is time they try. Frankly, the AI disruption might be precisely what we needed.

« But Daniel, won’t AI just replace all of us? »

I wish. But thus far, it is not happening. I have more AI accounts than most people have pencils and I am working 50 hours a week. Intelligence is not a scalar quantity. Once an AI can do something, I somehow always find more work to do.

Further reading. Daniel Lemire, Will AI Flood Us with Irrelevant Papers? Communications of the ACM, Vol. 67, No. 9 (September 2024).