json.loads took 105.3 ms, while compiling the regex made little difference
In this article
For JSON containing 50,000 records, json.dumps took 82.73ms and json.loads took 105.3ms.
It is reasonable to say that reading was 1.3x heavier than writing, but the regex results were far from what I expected before measuring. I had assumed compilation would make a clear difference: compile with re.compile, then use the pattern, and it should be noticeably faster. Locally it was 7.51ms versus 8.59ms, only a 1.1x difference. Python caches string patterns internally, so repeatedly passing the same pattern to re.findall costs less than I expected.
More surprising was that simple str.split reached 15.37ms. I had assumed it would be lightweight. The result that regex was faster when extracting the same information felt wrong at first, precisely because I expected split to be lighter. This was useful to measure.
The measurement date was June 18, 2026. I ran it around 20:18, and the date recorded in the log was 2026-06-18. The environment was a Surface Pro 11th Edition with a Snapdragon X 12-core X1E80100, Windows 11 Pro 10.0.26200 ARM64, and ARM64 Python 3.12.10. The machine was connected to AC power, with the Balanced power plan unchanged.
Work with JSON and log strings using only the standard library
I used only the standard library, so optimizations from external libraries are not mixed in. The comparison range was deliberately narrow. On the JSON side, I serialized 50,000 records once with json.dumps and restored the string with json.loads. The generated JSON was 5,538,890 bytes, or 5.54MB in decimal MB.
payload = json.dumps(records, separators=(",", ":"))
obj = json.loads(payload)
On the log side, I generated 20,000 lines of text and extracted values such as ID, level, and path with the same regular expression. I compared three cases: compiling re first, passing the string pattern directly to re.findall, and splitting with splitlines and split.
pattern = re.compile(r"id=(\d+) level=(\w+) path=([^ ]+)")
matches = pattern.findall(text)
matches = re.findall(r"id=(\d+) level=(\w+) path=([^ ]+)", text)
I measured each case five times and used the median rather than the single fastest run. This also protects against feeling too good about an outlier. To stay close to ARM64 Windows usage, I deliberately left the Surface Pro in its normal state instead of switching to a high-performance setting.
Leave all five runs visible
I kept every repetition because showing only medians makes the table look too clean and makes it easy to forget which run actually jumped. That matters with small measurements. The JSON had 50,000 records and json_bytes of 5,538,890 bytes. The text had text_lines of 20,000.
| Case | Input | runs_ms (5 runs) |
median_ms |
|---|---|---|---|
| json.dumps | 50,000 records / 5,538,890 bytes | 105.18 / 76.54 / 82.34 / 82.73 / 89.65 | 82.73ms |
| json.loads | 50,000 records / 5,538,890 bytes | 105.3 / 112.35 / 108.71 / 98.5 / 101.31 | 105.3ms |
| regex_compiled | 20,000 lines | 7.51 / 17.85 / 7.11 / 7.04 / 7.65 | 7.51ms |
| regex_uncompiled | 20,000 lines | 10.09 / 7.34 / 8.59 / 7.15 / 10.54 | 8.59ms |
| str_split | 20,000 lines | 21.84 / 11.0 / 15.44 / 15.37 / 9.49 | 15.37ms |
json.dumps processed 5.54MB in 82.73ms, about 67.0MB/s, or about 604,000 records/s. json.loads read the same 5.54MB in 105.3ms, falling to about 52.6MB/s, or about 475,000 records/s.
The difference is not enormous, but the direction is clear: JSON is heavier to read than to write. A tool that accumulates many file reads and parses may wait longer during restoration than during saving. That becomes relevant later in tools with more configuration files, especially when loading configuration at startup.
The compiled regex was 7.51ms and the uncompiled one 8.59ms. With 20,000 lines, that is about 2,663,000 lines/s versus 2,328,000 lines/s. Compilation is faster, but a 1.1x difference is smaller than it feels.
str.split took 15.37ms, about 1,301,000 lines/s. Although its name sounds lightweight, this implementation was about half as fast as the regex. It first builds a list of 20,000 lines with splitlines, then splits every line again, so the straightforward approach adds object-creation cost.
Compiled does not automatically win
When I first looked at the table, the 17.85ms regex_compiled outlier made the compiled case look unstable. If I focused only on that value, it would look like the measurement had failed. The median was 7.51ms, however, and the other four values were in the 7ms range. A single outlier can easily lead to the wrong decision.
The other mistaken assumption was overestimating the effect of re.compile. Python's re caches recently used patterns, so repeatedly passing the same string pattern does not compile it fully from scratch every time. I thought I knew that, but seeing 7.51ms and 8.59ms next to each other changed the impression. If speed is the only reason for a rewrite, the improvement is not dramatic here.
That does not mean “never compile.” Compilation still makes sense for several complex patterns, making the intent explicit outside a loop, or surfacing errors earlier. It simply was not large enough here to justify rewriting every short operation for speed alone.
Why did split lose?
Because str.split is a built-in method, I expected it to be faster than a regular expression. That intuition was wrong for this measurement. What exactly was being compared?
It was not one isolated split. I first turned a 20,000-line string into a list with splitlines, then split each line again. Extracting the needed columns created multiple temporary strings and lists. The regex returned only the required groups in one pass, which matched this log format better.
The difference was 15.37ms versus 7.51ms, about 2.0x. It is worth caring about for large log processing, but it may be noise for a short configuration file. My practical rule is to write the more readable version first and measure only when the workload reaches something like 20,000 lines.
How I will use them
For JSON, I now question restoration before saving. With 5.54MB, the operation is around 100ms, but the difference accumulates as the record count grows. A tool that calls json.loads on several files at startup is especially likely to feel the delay.
For regex, I will not treat re.compile as mandatory purely for speed. I compile and name a pattern when it is reused, but a short operation contained in one place can keep the string pattern. In my Python 3.12.10 environment, the measured difference was 1.1x.
The scope also matters. This log measured only the standard-library json and re; I am not mixing in conclusions about other libraries or other machines. That would change the conditions. I did not compare with a fast library such as orjson, although I would like to do that in another environment. As of June 18, 2026, this is a record of how the standard json and re behave on ARM64 Windows, and I am leaving it there rather than turning it into a different article.