FPS (Frames Per Second) คือเมตริกที่แสดงว่าระบบกราฟิกเรนเดอร์เฟรมแต่ละเฟรมได้กี่เฟรมในหนึ่งวินาที ในการพัฒนามือถือ FPS เป็นตัวบ่งชี้ประสิทธิภาพ UI มาตรฐาน: ยิ่ง FPS สูงเท่าใด แอนิเมชันก็จะยิ่งราบรื่นและอินเทอร์เฟซก็จะตอบสนองมากขึ้นเท่านั้น ตาม Google Android Performance, 2025 ค่าเป้าหมาย FPS สำหรับแอปพลิเคชันมือถือคือ 60 เฟรมต่อวินาที — ซึ่งเป็นเกณฑ์ที่ดวงตามนุษย์รับรู้การเคลื่อนไหวว่าต่อเนื่องและราบรื่น
ประเด็นสำคัญ
FPS (Frames Per Second) คือหน่วยวัดอัตราเฟรมที่ใช้ในคอมพิวเตอร์กราฟิก วิดีโอ และอินเทอร์เฟซมือถือ แต่ละเฟรมคือภาพนิ่งที่แสดงบนหน้าจอในช่วงเวลาสั้นๆ เมื่อเฟรมเปลี่ยนแปลงอย่างรวดเร็ว สมองจะรับรู้ว่าเป็นการเคลื่อนไหวต่อเนื่อง — เอฟเฟกต์นี้เรียกว่าการคงอยู่ของการมองเห็น สำหรับแอปพลิเคชันมือถือ FPS เป็นเมตริกที่สำคัญ เนื่องจากเฟรมที่พลาดไปจะเปลี่ยนแอนิเมชันที่ราบรื่นให้กลายเป็นการกระตุกที่สังเกตได้ แอปพลิเคชันต้องเรนเดอร์แต่ละเฟรมอย่างเคร่งครัดภายในงบประมาณเวลา: 16.6 มิลลิวินาทีสำหรับ 60 FPS, 11.1 มิลลิวินาทีสำหรับ 90 FPS, 8.3 มิลลิวินาทีสำหรับ 120 FPS
FPS ไม่ได้วัดเฉพาะสำหรับ UI เท่านั้น แต่ยังวัดสำหรับเกม วิดีโอ และกล้องอีกด้วย ในเกม FPS ขึ้นอยู่กับความซับซ้อนของฉาก คุณภาพของพื้นผิว และพลังของ GPU ในวิดีโอ FPS จะคงที่ (24, 30, 60 fps) และกำหนดโดยเนื้อหา ในแอปพลิเคชันมือถือ FPS ขึ้นอยู่กับประสิทธิภาพของโค้ด UI: ความซับซ้อนของเลย์เอาต์ จำนวนวิว ความถี่ในการวาดซ้ำ และการทำงานของ GC (Garbage Collection) ตาม Apple WWDC 2022 ค่า FPS เฉลี่ยในแอปพลิเคชันอาจลดลง 10–15% เนื่องจากการอัปเดตคอลเลกชันที่ไม่มีประสิทธิภาพ (reloadData แทนการ insert/delete/dequeueReusableCell) การวัด FPS แบบเรียลไทม์เป็นแนวทางปฏิบัติมาตรฐานสำหรับวิศวกร QA และนักพัฒนาที่ทำงานด้านประสิทธิภาพ
การคำนวณ FPS ในแอปพลิเคชันมือถือขึ้นอยู่กับการวัดเวลาระหว่างเฟรมที่ต่อเนื่องกัน สูตรที่ง่ายที่สุด: FPS = 1000 / deltaTimeMs โดยที่ deltaTimeMs คือช่วงเวลาระหว่างการเสร็จสิ้นของเฟรมก่อนหน้าและการเสร็จสิ้นของเฟรมปัจจุบัน หากเฟรมปัจจุบันเรนเดอร์ใน 20 มิลลิวินาที FPS = 1000 / 20 = 50 อย่างไรก็ตาม ในทางปฏิบัติ FPS แทบจะไม่เสถียรแม้ภายในหนึ่งวินาที: โปรไฟล์ทั่วไปประกอบด้วยเฟรม 12–16 มิลลิวินาทีสลับกับเฟรมที่ข้ามไป (jank) หรือเฟรมช้า (40–60 มิลลิวินาที) ดังนั้น FPS จึงวัดเป็นค่าเฉลี่ยเคลื่อนที่ในช่วง 1–5 วินาทีหรือเป็นเปอร์เซ็นไทล์ของการกระจายเวลาเฟรม
บน Android FPS คำนวณผ่าน Choreographer ซึ่งได้รับการเรียกกลับจาก VSync (พัลส์ซิงค์หน้าจอ) การเรียกกลับแต่ละครั้งสอดคล้องกับหนึ่งเฟรม หากการเรียกกลับไม่มาถึง — เฟรมจะถูกข้ามไป Choreographer ช่วยให้วัดจำนวนเฟรมที่แน่นอนต่อวินาทีและจำนวนเฟรมที่ข้ามไปได้ บน iOS CADisplayLink ทำงานคล้ายกัน — มันถูกเรียกทุกครั้งที่หน้าจอพร้อมที่จะเรนเดอร์เฟรมใหม่ คุณสมบัติ timestamp ประกอบด้วยเวลาที่แน่นอนของเฟรมล่าสุด และ targetTimestamp — เวลาที่คาดหวังของเฟรมถัดไป ความแตกต่างระหว่างทั้งสองคืองบประมาณเวลาสำหรับเฟรมปัจจุบัน
โค้ด Swift สาธิตการตรวจสอบ FPS อย่างง่ายผ่าน CADisplayLink ตัวนับ frameCount จะเพิ่มขึ้นทุกครั้งที่มีการเรียก และวินาทีละครั้งจะคำนวณ FPS จริง
class FpsCounter {
private var displayLink: CADisplayLink?
private var frameCount = 0
private var lastTime = TimeInterval(0)
func start() {
displayLink = CADisplayLink(
target: self,
selector: #selector(countFrame)
)
displayLink?.add(to: .current,
forMode: .common)
}
@objc
private func countFrame() {
frameCount += 1
let now = Date().timeIntervalSince1970
if now - lastTime >= 1.0 {
print("FPS: \(frameCount)")
frameCount = 0
lastTime = now
}
}
}
มาตรฐาน 60 FPS (หรือ 60 Hz) ได้รับการยอมรับในอุตสาหกรรมด้วยเหตุผลหลายประการ ประการแรกคือทางสรีรวิทยา: ดวงตามนุษย์ไม่สามารถแยกแยะเฟรมแต่ละเฟรมที่ความถี่สูงกว่า 50–60 Hz โดยรับรู้ว่าเป็นการเคลื่อนไหวที่ราบรื่น เกณฑ์นี้เรียกว่า Critical Flicker Fusion (CFF) ประการที่สองคือทางประวัติศาสตร์: หลอดรังสีแคโทด (CRT) รุ่นแรกทำงานที่ 60 Hz ในสหรัฐอเมริกา (NTSC) และ 50 Hz ในยุโรป (PAL) จอ LCD สมัยใหม่สืบทอดความถี่นี้ ประการที่สามคือทางวิศวกรรม: สำหรับแอนิเมชัน UI 60 FPS ให้เวลาแฝงการตอบสนองการสัมผัสระดับต่ำกว่ามิลลิวินาที ซึ่งสำคัญสำหรับการป้อนข้อความ การเลื่อน และการลาก
สำหรับนักพัฒนามือถือ 60 FPS ไม่ใช่แค่คำแนะนำ แต่เป็นงบประมาณที่เข้มงวดที่ 16.6 มิลลิวินาทีต่อเฟรม งบประมาณนี้แบ่งระหว่างทุกเฟสของการเรนเดอร์: อินพุต (1–2 มิลลิวินาที), แอนิเมชัน (2–3 มิลลิวินาที), เลย์เอาต์ (3–5 มิลลิวินาที), การวาด (3–5 มิลลิวินาที) และการสลับ (1–2 มิลลิวินาที) หากเฟสใดเกินงบประมาณย่อย เฟรมอาจไม่พอดีกับ 16.6 มิลลิวินาที Google Android Performance แนะนำให้อยู่ในช่วง 12–14 มิลลิวินาทีสำหรับการเตรียมเฟรม โดยเหลือเผื่อ 2–4 มิลลิวินาทีสำหรับการขัดจังหวะของระบบ (GC, เธรดพื้นหลัง) ตาม Firebase Performance แอปพลิเคชันที่มีค่า FPS เฉลี่ยต่ำกว่า 52 และ P99 FPS ต่ำกว่า 30 ได้รับข้อร้องเรียนด้านประสิทธิภาพเพิ่มขึ้น 35% ในรีวิว Google Play
FPS และ เวลาเฟรม เป็นสองด้านของเมตริกเดียวกัน และสิ่งสำคัญคืออย่าสับสน FPS คือความเร็ว เวลาเฟรมคือเวลาแฝง ที่ 60 FPS แต่ละเฟรมใช้เวลา 16.6 มิลลิวินาที ที่ 30 FPS — 33.3 มิลลิวินาที แต่ FPS เป็นเมตริกที่ไม่เป็นเชิงเส้น: การลดลงจาก 60 เป็น 30 FPS หมายความว่าเวลาเฟรมเพิ่มขึ้นเป็นสองเท่า ในขณะที่การลดลงจาก 30 เป็น 20 หมายถึงเพิ่มขึ้น 1.5 เท่า ดังนั้น โปรไฟล์เลอร์จึงแสดงเวลาเฟรมแทน FPS — ซึ่งช่วยให้เห็นเฟรมที่มีปัญหาแทนที่จะเป็นความถี่เฉลี่ย ตัวอย่างเช่น ค่าเฉลี่ย 55 FPS อาจซ่อนความจริงที่ว่า 5% ของเฟรมมีเวลาเฟรม 50–100 มิลลิวินาที — เฟรมเหล่านี้ทำให้เกิด Jank แต่ไม่ส่งผลกระทบอย่างมีนัยสำคัญต่อ FPS เฉลี่ย
เมื่อวิเคราะห์ประสิทธิภาพ แนะนำให้ดูฮิสโตแกรมเวลาเฟรมแทน FPS เฉลี่ย ใน Android Studio Profiler และ iOS Instruments เวลาเฟรมจะแสดงเป็นสเกล โดยโซนสีเขียวสูงถึง 16.6 มิลลิวินาที (60 FPS), สีเหลือง 16.6–33.3 มิลลิวินาที (30–60 FPS), สีแดงมากกว่า 33.3 มิลลิวินาที (น้อยกว่า 30 FPS) แต่ละคอลัมน์สีแดงคือความล่าช้าที่ผู้ใช้สังเกตได้ กฎเชิงปฏิบัติ: P95 Frame Time (95% ของเฟรมอยู่ใน X มิลลิวินาที) เป็นเมตริกที่เชื่อถือได้มากกว่า FPS เฉลี่ย หาก P95 Frame Time เกิน 32 มิลลิวินาที (30 FPS) แอปพลิเคชันจะรู้สึกว่าช้าแม้จะมี FPS เฉลี่ย 50
ฟังก์ชัน Kotlin สำหรับแปลงอาร์เรย์เวลาเฟรมเป็น FPS พร้อมเปอร์เซ็นไทล์ โดยจะส่งคืนไม่เพียง FPS เฉลี่ย แต่ยังรวมถึง P50, P90 และ P99 สำหรับการวิเคราะห์โดยละเอียด
data class FpsReport(
val average: Float,
val p50: Float,
val p90: Float,
val p99: Float
)
fun List<Long>.toFpsReport(): FpsReport {
val fpsValues = this.map { ms ->
if (ms > 0) 1000f / ms else 0f
}.sorted()
return FpsReport(
average = fpsValues.average().toFloat(),
p50 = fpsValues[fpsValues.size / 2],
p90 = fpsValues[(fpsValues.size * 90 / 100)],
p99 = fpsValues[(fpsValues.size * 99 / 100)]
)
}
อุปกรณ์มือถือสมัยใหม่ที่มีจอแสดงผล 90, 120 และ 144 Hz กำหนดข้อกำหนดใหม่สำหรับ FPS หากแอปพลิเคชันส่ง 60 FPS บนจอแสดงผล 120 Hz ผู้ใช้จะเห็นการกระตุกขนาดเล็ก เนื่องจากทุกรอบการรีเฟรชหน้าจอที่สองจะได้รับเฟรมเดียวกัน เพื่อรักษา 120 FPS งบประมาณต่อเฟรมจะลดลงจาก 16.6 เป็น 8.3 มิลลิวินาที — ซึ่งต้องใช้โค้ดการเรนเดอร์ที่มีประสิทธิภาพเป็นสองเท่า ตามที่นักพัฒนา Android (Google I/O 2023) กล่าวไว้ การบรรลุ 120 FPS ที่เสถียรจำเป็นต้อง: หลีกเลี่ยงการจัดสรรในวงจรการวาด ลดจำนวนวิวในลำดับชั้นให้น้อยที่สุด (ต่ำกว่า 80) ละทิ้ง drawable ที่หนักหน่วงเพื่อหันไปใช้ VectorDrawable และใช้ surfaceView สำหรับกราฟิกที่ซับซ้อน
สถานการณ์บน iOS คล้ายกัน: iPhone Pro พร้อม ProMotion (120 Hz) ต้องการเฟรมเป็นสองเท่า แต่เวลาต่อเฟรมลดลงครึ่งหนึ่ง Apple ตั้งข้อสังเกตว่าแอนิเมชันทั้งหมดไม่จำเป็นต้องทำงานที่ 120 FPS — Core Animation จะลดอัตราเฟรมสำหรับองค์ประกอบที่อยู่นิ่งหรือเปลี่ยนแปลงช้าโดยอัตโนมัติ อย่างไรก็ตาม การเลื่อน แอนิเมชันท่าทาง และการเปลี่ยนภาพต้องส่ง 120 FPS เพื่อความรู้สึก “เนียนนุ่ม” ปัญหาหลักเมื่อเปลี่ยนจาก 60 เป็น 120 FPS: การใช้พลังงานเพิ่มขึ้น (25–40% สำหรับ GPU), อุปกรณ์ร้อน และการคุมความร้อน (throttling) — เมื่ออัตราเฟรมลดลงเนื่องจากความร้อนสูงเกินไป ขอแนะนำให้ใช้กลไกสำรอง: หากเวลาเฟรมเกิน 8.3 มิลลิวินาทีอย่างต่อเนื่อง ให้ลดอัตราเฟรมเป้าหมายเป็น 60 FPS ด้วยโปรแกรม แทนที่จะรอการคุมความร้อนของระบบ
โค้ด Java สำหรับ Android ตรวจสอบว่าอุปกรณ์สามารถรองรับ 120 FPS ได้หรือไม่ และสลับโหมดการเรนเดอร์ ใช้ Display.getMode เพื่อกำหนดอัตรารีเฟรชที่รองรับ
class FpsModeSwitcher {
static boolean canDo120Fps(Activity activity) {
Display display = activity.getWindowManager()
.getDefaultDisplay();
for (Display.Mode mode : display.getSupportedModes()) {
if (mode.getRefreshRate() >= 120f) {
return true;
}
}
return false;
}
}
การปรับ FPS ให้เหมาะสมต้องใช้แนวทางที่เป็นระบบ เริ่มต้นด้วยการทำโปรไฟล์และจบลงด้วยการปรับโครงสร้างพื้นที่ที่มีปัญหา ขั้นตอนแรกคือการวัด FPS ปัจจุบันด้วยโปรไฟล์เลอร์ ขั้นตอนที่สองคือการค้นหาเฟรมที่เกินงบประมาณ บน Android สามารถทำได้ผ่าน GPU Profiling หรือ Perfetto บน iOS — Instruments ด้วยเทมเพลต Core Animation ขั้นตอนที่สามคือการกำจัดสาเหตุ: ลด overdraw, ลดความลึกของลำดับชั้นวิว, แทนที่เฟสเลย์เอาต์ด้วย ConstraintLayout, เพิ่ม ViewHolder Recycling, ย้ายการคำนวณหนักไปยังเธรดพื้นหลัง
การปรับ FPS เฉพาะรวมถึง: Frame Pacing — กลไกที่กระจายเวลาระหว่างเฟรมอย่างสม่ำเสมอเพื่อหลีกเลี่ยง “การระเบิด” ของเฟรมเร็วและช้า บน Android Choreographer.FrameCallback ด้วยช่วงเวลาคงที่ช่วยให้ใช้ Frame Pacing ได้ บน iOS CADisplayLink.preferredFrameRateRange ทำสิ่งเดียวกัน วิธีที่สอง — Triple Buffering: ระบบใช้บัฟเฟอร์สามตัวแทนที่จะเป็นสองตัว ทำให้ GPU เริ่มเรนเดอร์เฟรมถัดไปโดยไม่ต้องรอให้เฟรมก่อนหน้าปล่อย Android เปิดใช้ Triple Buffering โดยอัตโนมัติเมื่อจำเป็น แต่บน iOS นักพัฒนาสามารถขอได้อย่างชัดเจนผ่าน CAMetalLayer วิธีที่สาม — Texture Caching: การแคชบิตแมปในหน่วยความจำ GPU เพื่อหลีกเลี่ยงการโหลดซ้ำทุกเฟรม
ตัวอย่าง Kotlin สาธิตการใช้ Frame Pacing ด้วยช่วงเวลาคงที่ 16.6 มิลลิวินาที การเรียกกลับทั้งหมดมาถึงในช่วงเวลาที่สม่ำเสมอ แม้ว่าระบบจะล่าช้าก็ตาม
class PacedFrameRenderer {
private val targetDelta = 16_666_666L // 16.6 ms (60 FPS)
private var lastFrameTime = 0L
private val frameCallback =
Choreographer.FrameCallback { frameTimeNanos ->
val delta = frameTimeNanos - lastFrameTime
if (delta >= targetDelta) {
onFrame(delta)
lastFrameTime = frameTimeNanos
}
Choreographer.getInstance()
.postFrameCallback(this)
}
private fun onFrame(delta: Long) {
// การเรนเดอร์เฟรม
}
}
คำถามที่พบบ่อย
60 FPS เป็นระดับที่สะดวกสบายสำหรับแอปพลิเคชันมือถือ ความแตกต่างระหว่าง 60 และ 120 FPS สังเกตได้เฉพาะบนจอแสดงผลอัตรารีเฟรชสูงระหว่างแอนิเมชันที่รวดเร็ว (การเลื่อน การลาก) ต่ำกว่า 30 FPS — ไม่สบาย
FPS = 1000 / FrameTime (ms) หากเวลาเฟรม = 16.6 มิลลิวินาที FPS = 60 หากเวลาเฟรม = 33.3 มิลลิวินาที FPS = 30 แนะนำให้ตรวจสอบเวลาเฟรมแทน FPS เนื่องจากแสดงเฟรมที่มีปัญหา
เมื่อเลื่อน ระบบจะเรียก Layout และ Draw สำหรับแต่ละรายการใหม่ในรายการ หากวิวซับซ้อน เลย์เอาต์ไม่ได้ถูกแคช หรือใช้ drawable ที่หนัก — เวลาเฟรมจะเพิ่มขึ้นและ FPS จะลดลง วิธีแก้คือการรีไซเคิล ViewHolder และลำดับชั้นแบบราบ
ใช้ Instruments ด้วยเทมเพลต Core Animation (แสดง FPS แบบเรียลไทม์) สำหรับการวัดด้วยโปรแกรม — CADisplayLink พร้อมการนับเฟรมต่อวินาที สำหรับระบบผลิต — MetricKit ด้วยเมตริก MXAnimatoryMetric
Triple Buffering ใช้บัฟเฟอร์สามตัวแทนที่จะเป็นสองตัว ทำให้ GPU เริ่มเรนเดอร์เฟรมถัดไปก่อนที่ VSync ปัจจุบันจะเสร็จสมบูรณ์ ซึ่งช่วยลดภาระสูงสุดและปรับปรุงความเสถียรของ FPS แต่เพิ่มเวลาแฝงหนึ่งเฟรม
สรุป
เราจะพัฒนาแอปพลิเคชันบนมือถือแบบครบวงจร
IT Sectr สร้างแอปพลิเคชัน iOS และ Android สำหรับสตาร์ทอัพและธุรกิจตั้งแต่ปี 2017 เราจะให้คำแนะนำและเสนอวิธีแก้ปัญหาที่ดีที่สุดแก่คุณ
อ่านเพิ่มเติม