<!-- fivemad.com, Markdown source of https://fivemad.com/docs/development/threads-performance -->
> Source: https://fivemad.com/docs/development/threads-performance
> Section: Development
> Tags: performance, threads, CreateThread, Wait, resmon, profiler, optimization, ms
> Last updated: 2026-09-22
> Site map for assistants: https://fivemad.com/llms.txt

# Threads and performance

Write FiveM scripts that don't eat frames: CreateThread and Wait, dynamic waits, distance checks, ox_lib points, resmon, the profiler and reading its results.

Every client script shares the same game frame with GTA itself and every other resource. A resource that uses 1 ms per frame on a 60 FPS client is taking 6% of the frame budget. Ten of those and players feel it. This page shows how loops work, the patterns that keep them cheap, and how to measure.

[FiveM Lua Scripting Threads and Controls (CharlesHacks, Zero to Hero Ep. 5)](https://www.youtube.com/watch?v=9Mg-AO-YtUA) (YouTube video)

## Threads and Wait

Lua in FiveM uses coroutines. `CreateThread` starts one, and `Wait(ms)` pauses it and gives control back to the game:

```lua
CreateThread(function()
    while true do
        -- do something
        Wait(1000) -- sleep one second
    end
end)
```

- `Wait(0)` means "run again next frame". Needed for things drawn or checked every frame (markers, text, disabling controls).
- A `while true` loop **without** a `Wait` freezes the game (client) or the server (server side). txAdmin will eventually restart a server stuck like this.
- `Wait` only works inside a thread or an event/command handler, which already run as coroutines.
- `SetTimeout(ms, fn)` runs a function once after a delay, without a loop.

## The classic mistake

```lua title="client.lua (slow)"
CreateThread(function()
    while true do
        Wait(0)
        local pos = GetEntityCoords(PlayerPedId())
        for _, shop in ipairs(Config.Shops) do            -- 40 shops
            if #(pos - shop.coords) < 20.0 then
                DrawMarker(2, shop.coords.x, shop.coords.y, shop.coords.z, 0,0,0, 0,0,0, 0.3,0.3,0.3, 255,255,255,150, false,true,2, nil,nil,false)
            end
        end
    end
end)
```

It checks 40 distances **every frame** even when you're on the other side of the map. Multiply by every resource written like this.

## Pattern 1: dynamic wait

Sleep long when nothing is near, go per frame only when needed:

```lua title="client.lua"
CreateThread(function()
    while true do
        local sleep = 1000
        local pos = GetEntityCoords(PlayerPedId())

        for _, shop in ipairs(Config.Shops) do
            local dist = #(pos - shop.coords)
            if dist < 20.0 then
                sleep = 0
                DrawMarker(2, shop.coords.x, shop.coords.y, shop.coords.z, 0,0,0, 0,0,0, 0.3,0.3,0.3, 255,255,255,150, false,true,2, nil,nil,false)
                if dist < 1.5 and IsControlJustPressed(0, 38) then -- E
                    openShop(shop)
                end
            end
        end

        Wait(sleep)
    end
end)
```

Far from every shop, the loop runs once a second. That's usually a 20 to 50x cut in cost.

## Pattern 2: ox_lib points and zones

ox_lib does the distance bookkeeping for you and only calls your code near the point:

```lua title="client.lua"
for _, shop in ipairs(Config.Shops) do
    lib.points.new({
        coords = shop.coords,
        distance = 20,
        nearby = function(self)
            -- runs every frame only while within 20 units
            DrawMarker(2, self.coords.x, self.coords.y, self.coords.z, 0,0,0, 0,0,0, 0.3,0.3,0.3, 255,255,255,150, false,true,2, nil,nil,false)
            if self.currentDistance < 1.5 and IsControlJustPressed(0, 38) then
                openShop(shop)
            end
        end,
    })
end
```

`lib.zones` does the same for boxes, spheres and polygons with `onEnter` / `onExit`. Interaction resources like **ox_target** avoid loops entirely: players aim at something and pick an option.

## Pattern 3: events instead of polling

Don't check "is the player in a vehicle?" every frame if you can react to a change. ox_lib's `cache` updates values and fires events when they change:

```lua
lib.onCache('vehicle', function(vehicle)
    if vehicle then
        print('Entered vehicle', vehicle)
    else
        print('Left vehicle')
    end
end)
```

On the server, react to `playerDropped`, `onResourceStop`, state bag change handlers (see [State bags](/docs/development/state-bags-onesync)) instead of loops.

## More habits that matter

- **Cache what doesn't change within a loop**: call `PlayerPedId()` once per iteration, not five times. (ox_lib's `cache.ped` keeps it for you.)
- **Use vector math**: `#(a - b)` is faster and simpler than `GetDistanceBetweenCoords`.
- **Don't create entities, blips or threads in loops** without cleaning them up.
- **Avoid string building and table creation every frame**: they create garbage for the Lua GC.
- **Server side, never block**: use async database calls (`MySQL.query` with a callback, or `.await` inside a thread), not busy waits.
- **Don't send events every frame.** Send on change, or at most a few times per second, and batch.
- **Unload what you load**: models, anim dicts, ptfx assets, scaleforms.

## Measuring: resmon

Press <kbd>F8</kbd> and type:

```text
resmon 1
```

The resource monitor shows each client resource's **CPU time in ms per frame** and memory. Rough guide:

| ms | Meaning |
| --- | --- |
| `0.00` to `0.02` | Idle, great. |
| `0.02` to `0.10` | Fine for most resources. |
| `0.10` to `0.50` | Worth a look, especially if it's constant while nothing happens. |
| `> 0.50` constant | Something runs every frame that shouldn't. |

Test in the situations that matter: standing still, driving, near the resource's zone, with the UI open. A HUD or a target system will naturally use a bit more. On the server, txAdmin's performance chart shows tick times, and the server console prints `hitch` warnings when a tick takes too long.

## Measuring: the profiler

For "which function exactly?", use the built in profiler. It works on the client (F8) and the server console:

```text
profiler record 500
profiler view
```

- `profiler record 500` records 500 frames (the docs recommend 500).
- `profiler status` shows whether it's still recording.
- `profiler view` opens the result in Chrome's performance view (on the server, it prints a link you open yourself).
- `profiler saveJSON myprofile.json` saves it to a file.

In the view, the green graph is FPS and the yellow one CPU time. Spikes are hitches. Hover a frame to see which resource and which line took the time.

To skip reading flame charts, drop the saved JSON into the [Profiler Analyzer](/tools/profiler-analyzer): it shows which resources and threads use your frame or tick time and suggests fixes.

[Fivem Resmon & Event Log Tutorial (Lith Studios)](https://www.youtube.com/watch?v=yTSm2ba4RBk) (YouTube video)

## Server performance

- **Tick time**: the server runs its main loop on one thread. Long running Lua on the server delays everything, including sync.
- **Database**: slow queries show up as `mysql_slow_query_warning` messages when you set that convar. Add indexes to columns you search by (identifier, citizenid, plate).
- **Entities**: thousands of networked objects hurt everyone. Clean up what your scripts spawn.
- **Scheduled restarts** hide memory leaks but don't fix them. Find them with resmon's memory column and the profiler.

## Assets are performance too

A perfectly coded server still stutters if it streams 4K textures on every car. Oversized `.ytd` files cause texture loss and memory pressure. See [Streaming custom assets](/docs/development/streaming-assets#keep-it-light) and the [YTD Optimizer](/tools/ytd-optimizer).

Next: [State bags and OneSync](/docs/development/state-bags-onesync).
