GTA Online's PC loading screen could take six minutes, and the servers weren't the cause. In February 2021 a player called t0st found out why without the…
// the short version
GTA Online's PC loading screen could take six minutes, and the servers weren't the cause. In February 2021 a player called t0st found out why without the source code. Task Manager showed that after about a minute of normal loading, the disk, network and GPU went quiet while a single CPU core stayed maxed for about four more minutes. A sampling profiler, which pauses the process at intervals and records the instruction pointer, put nearly every sample in two places.
// what to take away
- On t0st's PC in 2021, GTA Online spent about four of its six loading minutes with one CPU core maxed while disk, network and GPU idled. A sampling profiler, which needs no source code, put nearly every sample in two places.
- The first: the game parsed a 10 MB JSON shop catalogue with sscanf, which in most C libraries calls strlen on its input. A C string stores no length, so every number read cost a walk through the whole rest of the text.
- The second: each of about 63,000 parsed items was compared with every item already stored, about 1.98 billion comparisons, in a list that held no duplicates. Fixing both took his load from 6:00 to 1:50; Rockstar shipped its own fix on 16 March 2021 and paid him $10,000.
What happened to GTA Online's loading screen on PC
GTA Online is the multiplayer mode of Grand Theft Auto V. By 2021 its PC version was known for loading slowly: on the PC of a player who goes by t0st (an AMD FX-8350, 16 GB of DDR3, a budget SATA SSD and a GTX 1070), story mode took about 1 minute 10 seconds to load and online mode about 6 minutes. A poll he found suggested most players were at least mildly annoyed, and the folk explanations were the game's size or its peer-to-peer networking.
He showed it was neither. In a post on 28 February 2021 he traced most of the wait to two pieces of code running on one CPU core, both doing far more work than the data needed. Rockstar confirmed the finding was real, shipped its own fix on 16 March 2021 and paid him $10,000. This page walks through how he found it, what the two bugs were, and why the pattern behind them, often called accidentally quadratic, is worth recognising in your own code.
Everything here is about the PC version of GTA Online in 2021. It says nothing about GTA 6, which Rockstar lists for 19 November 2026 on PS5 and Xbox Series X|S.
Step one: watch what is busy
Before reaching for anything clever, t0st opened Task Manager while the game loaded. For the first minute every resource moved, roughly the same work story mode does. After that the picture changed: disk reads stopped, network traffic fell to almost nothing apart from the rotating info banners, the GPU sat at zero and memory use was flat, while one CPU core stayed maxed for about four minutes.
That pattern rules a lot out. A game waiting on its servers, its disk or a peer-to-peer handshake looks idle. A game pinning one core while nothing else moves is computing, in its own code, on one thread. His old CPU had eight cores but weak single-thread performance by 2021 standards, which explains why his machine suffered more than a fast one, but not why the work existed at all.
One caution if you look at his screenshot: its overall CPU graph reads about 61% on an eight-core chip, which does not match one core alone. The video uses his own description ("max out a single core"), which the profile that follows supports.
Step two: sample where the time goes
A profiler tells you which code is using the time. Many profilers instrument the program, which needs its source or debug symbols, and he had neither. So he used a sampling profiler: it pauses the running process at intervals, records where it was (the instruction pointer, plus the call stack), and moves on. Count enough of those snapshots and the hottest code stands out, with no source needed. He had four minutes of a single hot thread, so precision did not matter.
The tool he used was Luke Stackwalker, an old free profiler for Windows. Without symbols it could only report raw addresses, so he grouped neighbouring addresses by eye. Two clusters held the samples on the busy thread, roughly 55% and 45%: two separate bottlenecks rather than one.
Step three: read only the hot code
The game's shipped code is obfuscated against reverse engineering, so the instructions on disk are not the ones that run. The protection has to undo itself before code executes, so he dumped the process's memory while it was loading and read the machine code at the two hot addresses from that dump. One address carried a recognisable label, strlen; one frame up was a function labelled vscan_fn, and he was "fairly confident" the caller was sscanf. Stepping through it in a debugger showed what it was parsing: about 10 MB of JSON, a "net shop catalog" with some 63,000 entries, apparently every item and upgrade you can buy in GTA Online.
The video names the method, not the tools, and so does this page beyond the profiler. It also leaves out how he tested the fix, which involved loading his own code into the running game.
Bug one: measuring 10 MB before every number
A C string does not store its length. It is a run of bytes ending in a zero byte, so the only way to know its length is to walk every byte until that zero. That is what strlen does.
sscanf reads values out of a string. As t0st notes, most sscanf implementations call strlen on their input first. On a short string that costs nothing. The parser here kept one pointer into the 10 MB buffer and called sscanf at that pointer for each value. To C, the "string" at that pointer runs all the way to the end of the catalogue, so every call measured the whole remaining text before reading a few bytes. The shape, as an illustration (this is not Rockstar's code):
const char *p = catalogue; /* ~10 MB of JSON, one '\0' at the very end */
while (find_next_number(&p)) {
int value;
sscanf(p, "%d", &value); /* many C libraries call strlen(p) here: */
/* a walk to the end of the whole buffer */
p += digits_in(value);
}Each call is linear in what remains, and the number of calls grows with the size of the text, so the total is quadratic: double the catalogue and this loop does about four times the work. The video draws each call as a tape measure running to the far end, and the runs stack into a triangle.
The obvious fixes all remove the repeated measurement: parse with a function that stops at the end of the value (strtol reports where it stopped), track the buffer's end once and pass lengths around, or use a JSON library built for large inputs. His proof of concept was blunter: it remembered the start and end of the one long string the first time strlen measured it, and answered later calls inside that range with end - p.
Bug two: checking for duplicates in a list that had none
The second hot spot ran right after each item was parsed. Items were stored in an array of entries, each holding a hash of the item's key and a pointer to the item. Before storing a new item, the code walked the whole array comparing hashes, to make sure the item was not already there.
The first item checks nothing, the hundredth checks 99, and the last checks about 63,000. t0st's arithmetic for about 63,000 items is (n² + n) / 2, which comes to 1,984,531,500 comparisons. Two details made it pure waste: the array was empty before loading began, and every item in the catalogue was unique, so no comparison could ever find a match. The code even had a separate function that inserts without checking.
The usual fixes: keep the entries in a hash map so a lookup is one probe instead of a walk, or, when the input is known to be unique, insert directly. That second option is what his proof of concept did.
The fix, and what the numbers measure
His measurements, all on his own PC, timed from the Rockstar logo to being in the game:
- Original online load: about 6:00
- Only the duplicate check removed: 4:30
- Only the
strlenmeasurement cached: 2:50 - Both: 1:50, which he computes as a 69.4% improvement
Those are single runs on one older machine. Faster PCs he found were already loading in about two minutes, and he wrote that the fix "won't solve everyone's load times", since other systems might have other bottlenecks.
Rockstar's statement to PC Gamer said t0st "did, in fact, reveal an aspect of the game code related to load times for the PC version of GTA Online that could be improved". It did not describe the mechanism or publish numbers, and no source says Rockstar used his patch; it shipped its own changes in the 16 March 2021 title update, whose notes thank t0st "for his contributions around this part of today's title update". t0st reports Rockstar paid him $10,000 through its bug bounty programme "as an exception", since bounties usually cover security issues, and Rockstar confirmed to PC Gamer that the payment was being made. On the same hardware after the update, his stopwatch read 1:54.88. PC Gamer timed its own PC at 3 minutes 15 seconds before the update and 2 minutes after.
How to spot the same pattern in your own code
Both bugs are a step that looks constant-time but scans the whole input, sitting inside a loop over that same input. Joel Spolsky called this Shlemiel the painter's algorithm in 2001, after a painter who walks back to the paint can for every dash he paints; a long-running blog collects real cases under the name accidentally quadratic. Common hiding places:
strlen,strcatorsscanfon a long C buffer inside a looplist.contains,array.includes,indexOforx in some_listinside a loop; a set or map turns each check into one lookup- building a string with repeated concatenation where the language copies on every append
- a "check it isn't already there" step before every insert, on data that is unique by construction
Two habits catch it. Time the operation at one size and at double the size: linear work roughly doubles, quadratic roughly quadruples. And when something takes minutes that should take seconds, profile before guessing, the way t0st did: look at what is busy, sample where the time goes, then read only that code. Sampling profilers exist for most stacks (perf on Linux, Instruments on macOS, py-spy for Python, the performance panel in browser dev tools).
Terms
- C string
- A sequence of bytes ending in a zero byte (
'\0'). It carries no length, so finding the length means walking to the zero. - strlen
- The C standard library function that returns a C string's length by counting bytes up to the terminating zero. Its cost grows with the length of the string.
- sscanf
- The C function that reads formatted values, such as an integer with
"%d", out of a string. Many implementations measure the input withstrlenbefore parsing. - Sampling profiler
- A profiler that pauses a running program at intervals and records where it is. The functions that appear in the most samples are where the time goes. It needs no source code.
- Instruction pointer
- The CPU register holding the address of the next instruction to run. A sampling profiler records it, which is why it can work on a program with no symbols.
- Quadratic time, O(n²)
- Work that grows with the square of the input size: twice the input, about four times the work. A linear step inside a linear loop is the usual cause.
- Accidentally quadratic
- An informal name for code that is quadratic by mistake, usually because a cheap-looking call scans the whole input.
- Hash map
- A structure that finds an entry by its key in roughly constant time, instead of comparing against every entry.
- Obfuscation
- Transforming shipped code so it is hard to read. In this case the code had to be read from memory while the game ran.
- Bug bounty
- A programme that pays outsiders for reporting problems, usually security flaws. Rockstar paid t0st through its programme as an exception.
Questions
Why did GTA Online take so long to load on PC?
In 2021 most of the wait on slower PCs was two pieces of single-threaded code parsing a 10 MB JSON shop catalogue: sscanf measured the rest of the text with strlen before every number, and each of about 63,000 items was compared with every item stored before it. On t0st's PC that was about four of the six minutes.
Was it the servers or the network?
No. Once the common loading finished, network traffic dropped to almost nothing while one CPU core stayed maxed. The time was spent computing in the game's own code.
Did Rockstar use t0st's fix?
Rockstar confirmed he had found code related to PC load times that could be improved, shipped its own changes on 16 March 2021, thanked him in the patch notes and paid him $10,000. No source says it used his patch, and it did not describe its changes.
How much faster did GTA Online load after the fix?
On t0st's PC: about 6:00 before, 1:50 with his proof of concept, and 1:54.88 after Rockstar's update. PC Gamer measured 3:15 before and 2:00 after on its own PC. Results depended on the machine.
Does GTA 6 have the same loading bug?
Nothing public says so. This was a 2021 bug in the PC version of GTA Online. GTA 6 is listed for 19 November 2026 on PS5 and Xbox Series X|S, with no PC date announced.
Is sscanf slow?
Not on its own. Measuring a short string is cheap. It becomes a problem when it is called over and over on a pointer into one very long buffer, because each call measures everything that remains.
// further reading
- How I cut GTA Online loading times by 70% ↗: t0st's full write-up, with the profiler output, the timings and the two updates
- Rockstar thanks GTA Online player who fixed poor load times ↗: PC Gamer, 15 March 2021, with Rockstar's statement and the bounty
- GTA Online load time fix released ↗: PC Gamer, 16 March 2021, with the patch notes and its own before and after timings
- Back to Basics ↗: Joel Spolsky on C strings, `strcat` and Shlemiel the painter's algorithm
// transcript
GTA Online could take six minutes to load, and it wasn't the servers. With GTA 6 out this month, here's what it really was, and how a PC player called t0st found it in 2021 without the source code, a find Rockstar paid $10,000 for. Task Manager gave the first clue, because after a minute of normal loading, the disk, network and GPU went quiet while one CPU core ran flat out for four more minutes, so the game wasn't waiting on anything, it was busy in its own code. To find where, t0st used a sampling profiler, which pauses a running program over and over and takes a snapshot of the instruction it's on. Count the snapshots and the busiest code stands out, source or no source, and nearly all of them landed in two places. The first was inside the C call that read each number out of the game's shop catalogue, 10 MB of JSON with about 63,000 items. That call starts by measuring the string it's handed, and a C string doesn't store its length, so measuring means walking every character to the zero byte at the end. The game handed it the text from the next number onward, so every number, just a few bytes long, cost a walk through the whole rest of the catalogue. The second was where each item went into a list, and before adding one, the game compared it with every item already stored, to rule out a duplicate, so the first item checks nothing, the hundredth checks ninety-nine, and by the last, it's almost two billion comparisons, in a catalogue with no duplicates at all. Both are the same mistake, one pass over all the data hidden inside a loop over all the data, which makes it quadratic, so twice the data takes four times as long. To prove it, t0st made the game measure the text once and skip the check, and on that PC the load fell from six minutes to one minute fifty. Two weeks later, Rockstar shipped its own fix and thanked t0st in the patch notes. It was never the servers, just two small loops that each looked too cheap to be the problem.
// source
This explainer is based on How I cut GTA Online loading times by 70% by t0st ↗. The original reporting and technical work belong to its publisher.