เมื่อผลทดสอบบอกว่าอย่าทำ แต่ทำไมเรายังเลือกทำ Parallelization ในโปรแกรม?

8 นาที 11 views บันทึกเป็น PDF
เมื่อผลทดสอบบอกว่าอย่าทำ แต่ทำไมเรายังเลือกทำ Parallelization ในโปรแกรม?

ทำไมการแบ่งงานให้หลายคนทำพร้อมกันถึงอาจทำให้โปรแกรมช้าลง? มาเรียนรู้เรื่องคอขวด (Bottleneck) และความสำคัญของการออกแบบโค้ดที่ถูกต้องและทนทานกว่าแค่ความเร็ว

เมื่อผลลัพธ์บอกว่าอย่าทำ แต่เราก็ยังเลือกจะทำ

ในการเขียนโปรแกรม บางครั้งตัวเลขที่วัดได้ก็ไม่ได้บอกความจริงทั้งหมดเสมอไป เหมือนกับตอนที่เราทำเครื่องมือค้นหาข้อมูลในไฟล์บันทึกการทำงาน (Log Query Tool) เราพยายามทำให้มันทำงานเร็วขึ้นด้วยการแบ่งงานให้หลายคนช่วยทำพร้อมกัน แต่ผลลัพธ์กลับกลายเป็นว่าการมีคนช่วยหลายคนกลับทำให้งานช้าลงกว่าเดิมเสียอย่างนั้น

เราทดสอบด้วยการใช้ Goroutines (หน่วยทำงานย่อยในภาษา Go ที่ช่วยให้รันคำสั่งพร้อมกันได้) จำนวน 8 ตัว เพื่อจัดการข้อมูลขนาด 76.3MB ผลปรากฏว่ามันใช้เวลาไป 7.558 วินาที ในขณะที่ถ้าให้คนเดียวทำ มันใช้เวลาเพียง 6.535 วินาทีเท่านั้น ซึ่งการแบ่งงานให้ 8 คนทำช้ากว่าเดิมถึง 16%

แม้ผลการทดสอบจะบอกว่าการเพิ่มจำนวนคนทำงานไม่ได้ช่วยให้เร็วขึ้น แต่เราก็ยังตัดสินใจใส่ความสามารถนี้เข้าไปในโปรแกรมของเราอยู่ดี เพราะในโลกของการพัฒนาซอฟต์แวร์ บางครั้งการตัดสินใจไม่ได้ขึ้นอยู่กับความเร็วในวันนี้เพียงอย่างเดียว แต่ขึ้นอยู่กับการวางโครงสร้างเพื่ออนาคตด้วย

ทำไมการแบ่งงานทำพร้อมกันถึงช้ากว่าเดิม

หลายคนอาจสงสัยว่าทำไมการเพิ่มจำนวนคน (Workers) ถึงไม่ช่วยให้งานเสร็จไวขึ้น ให้ลองนึกภาพการทำอาหาร ถ้าคุณมีเชฟคนเดียวเขาจะทำทุกอย่างตามลำดับขั้นตอน แต่ถ้าคุณมีเชฟ 8 คน คุณต้องเสียเวลาในการ Coordination (การประสานงานหรือส่งต่องานระหว่างกัน) ซึ่งถ้างานนั้นมีขั้นตอนการเตรียมวัตถุดิบที่ซับซ้อนและต้องรอคิว การเพิ่มคนจะกลายเป็นภาระแทน

ในกรณีของเรา ปัญหาคอขวดอยู่ที่ขั้นตอนการ Decode (การแปลงข้อมูลดิบให้เป็นรูปแบบที่โปรแกรมเข้าใจ) ซึ่งเป็นงานหลักที่กินเวลามากที่สุด การที่เราเพิ่มจำนวนคนทำงานเข้าไปช่วยแค่ขั้นตอนการคำนวณสรุปผล แต่ขั้นตอนการอ่านข้อมูลดิบยังคงทำได้ทีละคนเหมือนเดิม ทำให้เวลาที่เสียไปกับการส่งข้อมูลให้กันระหว่างคนทำงานนั้นไม่คุ้มค่าเลย

นี่คือบทเรียนสำคัญสำหรับโปรแกรมเมอร์มือใหม่ว่า Parallelization (การทำหลายอย่างพร้อมกัน) ไม่ใช่ยาวิเศษที่จะแก้ปัญหาช้าได้ทุกเรื่อง หากเราไม่ได้ปรับปรุงขั้นตอนที่ช้าที่สุด (Bottleneck) การเพิ่มทรัพยากรเข้าไปก็เหมือนการเอาคนไปยืนออหน้าประตูที่เปิดได้ทีละนิด ยิ่งคนเยอะก็ยิ่งวุ่นวายและช้าลงกว่าเดิม

ความถูกต้องต้องมาก่อนความเร็ว

เหตุผลที่เราตัดสินใจใส่ฟีเจอร์นี้ลงไปทั้งที่มันช้ากว่า คือเรื่องของ ความถูกต้อง (Correctness) การเขียนโปรแกรมที่ดีไม่ได้ดูแค่ว่ามันเร็วแค่ไหน แต่ต้องดูว่ามันให้ผลลัพธ์ที่เชื่อถือได้หรือไม่ เราได้ทดสอบแล้วว่าไม่ว่าจะใช้คนทำงานกี่คน ผลลัพธ์ที่ได้ออกมานั้นเหมือนกันทุกประการ ซึ่งเป็นเรื่องที่สำคัญที่สุดสำหรับเครื่องมือจัดการข้อมูล

ลองดูตัวอย่างการเปรียบเทียบการทำงานแบบขั้นตอนเดียวกับหลายขั้นตอนในโค้ด Go ง่ายๆ ดังนี้ครับ

// โค้ดส่วนที่จัดการงานแบบง่าย
func processLog(data []string) {
    for _, line := range data {
        // ประมวลผลทีละบรรทัด
        println(line)
    }
}

// โค้ดส่วนที่แบ่งงานให้หลายคนทำ
func processLogParallel(data []string) {
    // สร้างช่องทางส่งข้อมูลและแบ่งงาน
    // แต่ละคนจะประมวลผลข้อมูลในส่วนของตัวเอง
}

ในโค้ดชุดแรกเป็นการทำงานแบบวนลูป (Loop) ทีละบรรทัด ซึ่งเรียบง่ายและตรวจสอบได้ง่าย ส่วนโค้ดชุดที่สองคือการใช้ Concurrency (การทำงานแบบขนาน) เพื่อแบ่งงาน ซึ่งต้องมีการจัดการ Channel (ช่องทางสื่อสารระหว่างหน่วยทำงาน) ให้ถูกต้อง ผลลัพธ์ที่ได้จากการรันทั้งสองวิธีต้องได้ข้อมูลชุดเดียวกันเป๊ะ ห้ามมีบรรทัดไหนหายไปหรือผิดเพี้ยนเด็ดขาด

การออกแบบที่ใส่ใจรายละเอียดข้อมูล

อีกเรื่องที่เราภูมิใจมากคือการจัดการกับข้อมูลที่หายไป ปกติแล้วเครื่องมือส่วนใหญ่จะมองว่าค่าที่ไม่มีอยู่จริง (Missing) กับค่าที่เป็นศูนย์ (Null) คือเรื่องเดียวกัน ซึ่งมักจะทำให้โปรแกรมพังเมื่อเจอข้อมูลที่หน้าตาแปลกไปจากเดิม เราจึงออกแบบให้ระบบแยกแยะสถานะเหล่านี้ออกจากกันอย่างชัดเจนตั้งแต่เริ่มสร้างโปรเจกต์

การแยกแยะสถานะ Missing, Null และ False ออกจากกันช่วยให้โปรแกรมไม่พังกลางคันเมื่อเจอข้อมูลที่ไม่สมบูรณ์ นี่คือแนวคิดเรื่อง Robustness (ความทนทานของโปรแกรม) ที่โปรแกรมเมอร์ควรมี เราไม่ได้แค่เขียนโค้ดให้ทำงานได้ แต่ต้องเขียนให้มันรับมือกับสิ่งที่คาดไม่ถึงได้ด้วย

หากเราไม่วางรากฐานนี้ไว้ตั้งแต่ต้น การจะมาแก้ไขภายหลังจะเป็นเรื่องยากมาก การเลือกออกแบบให้ถูกต้องตั้งแต่บรรทัดแรก แม้จะใช้เวลานานกว่าในการคิด แต่จะช่วยลดบั๊ก (ข้อผิดพลาด) ที่จะเกิดขึ้นในอนาคตได้อย่างมหาศาล เพราะโค้ดที่เขียนมาดีจะช่วยให้คุณไม่ต้องกลับมาแก้เรื่องเดิมซ้ำๆ ในภายหลัง

ทำไมเราถึงลบ CI ทิ้งแทนที่จะเก็บไว้

ในระหว่างการทำโปรเจกต์ เราได้ใส่ CI (Continuous Integration - ระบบตรวจสอบโค้ดอัตโนมัติ) เข้าไปเพื่อช่วยเช็คความเรียบร้อยของโค้ด แต่เมื่อมันทำงานผิดพลาดโดยที่เราหาสาเหตุไม่เจอและไม่สามารถแก้ไขได้ทันท่วงที เราจึงตัดสินใจลบมันทิ้งออกไปจากระบบทันที แทนที่จะปล่อยให้มันขึ้นไฟสีแดงค้างไว้โดยไม่มีคำอธิบาย

สำหรับมือใหม่ การมีระบบตรวจสอบอัตโนมัติเป็นเรื่องดี แต่ถ้ามันให้ผลลัพธ์ที่เชื่อถือไม่ได้ การลบออกดีกว่าการเก็บไว้ให้สับสน เพราะการเห็นไฟเตือนสีแดงโดยไม่รู้สาเหตุจะทำให้เราเสียสมาธิ และอาจทำให้เราละเลยไฟเตือนสีแดงจริงๆ ในอนาคตได้ ซึ่งเป็นนิสัยที่ไม่ดีในการทำงานเป็นทีม

จำไว้ว่าเครื่องมือมีไว้เพื่อช่วยให้เราทำงานง่ายขึ้น ไม่ได้มีไว้เพื่อสร้างภาระเพิ่ม หากเครื่องมือไหนทำตัวเป็นปัญหามากกว่าเป็นตัวช่วย การตัดใจทิ้งไปก่อนแล้วค่อยกลับมาทำใหม่เมื่อพร้อมคือทางเลือกที่ฉลาดกว่า การทำงานอย่างโปร่งใสและตรวจสอบได้สำคัญกว่าการมีเครื่องมือที่ดูหรูหราแต่ใช้งานจริงไม่ได้

สรุปบทเรียนจากการตัดสินใจ

เราตัดสินใจใส่ตัวเลือกการประมวลผลแบบขนานเข้าไป แม้ผลทดสอบจะบอกว่าช้ากว่าเดิม เพราะเรามองว่ามันคือ การวางโครงสร้างพื้นฐาน (Baseline) เพื่อรอการปรับปรุงในอนาคต เมื่อวันหนึ่งที่เราปรับปรุงขั้นตอนการอ่านข้อมูลให้เร็วขึ้นได้ ระบบจะพร้อมรองรับการทำงานหลายคนทันทีโดยไม่ต้องไปแก้โครงสร้างหลักอีก

สำหรับการนำไปใช้จริง: เมื่อคุณกำลังฝึกเขียนโปรแกรม อย่าเพิ่งหมกมุ่นกับการทำให้โค้ดเร็วที่สุดเพียงอย่างเดียว แต่ให้เน้นที่ความถูกต้องและการออกแบบที่ยืดหยุ่นก่อน เหมือนที่เราทำคือ

  1. เขียนโค้ดให้ทำงานได้ถูกต้องและจัดการเคสแปลกๆ ได้ครบถ้วนก่อน
  2. วัดผลด้วยตัวเลขจริงเสมอ เพื่อให้รู้ว่าสิ่งที่ทำไปนั้นได้ผลจริงหรือไม่
  3. ถ้าผลลัพธ์ยังไม่ดีพอ ให้ยอมรับความจริง แต่ถ้าโครงสร้างมันดีแล้ว ให้เก็บไว้เป็นรากฐานสำหรับพัฒนาต่อในเวอร์ชันถัดไป

การเป็นโปรแกรมเมอร์ที่ดีไม่ได้วัดกันที่ว่าคุณทำฟีเจอร์ได้เร็วแค่ไหน แต่วัดกันที่ว่าคุณเข้าใจเหตุผลเบื้องหลังการตัดสินใจของคุณมากน้อยเพียงใด และพร้อมที่จะเรียนรู้จากความผิดพลาดเพื่อก้าวต่อไปได้ดีกว่าเดิมหรือไม่ครับ


ที่มา: We Shipped an Optimization Our Own Benchmark Said Not To — DEV Community

แชร์บทความ

Facebook X LINE

บทความที่เกี่ยวข้อง

จัดการเซิร์ฟเวอร์ผ่าน VS Code ให้ง่ายขึ้นด้วย Easy SSH พร้อมฟีเจอร์โหลดไฟล์ผ่านคลิกเดียว

จัดการเซิร์ฟเวอร์ผ่าน VS Code ให้ง่ายขึ้นด้วย Easy SSH พร้อมฟีเจอร์โหลดไฟล์ผ่านคลิกเดียว

เบื่อไหมที่ต้องสลับหน้าจอไปมาเพื่อจัดการเซิร์ฟเวอร์? มาลองใช้ Easy SSH ปลั๊กอิน VS Code ที่ช่วยให้คุณรีโมทผ่าน Terminal ได้สะดวก แถมโหลดไฟล์ได้ง่ายแค่กด Ctrl+click

ที่มา: DEV Community

1 hour ago 11 นาที
2 views
วิธีดึงข้อมูลราคาจาก Google Hotels ด้วย API สำหรับนักพัฒนา

วิธีดึงข้อมูลราคาจาก Google Hotels ด้วย API สำหรับนักพัฒนา

อยากทำแอปท่องเที่ยวแต่ดึงข้อมูลราคาจาก Google Hotels ไม่ได้? มาดูวิธีใช้ Apify Actor ช่วยดึงข้อมูลแบบอัตโนมัติด้วย Python ง่ายๆ ไม่ต้องกลัวเว็บพัง

ที่มา: DEV Community

4 hours ago 8 นาที
3 views
วิธีเช็กความพร้อมโปรเจกต์ก่อนปล่อยงานจริงด้วย ReleaseReady

วิธีเช็กความพร้อมโปรเจกต์ก่อนปล่อยงานจริงด้วย ReleaseReady

เคยไหม? โค้ดรันได้ในเครื่องแต่พอปล่อยจริงกลับพัง! มาดูวิธีตรวจสอบความพร้อมของโปรเจกต์ก่อนอัปขึ้น GitHub ด้วยเครื่องมือ ReleaseReady กัน

ที่มา: DEV Community

8 hours ago 9 นาที
4 views