<!-- fivemad.com, Markdown source of https://fivemad.com/docs/code-optimisation/natives -->
> Source: https://fivemad.com/docs/code-optimisation/natives
> Section: Code optimisation
> Tags: natives, joaat, GetHashKey, PlayerPedId, vector3, distance, performance, cfxlua
> Last updated: 2026-09-25
> Site map for assistants: https://fivemad.com/llms.txt

# Cheap native calls

Backtick hashes, joaat, vector distance instead of GetDistanceBetweenCoords, caching ped handles safely, and keeping per frame natives behind a gate.

A native call leaves the Lua interpreter, crosses into the game or the server core, and comes back. It is not expensive on its own, and it is not free either. In a loop that runs every frame the number of native calls per pass is usually the biggest thing you control.

This page is about the cost. For what natives are and how to find them, see [Natives: talking to the game](/docs/development/natives).

## Hash strings at compile time

GTA identifies models, weapons, anim sets and much more by a 32 bit joaat hash of the name. You have three ways to produce one, and they are not equal:

```lua
local a = GetHashKey('adder')   -- a native call, at runtime, every time
local b = joaat('adder')        -- a CfxLua function, at runtime, no native call
local c = `adder`               -- hashed while the script compiles, nothing at runtime
```

All three give the same number. The backtick form is the one to use for a name you type in the source: the Cfx docs describe it as compile time generation of hash keys with zero runtime overhead, so `` `adder` `` is a literal number by the time the script runs.

When the name comes from a variable you cannot use backticks, and then `joaat(name)` is the right call. It hashes in the Lua runtime instead of going out to a native. Note that `joaat` lowercases the input before hashing, which is also why spawn names work in any case.

```lua
-- In a loop, over a config full of names
for i = 1, #Config.Blacklist do
    blacklist[joaat(Config.Blacklist[i])] = true   -- hashed once, at load
end

local function isBlacklisted(model)
    return blacklist[model] == true                 -- no hashing per call
end
```

`GetHashKey` is not broken and thousands of resources use it. There is just no case left where it is the best of the three.

## Distance without a native

Vectors are real types in CfxLua, and `#v` is the magnitude of a vector, so the distance between two points is one operator:

```lua
-- A native call with seven arguments
local d = GetDistanceBetweenCoords(x1, y1, z1, x2, y2, z2, true)

-- Vector maths in the VM
local d = #(coordsA - coordsB)
```

Most position natives already hand you a `vector3`, so the second form usually saves you unpacking as well. When you are only comparing against a radius and do not need the number itself, you can skip the square root by comparing the squared lengths:

```lua
local diff = coordsA - coordsB
if (diff.x * diff.x + diff.y * diff.y + diff.z * diff.z) < (20.0 * 20.0) then
```

That is worth it over a few thousand comparisons and not worth the loss of clarity over forty. Use `#(a - b) < 20.0` unless you have measured a reason not to.

For a flat check that ignores height, build the difference and leave `z` out of the sum, or compare `vector2(a.x, a.y)` against `vector2(b.x, b.y)`.

## The local ped

```lua
local ped = GetPlayerPed(-1)   -- old, magic number
local ped = PlayerPedId()      -- the current way
```

Both are natives. The important part is not which one you call, it is how often. Inside a loop, call it once per pass and reuse the handle:

```lua
CreateThread(function()
    while true do
        local ped = PlayerPedId()          -- once per pass
        local pos = GetEntityCoords(ped)   -- once per pass
        ...
        Wait(sleep)
    end
end)
```

> [!WARNING]
> Do not cache a ped handle forever. The handle changes when the player respawns or the model is switched, and code holding the old one will silently do nothing. Cache it for the body of a loop pass, or use `ox_lib`'s `cache.ped`, which refreshes itself when the ped changes.

The same applies to any handle: vehicles, objects and blips are local numbers that can become invalid. Check with `DoesEntityExist(entity)` before you use one you stored.

## Drop the `Citizen.` prefix

`Wait`, `CreateThread`, `SetTimeout` and `ClearTimeout` are set up as plain global aliases of the `Citizen.*` functions in the FiveM Lua scheduler. `Wait` **is** `Citizen.Wait`, the same function object.

```lua
Citizen.CreateThread(function()
    Citizen.Wait(1000)
end)

CreateThread(function()
    Wait(1000)
end)
```

So this is a readability change, not a speed one. The prefixed form costs one extra table lookup per call and nothing else, which is nothing you will ever measure. Write the short form because every current example does, and be suspicious of a tutorial that still uses the long one: it is usually old enough that other things in it are out of date too.

## Per frame natives belong behind a gate

Some natives only do anything for the frame you call them in. `DrawMarker`, `DrawText`, `DisableControlAction`, `HideHudComponentThisFrame` and friends all have to run every frame to stay on screen, which means a `Wait(0)` loop, which means you must make sure that loop is not running when there is nothing to draw:

```lua
CreateThread(function()
    while true do
        local sleep = 500
        if nearMarker then
            sleep = 0
            DrawMarker(...)
        end
        Wait(sleep)
    end
end)
```

That pattern, and the `ox_lib` points and zones that replace it, are covered in [Threads and performance](/docs/development/threads-performance).

## Hoist what cannot change

Anything constant for the life of the resource should be read once, not per frame:

```lua
-- Called every frame, always the same answer
CreateThread(function()
    while true do
        Wait(0)
        if GetCurrentResourceName() == 'my_resource' then ... end
    end
end)

-- Read once at load
local RESOURCE <const> = GetCurrentResourceName()
```

Convars, screen resolution, config values and model hashes all fall in this group. Resolution is the one exception worth a note: it can change when the player alt tabs or edits settings, so read it on a timer or on the relevant event rather than every frame.

## Load and release streamed assets

Models, anim dicts, particle assets, texture dicts and scaleforms have to be requested and waited for. Always put a timeout on the wait so a missing asset does not hang a thread forever, and release what you loaded:

```lua
local function loadModel(model)
    local hash = type(model) == 'number' and model or joaat(model)
    if not IsModelInCdimage(hash) then return nil end

    RequestModel(hash)
    local deadline = GetGameTimer() + 5000
    while not HasModelLoaded(hash) do
        if GetGameTimer() > deadline then return nil end
        Wait(0)
    end
    return hash
end

-- when you are done
SetModelAsNoLongerNeeded(hash)
```

`ox_lib` ships `lib.requestModel`, `lib.requestAnimDict` and `lib.requestNamedPtfxAsset` with the timeout already handled.

Next: [Events that stay cheap and safe](/docs/code-optimisation/events).
