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
Post a Comment