FiveM Resmon สูงแก้อย่างไร ลด Script กิน CPU และ Memory

FiveM Resmon สูง หมายถึง Resource ฝั่ง Client ใช้ CPU Time หรือ Memory มากกว่าที่ควรในสถานการณ์นั้น วิธีแก้ที่ถูกต้องไม่ใช่ใส่ Wait(1000) ลงทุก Thread แต่ต้องหาก่อนว่า Resource ใช้เวลาที่ส่วนใด จากนั้นลดงานที่ไม่จำเป็น เช่น Loop ทุก Frame, Native Calls ซ้ำ, Entity Scan, Event ที่ Trigger ถี่, NUI Updates, Database Requests หรือ Cache ที่ไม่ถูก Cleanup

เปิด Resource Monitor ได้ด้วย

resmon true

และปิดด้วย

resmon false

เมื่อพบ Resource ที่มีค่าผิดปกติ ควรใช้ Profiler ต่อ

profiler record 500

แล้ว

profiler view

Workflow ที่แนะนำคือ

Resmon
↓
หา Resource ที่น่าสงสัย
↓
Profiler
↓
หา Thread / Function / File / Line
↓
แก้ Bottleneck
↓
ทดสอบ Scenario เดิม
↓
วัดอีกครั้ง

อย่า Optimize จากชื่อ Resource หรือความรู้สึกเพียงอย่างเดียว

① FiveM Resmon คืออะไร

resmon หรือ Resource Monitor เป็นเครื่องมือของ FiveM สำหรับดู Resource Performance ฝั่ง Client

ข้อมูลสำคัญที่แสดง ได้แก่

Resource
CPU Usage / Time
Memory Usage

จึงช่วยตอบคำถามแรกว่า

Resource ไหนควรตรวจต่อ?

ก่อนใช้ Profiler เจาะลงไปใน Code

② Resmon สูงหมายความว่า Script แย่เสมอไหม

ไม่

Resource อาจใช้ CPU สูงชั่วคราวตอน

Initialization
เปิด Menu
Spawn Entity
โหลดข้อมูล
Player Spawn
เปลี่ยน Character

แล้วกลับมาต่ำ

ดังนั้นต้องดู

ค่าต่อเนื่อง
+
Spike
+
สถานการณ์ที่เกิด

ไม่ควรตัดสินจาก Screenshot เพียงช่วงเดียว

③ มีค่า ms เท่าไรถึงเรียกว่าสูง

ไม่มีตัวเลขเดียวที่ใช้ตัดสิน Resource ทุกประเภทได้

Resource ที่ทำ HUD, Rendering หรือ Interaction แบบ Real-time ย่อมมี Workload ต่างจาก Resource ที่ทำงานเฉพาะตอน Player กด Command

สิ่งสำคัญคือเปรียบเทียบ

Idle
vs
ใช้งาน Feature

Before
vs
After Optimization

และดูผลต่อ FPS จริง

④ วิธีเปิด Resmon

เปิด F8 แล้วพิมพ์

resmon true

จะเห็น Resource Monitor

เมื่อไม่ใช้แล้ว

resmon false

ได้

⑤ ถ้า resmon ขึ้น Access denied

Cfx.re ระบุว่า Production Build บางกรณีอาจแสดง

Access denied for command resmon

และ Development Environment อาจต้องเปิด Developer Mode ตามวิธีที่ Cfx.re กำหนด

ไม่ควรเปลี่ยน Production Configuration แบบสุ่มเพียงเพื่อเปิด Debug Tool

⑥ Resmon ใช้ฝั่ง Server หรือ Client

เอกสาร Cfx.re ปัจจุบันอธิบาย resmon เป็น Client Resource Monitor

สำหรับ Server Performance ให้ใช้เครื่องมืออย่าง

Server Profiler
txAdmin Monitoring
FXServer Console

แทนหรือใช้ร่วมกัน

⑦ FxDK มี Resource Monitor ไหม

มี

FxDK ปัจจุบันมี Resource Monitor ที่รวมข้อมูล Game และ Server Resources ใน Workspace

จึงสะดวกสำหรับ Developer ที่กำลังเขียน Resource ใน FxDK

⑧ Resmon กับ Profiler ต่างกันอย่างไร

จำง่าย ๆ

Resmon
→ ใครกิน?
Profiler
→ กินตรงไหน?

เช่น

Resmon
↓
my_garage สูง
↓
Profiler
↓
client/main.lua
↓
Thread ตรวจ Garage
↓
Loop บรรทัดหนึ่ง

นี่คือ Workflow ที่ควรใช้จริง

⑨ อย่าเริ่มจากการใส่ Wait

วิธีที่พบกันบ่อยคือ

Resmon สูง
↓
เปลี่ยน Wait(0)
เป็น Wait(1000)

โดยไม่ดูว่า Thread ทำอะไร

อาจทำให้ CPU ลดลงจริง แต่ Gameplay พัง เช่น

Marker ตอบสนองช้า
Key input ไม่ทัน
Speedometer กระตุก
Interaction Delay

ต้องลด Work โดยไม่ทำลาย Requirement ของ Feature

⑩ Wait(0) คืออะไร

ใน CfxLua

Wait(0)

Yield Coroutine จนถึง Game Tick ถัดไป

ดังนั้น Loop แบบ

CreateThread(function()

    while true do

        Wait(0)

        -- work

    end
end)

คือ Work ที่เกิดทุก Tick

ถ้างานด้านในหนัก Resmon สามารถสูงได้

⑪ Loop ไม่มี Wait อันตรายมาก

ห้ามทำ

CreateThread(function()

    while true do

        DoSomething()

    end
end)

Cfx.re เตือนว่า while true ที่ไม่มี Wait สามารถทำให้ Client ค้างหรือ Crash ได้

Loop ต่อเนื่องต้องมี Yield

⑫ Wait(0) ใช้เมื่อจำเป็น

งานบางอย่างเหมาะกับทุก Frame เช่น

Draw interaction
Control disabling
Camera updates
บาง Rendering Logic

แต่สิ่งเหล่านี้ไม่ควรทำทุก Frame โดยอัตโนมัติ

Database query
Job check
Money check
หา Garage ทั้ง Server
ค้น Vehicle ทุกคัน

⑬ Adaptive Sleep คืออะไร

เป็น Pattern ที่ปรับ Wait ตาม State

ตัวอย่าง

CreateThread(function()

    while true do

        local sleep =
            1000

        local ped =
            PlayerPedId()

        local coords =
            GetEntityCoords(
                ped
            )

        if IsNearInteraction(
            coords
        ) then

            sleep =
                0

            DrawInteraction()
        end

        Wait(
            sleep
        )
    end
end)

Player อยู่ไกล

Wait 1000

Player อยู่ใกล้จึงใช้

Wait 0

ช่วยลด Work ขณะ Feature ไม่จำเป็นต้องทำงานละเอียด

⑭ ปัญหา Adaptive Sleep ที่พบบ่อย

อย่าลืม Reset ค่า Sleep ในแต่ละ Loop

ตัวอย่างที่เสี่ยง

local sleep =
    1000

CreateThread(function()

    while true do

        if near then
            sleep = 0
        end

        Wait(
            sleep
        )
    end
end)

เมื่อ sleep กลายเป็น 0 แล้วอาจไม่กลับเป็น 1000

ควรกำหนดค่าเริ่มต้นใหม่ในแต่ละ Iteration ตาม Logic

⑮ แยก Thread ตาม Frequency

อย่าเอาทุกอย่างไว้ใน

Wait(0)

Thread เดียว

สามารถแบ่ง

Every Frame
→ Rendering / input

100–250 ms
→ Vehicle status

500–1000 ms
→ Location/zone search

Event-driven
→ Job / money / inventory

ทำให้ Workload เหมาะกับ Requirement มากขึ้น

⑯ ตัวอย่าง Thread ที่หนักเกินไป

CreateThread(function()

    while true do

        Wait(0)

        UpdateHud()

        CheckJob()

        ScanGarages()

        ScanVehicles()

        UpdateBlips()

        CheckInventory()

        SendServerState()

    end
end)

ทุกอย่างทำทุก Frame ทั้งที่หลายส่วนไม่จำเป็น

นี่เป็น Architecture ที่ควรแยก

⑰ Polling คืออะไร

Polling คือการตรวจข้อมูลซ้ำตามเวลา

เช่น

while true do

    Wait(500)

    local job =
        GetPlayerJob()

end

ทั้งที่ Job เปลี่ยนไม่บ่อย

ถ้า Framework มี Event ตอน Job เปลี่ยน ควรพิจารณา Event-driven

⑱ Event-driven ลด Resmon ได้อย่างไร

แทน

ทุก 500 ms
→ Job เปลี่ยนหรือยัง?

ใช้

Job เปลี่ยน
→ Event
→ Update

จำนวน Work จะสัมพันธ์กับการเปลี่ยนข้อมูลจริง

เหมาะกับ

Job
Money
Inventory
Player loaded
Vehicle spawned
Permission state

ตาม API ที่ Framework มี

⑲ Event ไม่ได้ฟรี

Event-driven ไม่ได้แปลว่า Trigger Event ถี่แค่ไหนก็ได้

ถ้าทำ

TriggerServerEvent(
    'player:updatePosition',
    coords
)

ทุก Frame

Client 200 คนสามารถสร้าง Network/Event Work จำนวนมาก

ต้องลด Frequency และ Payload ด้วย

⑳ TriggerServerEvent ทุก Frame ไม่ควร

โดยทั่วไปหลีกเลี่ยง

60 FPS
×
100 Players
=
6,000 requests ต่อวินาที

เพียงจาก Event หนึ่งประเภทในตัวอย่างสมมติ

ควรใช้

Event on change
Throttle
Batch
State synchronization ที่เหมาะสม
Server-side calculation

ตาม Use Case

㉑ Native Calls ทำให้ Resmon สูงได้ไหม

ได้เมื่อเรียกจำนวนมาก

เช่น Thread เดียวทำ

PlayerPedId()
GetEntityCoords()
GetEntityHealth()
GetEntitySpeed()
GetVehiclePedIsIn()

หลายครั้งต่อ Frame

Native Call แต่ละตัวอาจเล็ก แต่ Cost รวมจากหลาย Resources สามารถเพิ่มได้

㉒ Cache Native Result ในรอบเดียว

ไม่ควรเรียก

GetEntityCoords(
    PlayerPedId()
)

ซ้ำ 10 จุดใน Loop เดียวหากใช้ข้อมูลเดียวกัน

ใช้

local ped =
    PlayerPedId()

local coords =
    GetEntityCoords(
        ped
    )

แล้ว Reuse

แต่ต้องไม่ Cache Handle นานเกิน Lifetime ที่มันเชื่อถือได้

㉓ PlayerPedId ควร Cache ถาวรไหม

ไม่แนะนำให้สมมติว่า Player Ped ไม่เปลี่ยนตลอด Session

Player Ped สามารถเปลี่ยนจาก Gameplay/Respawn/Model Change

Cache ภายในรอบหรือช่วงที่เหมาะสมได้ แต่ต้อง Refresh ตาม Lifecycle

㉔ Entity Scan คือหนึ่งในตัวการสำคัญ

Resource ที่ Scan

Vehicles ทั้งหมด
Peds ทั้งหมด
Objects ทั้งหมด

บ่อย ๆ สามารถสร้าง Work สูงเมื่อ Entity จำนวนมาก

ยิ่ง Server ใช้ OneSync และมีระบบ Entity เยอะ ต้องระวัง Scaling

㉕ อย่า Scan Vehicle ทั้งหมดเพื่อหา Plate หนึ่งคัน

ถ้า Resource สร้าง/รู้จัก Vehicle อยู่แล้ว ให้ Track

Database ID
Network ID
Entity Handle

ตาม Context

แทน

GetGamePool vehicles
↓
Loop ทุกคัน
↓
Get plate
↓
เทียบ

ซ้ำ ๆ

㉖ Nested Loop อันตรายอย่างไร

ตัวอย่าง

for i = 1, #Garages do

    for j = 1, #Vehicles do

        Check(
            Garages[i],
            Vehicles[j]
        )

    end
end

ถ้ามี

100 Garages
500 Vehicles

คือ

50,000 comparisons

ต่อการรันหนึ่งรอบ

ถ้ารันทุก Frame Cost จะสูงมากขึ้นอย่างรวดเร็ว

㉗ Lookup Table ช่วยได้

แทนค้น Array

for i = 1, #Vehicles do

    if Vehicles[i].plate
        == plate then
        ...
    end
end

สร้าง Index ใน Memory

VehiclesByPlate[
    plate
] = vehicle

จากนั้น

local vehicle =
    VehiclesByPlate[
        plate
    ]

หาก Data Model เหมาะกับ Lookup แบบนี้

㉘ Table Sort ไม่ควรทำทุก Frame

ไม่ควร

while true do

    Wait(0)

    table.sort(
        players,
        sortFunction
    )

end

ถ้า List เปลี่ยนเฉพาะ Player Join/Leave

ควร Sort เมื่อ Data เปลี่ยน

ไม่ใช่ทุก Frame

㉙ JSON Encode/Decode ก็มี Cost

เช่น

Inventory 5,000 items
↓
json.encode
↓
ทุก 100 ms

สร้าง CPU และ Memory Allocation

ถ้าข้อมูลเปลี่ยนเพียง Slot เดียว ควรส่งเฉพาะส่วนที่เปลี่ยนเมื่อ Architecture รองรับ

㉚ NUI ทำให้ Resource สูงได้อย่างไร

NUI มี Work ทั้งสองฝั่ง

Client Script
+
CEF Browser

Client อาจ

SendNUIMessage ถี่
Serialize Payload ใหญ่

Browser อาจ

Render DOM ใหม่
Animate
Blur
Video
JavaScript loops

ดังนั้นต้องตรวจ NUI DevTools ด้วย ไม่ใช่ดู Resmon อย่างเดียว

㉛ อย่าส่ง SendNUIMessage ทุก Frameโดยไม่มีเหตุผล

ตัวอย่างไม่ดี

CreateThread(function()

    while true do

        Wait(0)

        SendNUIMessage({
            action = 'hud',
            cash = cash,
            job = job
        })

    end
end)

Cash กับ Job ไม่ได้เปลี่ยนทุก Frame

ควร Update ตอน Value เปลี่ยน

㉜ แยก Frequency ของ HUD

ตัวอย่าง

Speed
→ 100–200 ms หรือตาม UX

Cash
→ ตอนเปลี่ยน

Job
→ ตอนเปลี่ยน

Inventory
→ ตอนเปลี่ยน/เปิด UI

Server name
→ โหลดครั้งเดียว

ไม่จำเป็นต้องทุกข้อมูลอยู่ Thread 0 ms เดียวกัน

㉝ Full State กับ Delta Update

ตอนเปิด Inventory

ส่ง Inventory ทั้งหมด

เหมาะสม

แต่เมื่อ Slot 5 เปลี่ยน

ไม่จำเป็นต้องส่ง Inventory ทุก Slot ใหม่

ส่ง

SendNUIMessage({
    action = 'updateSlot',
    slot = 5,
    item = item
})

ช่วยลด Serialization และ DOM Work

㉞ Browser Timer ต้องหยุดเมื่อ UI ปิด

NUI ซ่อนด้วย

display: none;

ไม่ได้แปลว่า

setInterval(...)

หยุด

Frontend ควรมี Lifecycle

Open
→ Start timers

Close
→ Stop timers

โดยเฉพาะ UI หนัก เช่น Phone/Inventory

㉟ CSS ทำ FPS ตกได้ไหม

ได้

สิ่งที่ควรระวัง

Backdrop blur
Large box shadows
Continuous animations
Huge transparent layers
Video backgrounds
Canvas/WebGL

Resmon ของ Lua อาจดูดี แต่ GPU/CEF ยังหนัก

ใช้ NUI DevTools และ cl_drawperf ประกอบ

㊱ cl_drawperf ใช้ทำอะไร

เปิดด้วย

cl_drawperf true

Cfx.re ปัจจุบันระบุว่าจะช่วยแสดง Metrics เช่น

FPS
Ping
Packet Loss
CPU Usage
GPU Usage
GPU Temperature

มีประโยชน์สำหรับแยก Script CPU กับ GPU Bottleneck เบื้องต้น

㊲ ถ้า GPU 100% แต่ Resmon ต่ำ

อาจไม่ได้เป็น Lua Script Problem

ตรวจ

Graphics settings
Resolution
MLO
Textures
NUI effects
Shaders
GPU load

อย่าพยายามลด Wait() อย่างเดียวเมื่อ Bottleneck อยู่ GPU

㊳ Memory ใน Resmon คืออะไร

Resmon ยังแสดง Memory Usage ต่อ Resource

Resource ที่ Memory สูงผิดปกติอาจมี

Table โตไม่หยุด
Cache ไม่มี Limit
Data History สะสม
Large JSON
NUI Asset/Data
Event tracking
Entity references

ต้องดู Trend ไม่ใช่เพียงตัวเลขจุดเดียว

㊴ Memory สูงกับ Memory Leak ต่างกัน

Resource ใช้ Memory สูงอาจเป็นเรื่องปกติถ้ามี Data จำนวนมาก

Memory Leak คือ Pattern ที่ Memory เพิ่มต่อเนื่องโดยไม่คืนหรือ Cleanup ตาม Lifecycle

เช่น

100 MB
↓
150 MB
↓
250 MB
↓
500 MB

หลังใช้งาน Feature ซ้ำ ๆ

นี่น่าสงสัยกว่า Resource ที่คงที่ 150 MB

㊵ ตัวอย่าง Memory Leak จาก History

local History = {}

RegisterNetEvent(
    'garage:used',
    function(data)

        History[
            #History + 1
        ] = data
    end
)

ถ้า Event เกิดหลายล้านครั้งและไม่มี Cleanup

Table จะโตตลอด

ต้องถามว่า History จำเป็นต้องเก็บทั้งหมดจริงหรือไม่

㊶ จำกัด Cache Size

ตัวอย่าง Concept

local MAX_HISTORY =
    100

History[
    #History + 1
] = data

if #History > MAX_HISTORY then

    table.remove(
        History,
        1
    )
end

หรือใช้ Ring Buffer/Data Structure ที่เหมาะกว่าเมื่อ Performance สำคัญ

㊷ Cache Player ต้อง Cleanup ตอนออก

Server Resource อาจเก็บ

Players[
    source
] = data

เมื่อ Player Disconnect ต้อง Cleanup

AddEventHandler(
    'playerDropped',
    function()

        local src =
            source

        Players[
            src
        ] = nil
    end
)

ไม่เช่นนั้น Server Memory Table อาจสะสมข้อมูล Session เก่า

㊸ Entity Cache ก็ต้อง Cleanup

ถ้าเก็บ

Entities[
    entityId
] = metadata

เมื่อ Entity ถูกลบ Metadata ที่ไม่ใช้ต่อก็ควรถูกลบ

อย่าเก็บ Handle เก่าตลอด Runtime

㊹ Event Handlers ทำให้ Memory Leak ไหม

Runtime Handler ที่สร้าง Dynamic จำนวนมากโดยไม่มี Lifecycle ที่ดีอาจสร้างปัญหาได้

โดยเฉพาะระบบที่ Register Handler ใหม่ซ้ำทุกครั้งที่เปิด Feature โดยไม่จำเป็น

ส่วน Handler Static ที่ Register ครั้งเดียวตอน Resource Load โดยทั่วไปเป็น Pattern ปกติ

㊺ อย่า Register Command/Event ใน Loop

ไม่ควร

while true do

    Wait(1000)

    RegisterNetEvent(
        'my:event',
        handler
    )

end

Register Event/Command โดยทั่วไปทำตอน Resource Initialization

ไม่ใช่ทุก Tick

㊻ State Bags ช่วยลด Polling ได้ไหม

ได้ในบาง Use Case

แทน Polling State ซ้ำ ๆ สามารถใช้ State Bag Change Handler ตอบสนองเมื่อ State เปลี่ยน

เช่น

Entity state changed
↓
Change handler
↓
Update feature

แทน

ทุก 100 ms
→ ตรวจ state

แต่ State Bags ก็ต้องออกแบบ Payload และ Replication ให้เหมาะสม

㊼ OneSync scope events ต้องระวัง

Cfx.re ปัจจุบันเตือนว่า Event อย่าง

playerEnteredScope
playerLeftScope

มี Scaling Performance Cost เพราะ Event เพิ่มตามจำนวน Players ที่เข้า/ออก Scope

เอกสารแนะนำให้ใช้ State Bags เมื่อ Use Case เหมาะสม

ดังนั้นอย่าใช้ Scope Events แบบกว้างโดยไม่คิดเรื่อง Player Scale

㊽ Server-side Resmon สูงดูอย่างไร

ตัว resmon ที่กล่าวถึงใน Cfx.re Fact Sheet เน้น Client

Server ให้ดู

txAdmin
Profiler
Server hitch warnings
CPU/RAM
Thread performance

ถ้า Server CPU สูง ควร Profile ในช่วงที่เกิดปัญหา

㊾ txAdmin ช่วยอะไร

txAdmin ปัจจุบันมี Monitoring เช่น

Server CPU
RAM
Server Threads Performance
Player Count
Live Console

จึงช่วยหาว่า

CPU Spike เกิดตอนไหน
Player เท่าไร
มี Hitch หรือไม่

แล้วใช้ Profiler เจาะต่อ

㊿ Server Hitch Warning ควรคิดถึงอะไร

Cfx.re ระบุว่าสาเหตุที่พบได้ เช่น

SQL Query ช้า
Unoptimized loops

ดังนั้นถ้า Server Hitch

อย่ามองเฉพาะ Lua

ตรวจ

Database
Events
Loops
Mass player processing
Entity operations

ด้วย

51 Database Query ถี่เกินไปทำให้ Server ช้า

ตัวอย่าง

for _, player in pairs(
    GetPlayers()
) do

    MySQL.query.await(
        'SELECT * FROM users WHERE id = ?',
        {
            getPlayerId(
                player
            )
        }
    )

end

Player 200 คน

→ 200 Queries

ถ้ารันทุกวินาทีจะกลายเป็น Workload ใหญ่

ใช้ Cache/Batch/Event Architecture ตามความเหมาะสม

52 N+1 Query ต้องระวัง

Pattern

1 Query
โหลด Players

จากนั้น
1 Query ต่อ Player
โหลด Vehicles

100 Players

101 Queries

อาจเปลี่ยนเป็น Batch Query หรือ Query Architecture ที่เหมาะสมได้

บทความ Database ก่อนหน้าได้อธิบายเรื่องนี้แล้ว

53 Save Player ทุกคนพร้อมกันมี Spike

สมมติ

200 Players
×
5 Queries
=
1,000 Queries

พร้อมกันทุก 5 นาที

อาจสร้าง Server Load Spike

พิจารณา

Dirty-state save
Batching
Staggered saves
Transaction
ลด Query ต่อ Player

ตาม Framework Design

54 Dirty State คืออะไร

Track เฉพาะ Player/Data ที่มีการเปลี่ยน

DirtyPlayers[
    source
] = true

เมื่อ Save Cycle มาถึงก็ Save เฉพาะ State ที่ Dirty

แต่ต้องมี Reliability Strategy สำหรับ

Disconnect
Crash
Server shutdown

ด้วย

55 HTTP Requests ก็ทำให้ Resource หน่วงได้

ถ้า Resource เรียก External API บ่อย

Discord API
License API
Web API
Webhook

ควรหลีกเลี่ยง Polling ถี่เกินไป

และอย่าออกแบบให้ Gameplay Critical Loop ต้องรอ Remote API โดยไม่จำเป็น

56 Logging มากเกินไปมีผลไหม

ได้

เช่น

while true do

    Wait(0)

    print(
        'player coords',
        coords
    )
end

Console Logging ทุก Frameสร้างทั้ง CPU และ I/O Noise

Debug Log ควรถูกปิดหรือลด Frequency ใน Production

57 Debug Mode ไม่ควรเปิดตลอด

เช่น

mysql_debug
verbose NUI logs
entity logs
event logs

เหมาะกับ Debug

แต่ Production ควรเปิดเท่าที่จำเป็น

เพราะ Logging จำนวนมากสามารถสร้าง Performance และ Storage Cost

58 ใช้ Local Variable ช่วยได้ไหม

ใน Lua Hot Path การใช้ Local Variables มักเหมาะกว่า Global Lookups

เช่น

local playerPedId =
    PlayerPedId

อาจมีประโยชน์ในบาง Hot Loop

แต่ อย่า Micro-optimize ก่อนแก้ Algorithm

ลด

50,000 iterations

ให้เหลือ

100

มีผลมากกว่าประหยัด Lookup เล็กน้อยใน Code ที่ไม่ใช่ Bottleneck

59 Algorithm สำคัญกว่า Micro Optimization

ตัวอย่าง

แบบแรก

ทุก Frame
→ Scan 100 Garages
→ Scan 500 Vehicles

แบบสอง

ทุก 1 วินาที
→ หา Garage ที่ใกล้ที่สุด

ทุก Frame
→ ตรวจ Garage เดียวเมื่ออยู่ใกล้

การเปลี่ยน Architecture มักสร้างผลมากกว่าปรับ Syntax เล็กน้อย

⑥⓪ Checklist ลด FiveM Resmon สูง

ตรวจทั้งหมดนี้

① Resmon Resource ไหนสูง?
② สูงตลอดหรือ Spike?
③ เกิดตอนทำ Action ไหน?
④ Profile ช่วงนั้นหรือยัง?
⑤ Thread ไหนกินเวลามาก?
⑥ File/Line ไหน?
⑦ Loop มี Wait หรือไม่?
⑧ Wait(0) จำเป็นไหม?
⑨ ใช้ Adaptive Wait ได้ไหม?
⑩ แยก Frequency ได้ไหม?
⑪ Polling เปลี่ยนเป็น Event ได้ไหม?
⑫ Native ถูกเรียกซ้ำหรือไม่?
⑬ Cache Result ในรอบได้ไหม?
⑭ Entity Scan มากเกินไปไหม?
⑮ Nested Loop หรือไม่?
⑯ ใช้ Lookup Table ได้ไหม?
⑰ Sort ทุก Frame หรือไม่?
⑱ JSON Encode ใหญ่หรือถี่ไหม?
⑲ Event Trigger ถี่เกินไหม?
⑳ TriggerServerEvent ทุก Tick หรือไม่?
㉑ Payload Network ใหญ่ไหม?
㉒ SendNUIMessage ถี่ไหม?
㉓ NUI ส่ง Full State ทั้งชุดหรือไม่?
㉔ Browser DOM Render หนักไหม?
㉕ CSS Blur/Animation หนักไหม?
㉖ Browser Timer หยุดตอน UI ปิดไหม?
㉗ Memory เพิ่มต่อเนื่องไหม?
㉘ Cache มี Limit ไหม?
㉙ Player Cache Cleanup ไหม?
㉚ Entity Cache Cleanup ไหม?
㉛ Handler ถูก Register ซ้ำไหม?
㉜ Database Query อยู่ใน Loop ไหม?
㉝ N+1 Query หรือไม่?
㉞ Slow SQL หรือไม่?
㉟ Mass save เกิดพร้อมกันไหม?
㊱ Logging มากเกินไปไหม?
㊲ HTTP API ถูกเรียกถี่ไหม?
㊳ Server CPU/GPU bottleneck ถูกแยกหรือยัง?
㊴ Profile หลังแก้อีกครั้งหรือยัง?
㊵ Test Player Scale จริงหรือยัง?

ตัวอย่างแก้ Resmon สูงของ Garage

สมมติ Resource

com_garage

Resmon เพิ่มสูงเมื่อ Player อยู่ในเมือง

เปิด Code พบ

CreateThread(function()

    while true do

        Wait(0)

        local ped =
            PlayerPedId()

        local coords =
            GetEntityCoords(
                ped
            )

        for i = 1, #Garages do

            local distance =
                #(
                    coords
                    -
                    Garages[i].coords
                )

            if distance < 2.0 then

                DrawMarker(
                    -- ...
                )
            end
        end
    end
end)

ถ้ามี Garage 300 จุด

300 distance calculations
×
ทุก Frame

ตลอดเวลา

ปรับ Garage เป็น Adaptive Logic

local nearestGarage =
    nil

CreateThread(function()

    while true do

        Wait(1000)

        local ped =
            PlayerPedId()

        local coords =
            GetEntityCoords(
                ped
            )

        local closest =
            nil

        local closestDistance =
            math.huge

        for i = 1, #Garages do

            local distance =
                #(
                    coords
                    -
                    Garages[i].coords
                )

            if distance
                < closestDistance then

                closestDistance =
                    distance

                closest =
                    Garages[i]
            end
        end

        nearestGarage =
            closest
    end
end)

จากนั้น Render Thread

CreateThread(function()

    while true do

        local sleep =
            1000

        local garage =
            nearestGarage

        if garage then

            local ped =
                PlayerPedId()

            local coords =
                GetEntityCoords(
                    ped
                )

            local distance =
                #(
                    coords
                    -
                    garage.coords
                )

            if distance < 20.0 then

                sleep =
                    0

                DrawMarker(
                    -- ...
                )
            end
        end

        Wait(
            sleep
        )
    end
end)

ตอนนี้

Scan Garage ทั้งหมด
→ ทุก 1 วินาที

แต่

Draw ทุก Frame
→ เฉพาะ Garage ใกล้ที่สุด

Architecture เบาลงมากโดยยังตอบสนองได้ดี

ถ้า Garage มีหลายพันจุดทำอย่างไร

การ Scan ทั้งหมดทุก 1 วินาทีก็อาจเริ่มไม่เหมาะ

สามารถใช้

Grid
Zone system
Spatial partitioning
Buckets by area
Nearest indexes

เพื่อจำกัด Candidates ก่อน Distance Calculation

Optimization ต้อง Scale ตามจำนวนข้อมูล

ตัวอย่างลด NUI HUD Resmon

แบบเดิม

CreateThread(function()

    while true do

        Wait(0)

        SendNUIMessage({
            action = 'hud',

            cash =
                GetCash(),

            job =
                GetJob(),

            hunger =
                GetHunger()
        })
    end
end)

เปลี่ยนเป็น Event-driven

RegisterNetEvent(
    'player:moneyChanged',
    function(cash)

        SendNUIMessage({
            action =
                'cash',

            value =
                cash
        })
    end
)

Job

RegisterNetEvent(
    'player:jobChanged',
    function(job)

        SendNUIMessage({
            action =
                'job',

            value =
                job
        })
    end
)

Hunger อาจ Update ในช่วงเวลาที่เหมาะสม

แทนส่งทุก Frame

ตัวอย่างลด Network Event

แบบเดิม

CreateThread(function()

    while true do

        Wait(0)

        TriggerServerEvent(
            'player:coords',
            GetEntityCoords(
                PlayerPedId()
            )
        )
    end
end)

ไม่เหมาะสำหรับข้อมูลที่ Server ไม่จำเป็นต้องได้รับทุก Tick

หาก Server ต้องการ Position ปัจจุบัน ในหลายกรณี Server-side Natives/OneSync สามารถให้ข้อมูลโดยไม่ต้องให้ Client Spam Event เอง

เลือก Architecture ตาม Requirement จริง

ตัวอย่าง Memory Cache Player

local PlayerCache = {}

RegisterNetEvent(
    'player:loaded',
    function(player)

        PlayerCache[
            player.id
        ] = player
    end
)

ต้องมี Cleanup ตาม Lifecycle

ไม่ควรสะสม Player ที่ออกไปแล้วตลอด Runtime

Resource Stop ต้อง Cleanup

Resource ที่สร้าง

Blips
Entities
NUI Focus
Cameras
Timers/State

ควรมี Cleanup Strategy

ตัวอย่าง Client

AddEventHandler(
    'onClientResourceStop',
    function(resourceName)

        if resourceName ~=
            GetCurrentResourceName() then
            return
        end

        SetNuiFocus(
            false,
            false
        )

        for i = 1, #Blips do

            local blip =
                Blips[i]

            if DoesBlipExist(
                blip
            ) then

                RemoveBlip(
                    blip
                )
            end
        end
    end
)

ทำให้ Resource Restart ได้สะอาดขึ้น

Performance ต้องทดสอบ Player Scale

Resource อาจดูดีเมื่อ

1 Player

แต่พังตอน

100 Players

ถ้า Algorithm เป็น

O(players²)

เช่น Player ทุกคน Loop เทียบ Player ทุกคน

Cost สามารถโตอย่างรวดเร็ว

จึงต้องทดสอบ Scale ไม่ใช่เพียง Local Development

OneSync Scope Event เป็นตัวอย่าง Scaling Cost

Cfx.re เตือนโดยตรงว่า playerEnteredScope และ playerLeftScope มี Scaling Performance Cost

เช่นมี Players หลายคนอยู่ใน Scope เดียวกัน Event จำนวนมากสามารถเกิดเพิ่มตามจำนวน Player Relationships

เมื่อเหมาะสม Cfx.re แนะนำ State Bags แทน Scope Events เหล่านี้

Server Performance ต้องดู Player Count คู่กัน

txAdmin มีกราฟ

CPU/RAM
Server Threads
Player Count

ถ้าเห็น

20 Players
→ CPU 20%

100 Players
→ CPU 80%

แสดงว่ามี Workload ที่ Scale กับจำนวนผู้เล่น

Profiler สามารถช่วยเจาะว่า Resource ใดโตตาม Player Count

ก่อน Optimization ต้องสร้าง Scenario

ตัวอย่าง

Scenario A
Player ยืน Idle

Scenario B
เปิด Inventory

Scenario C
เข้า Garage

Scenario D
ผู้เล่น 50 คนออนไลน์

วัดแยกกัน

จะรู้ Feature ไหนเพิ่ม Cost

ไม่ควรจับทุกอย่างพร้อมกันแล้วเดา

วัด Before และ After

ก่อนแก้บันทึก

FPS
Resource ms
Memory
Profiler

หลังแก้ทำ Scenario เดิม

เช่น

Before
Garage Thread = 2.8 ms

After
Garage Thread = 0.3 ms

ตัวเลขจริงทำให้รู้ว่าการ Optimize ได้ผล

อย่า Optimize แล้ว Gameplay แย่ลง

ตัวอย่างลด

Wait(0)

เป็น

Wait(5000)

จน Marker ใช้เวลา 5 วินาทีถึงปรากฏ

แม้ Resmon สวยขึ้น แต่ UX แย่

Optimization ที่ดีต้องรักษา

Correctness
Responsiveness
Performance

พร้อมกัน

อย่าไล่เลข 0.00 ms อย่างเดียว

Resource ที่มี Function จริงย่อมใช้ CPU

เป้าหมายคือกำจัด

งานที่ไม่จำเป็น
งานซ้ำ
Polling เกิน
Algorithm ไม่ Scale
Memory ที่ไม่ Cleanup

ไม่ใช่บีบทุก Resource ให้เป็น 0.00 ตลอดเวลา

Performance Budget ควรคิดรวมทั้ง Server

Player ใช้ Resource หลายสิบหรือหลายร้อยตัวพร้อมกัน

แม้แต่ละ Resource ใช้ไม่มาก

Resource A
+
Resource B
+
Resource C
+
...

ก็รวมกันเป็น Client CPU Cost

ดังนั้น Optimization ทุก Resource เล็กน้อยสามารถช่วย Server Pack ขนาดใหญ่ได้

วิธีไล่แก้ Resmon สูงแบบสั้นที่สุด

ใช้ขั้นตอนนี้

① resmon true
↓
② Reproduce ปัญหา
↓
③ จด Resource ที่สูง
↓
④ profiler record 500
↓
⑤ Reproduce ซ้ำ
↓
⑥ profiler view
↓
⑦ หา Thread/File/Line
↓
⑧ ลด Work / Frequency
↓
⑨ Restart Resource
↓
⑩ Reproduce Scenario เดิม
↓
⑪ Profile ใหม่

ถ้าหลังแก้ไม่มีความแตกต่าง

อาจแก้ผิด Bottleneck

กลับไปดู Profile ใหม่

คำถามที่พบบ่อยเกี่ยวกับ FiveM Resmon สูง

FiveM Resmon คืออะไร

Resource Monitor ฝั่ง Client สำหรับดู CPU Time และ Memory Usage ของแต่ละ Resource

เปิด Resmon อย่างไร

resmon true

ปิดอย่างไร

resmon false

Resmon สูงแก้อย่างไร

หา Resource ก่อน จากนั้นใช้ Profiler ระบุ Thread/File/Line แล้วลด Work ที่ไม่จำเป็น

Resmon สูงเพราะ Wait(0) ใช่ไหม

อาจใช่ แต่ Wait(0) ไม่ผิดเสมอ ต้องดูงานข้างใน

Loop ไม่มี Wait ได้ไหม

ไม่ควร Cfx.re เตือนว่าสามารถทำให้ Client ค้างหรือ Crash ได้

Wait เท่าไรดีที่สุด

ไม่มีค่าตายตัว ต้องสัมพันธ์กับ Feature

Adaptive Wait คืออะไร

ใช้ Wait นานเมื่อไม่ต้องทำงานละเอียด และลด Wait เมื่อ Player อยู่ใน State ที่ต้องตอบสนองเร็ว

Event ดีกว่า Loop ไหม

ถ้าข้อมูลเปลี่ยนจาก Event ชัดเจน Event-driven มักลด Polling ได้

Native Calls ทำให้ Resmon สูงไหม

ได้เมื่อถูกเรียกจำนวนมากหรือใน Loop ถี่

Entity Scan ทำให้ Resmon สูงไหม

ได้ โดยเฉพาะ Scan จำนวนมากทุก Frame

Nested Loop ต้องระวังไหม

มาก เพราะจำนวน Operations สามารถเพิ่มแบบคูณ

SendNUIMessage ทำให้ Resmon สูงไหม

การส่งข้อมูลถี่/ใหญ่เพิ่ม Work ฝั่ง Client และ Browser ได้

NUI ทำ FPS ตกแต่ Resmon ต่ำได้ไหม

ได้ หาก Cost อยู่ใน CEF Rendering, CSS, JavaScript หรือ GPU

ใช้ cl_drawperf ช่วยได้ไหม

ได้ ใช้ดู FPS, CPU/GPU Usage และ Metrics อื่นเบื้องต้น

Memory Resmon สูงแก้อย่างไร

ดูว่า Cache/Table/History/Entity Data โตต่อเนื่องหรือไม่ และมี Cleanup ตาม Lifecycle หรือไม่

Memory สูงคือ Leak เสมอไหม

ไม่ ต้องดูว่าขนาดคงที่หรือเพิ่มต่อเนื่อง

Player Cache ต้องลบไหม

ต้อง Cleanup เมื่อ Player ออกหากข้อมูลนั้นไม่ต้องเก็บต่อ

Entity Cache ต้องลบไหม

ควร Cleanup Metadata ของ Entity ที่หมด Lifetime เมื่อไม่จำเป็นแล้ว

Database Query มีผลต่อ Resmon Client ไหม

Database ปกติอยู่ Server-side แต่ Database/Server Work ที่ช้าสามารถทำให้ Server Hitch หรือ Gameplay Latency ได้

Server Resmon ดูอย่างไร

ใช้ Server Profiler, txAdmin และ Monitoring Tools มากกว่า Client resmon

SQL ช้าทำ Server Hitch ได้ไหม

ได้ Cfx.re ระบุ Underperforming SQL Queries เป็นหนึ่งในสาเหตุของ Hitch Warnings

Profiler เปิดอย่างไร

profiler record 500

หลัง Capture ดูอย่างไร

profiler view

Profiler หา Line Code ได้ไหม

Cfx.re Guide แสดงว่าสามารถเจาะ Resource Tick ไปถึง Thread, File และ Line ใน Profile ได้

txAdmin ดู CPU ได้ไหม

ได้ txAdmin มี Monitoring CPU/RAM และ Server Threads Performance

Scope Events ใน OneSync หนักไหม

Cfx.re เตือนว่า playerEnteredScope/playerLeftScope มี Scaling Performance Cost และแนะนำ State Bags ใน Use Case ที่เหมาะสม

Logging ทุก Frame มีผลไหม

มีได้ และสร้าง Console Noise จำนวนมาก

Optimize แล้วต้องวัดอีกไหม

ต้อง เพราะการแก้ที่ไม่มี Before/After Measurement อาจไม่ได้แก้ Bottleneck จริง

สรุป FiveM Resmon สูงแก้อย่างไร ลด Script กิน CPU และ Memory

ปัญหา Resmon สูงไม่ควรแก้ด้วย

ใส่ Wait เยอะขึ้นทุกจุด

แต่ต้องใช้กระบวนการ

Measure
↓
Identify
↓
Optimize
↓
Measure Again

เริ่มจาก

resmon true

เพื่อหา Resource

จากนั้นใช้

profiler record 500

และ

profiler view

เพื่อเจาะเข้า

Resource
↓
Thread
↓
File
↓
Line

เมื่อเจอ Bottleneck ให้ตรวจกลุ่มใหญ่เหล่านี้

Loops
Wait
Natives
Entity scans
Nested loops
Events
Network payload
NUI
Database
Logging
Memory cache

หลัก Optimization สำคัญคือ ให้ Code ทำงานตามความถี่ที่จำเป็นจริง

Rendering
→ Every Frame เมื่อจำเป็น

Zone detection
→ ทำเป็นช่วง/Spatial search

Job/Money
→ Event-driven

Database
→ เมื่อ Persistent Data ต้องเปลี่ยน

NUI
→ เมื่อข้อมูลเปลี่ยน

ส่วน Memory ต้องตรวจว่าข้อมูลมี Lifecycle หรือไม่

Create
↓
Use
↓
Cleanup

Table ที่เพิ่มข้อมูลแต่ไม่เคยลบ, Entity Cache ที่เก็บ Handle เก่า หรือ Player Cache ที่ไม่ Cleanup ตอนออก ล้วนสามารถทำ Memory Usage เพิ่มขึ้นเรื่อย ๆ ได้

สำหรับ Developer ที่เรียน FiveM กับ comsiam หลักสำคัญที่สุดคือ อย่า Optimize Resource จากตัวเลข Resmon เพียงบรรทัดเดียว ให้ Reproduce ปัญหาแล้วใช้ Profiler ยืนยันว่า Code ส่วนใดคือ Bottleneck จริง

และหลักจาก comsiam อีกข้อคือ ถ้าต้องเลือกระหว่าง Micro Optimization กับเปลี่ยน Architecture ให้ทำงานน้อยลง ให้แก้ Architecture ก่อน เพราะการลด Loop จาก 50,000 Operations เหลือไม่กี่ร้อยครั้งมักสร้างผลมากกว่าการปรับ Syntax เล็ก ๆ ภายใน Loop เดิม

นี่คือจุดสิ้นสุดของ หมวดที่ 9: FiveM Developer, Lua, Resource และ Database ลำดับ 401–460 โดยหัวข้อนี้เป็นบทความลำดับ 460 ตามชุดที่ล็อกไว้

Comments

Popular posts from this blog

FiveM ยังน่าเล่นไหม? Enhanced เปลี่ยน FiveM แค่ไหน

FiveM คืออะไร เล่นอย่างไร สำหรับมือใหม่ เริ่มต้นตั้งแต่ศูนย์

วิธีตั้ง Admin Permission ด้วย add_ace และ add_principal FiveM แบบละเอียด