2 คะแนน โดย GN⁺ 2023-11-09 | 1 ความคิดเห็น | แชร์ทาง WhatsApp
  • แม้จะตั้งข้อจำกัด CPU ให้คอนเทนเนอร์ แต่โดยค่าเริ่มต้น Go runtime จะไม่รู้เรื่องนี้ จึงอาจสร้างเธรดตามจำนวนคอร์ทั้งหมดของโฮสต์และทำให้ latency สูงขึ้นได้
  • Go GC ส่วนใหญ่ทำงานพร้อมกับแอปพลิเคชัน แต่ในช่วง Sweep Termination และ Mark Termination จำเป็นต้องมีช่วง stop-the-world (STW) ที่หยุด goroutine ทั้งหมด
  • Linux CFS แบ่งสรรจำนวนคอร์ออกเป็นเวลา CPU ต่อวินาที โดย --cpus=4 หมายถึงให้เวลา CPU แก่คอนเทนเนอร์เทียบเท่า 4 วินาทีในทุก ๆ 1 วินาที
  • หากรันคอนเทนเนอร์ที่จำกัด 4 คอร์บนโฮสต์ 16 คอร์ Go อาจวาง goroutine บน OS thread ได้ 16 ตัว ทำให้ STW ยาวขึ้นหลังใช้ CPU quota หมด
  • เมื่อตั้ง GOMAXPROCS ให้ตรงกับข้อจำกัด CPU ของคอนเทนเนอร์ ในตัวอย่าง GC cycle ลดจากต่ำกว่า 2.5ms เหลือต่ำกว่า 1ms และ STW ลดลงเหลือประมาณ 26μs

ความไม่สอดคล้องกันระหว่างข้อจำกัด CPU ของคอนเทนเนอร์กับ Go runtime

  • เมื่อรันแอปพลิเคชัน Go ในคอนเทนเนอร์ ข้อจำกัด CPU เป็นกลไกที่ป้องกันไม่ให้ใช้ CPU ทั้งหมดของโฮสต์
  • ปัญหาคือโดยค่าเริ่มต้น Go runtime ไม่รับรู้ ข้อจำกัด CPU ของคอนเทนเนอร์
  • ความไม่สอดคล้องนี้ทำให้ runtime มองว่าสามารถใช้ CPU ได้มากกว่า quota จริง และอาจนำไปสู่ latency ที่สูงได้

จุดที่เกิด STW ใน Go GC

  • Garbage collector ของ Go ทำงาน พร้อมกัน กับแอปพลิเคชันเป็นส่วนใหญ่
  • อย่างไรก็ตาม ในกระบวนการ GC มีสองช่วงที่ต้องหยุด goroutine ทั้งหมด
    • ขั้นตอนที่หยุดก่อน Mark Phase เพื่อใช้ write barrier คือ Sweep Termination
    • ขั้นตอนที่หยุดอีกครั้งหลัง Mark Phase เพื่อเอา write barrier ออกคือ Mark Termination
  • ช่วง STW โดยทั่วไปอยู่ในระดับหลายสิบไมโครวินาที
  • แอปพลิเคชันตัวอย่างเป็นเว็บแอปพลิเคชันแบบง่ายที่จัดสรรหน่วยความจำจำนวนมาก และซอร์สโค้ดอยู่ที่ go-cfs-blog
  • คอนเทนเนอร์รันโดยจำกัดไว้ที่ 4 CPU
docker run --cpus=4 -p 8080:8080 $(ko build -L main.go)
  • สามารถเก็บ trace ด้วยแพ็กเกจ runtime/trace และวิเคราะห์ด้วย go tool trace
  • ในการรันครั้งนี้ GC cycle ต่ำกว่า 2.5ms แต่เกือบ 10% ของเวลานั้นเป็นช่วง STW
  • สำหรับแอปพลิเคชันที่ไวต่อ latency สัดส่วนระดับนี้ก็อาจเป็นปัญหาได้

ข้อจำกัด CPU ของ Docker และการทำงานของ Linux CFS

  • ข้อจำกัด CPU --cpus ของ Docker เป็น hard limit
  • สามารถตั้ง --cpu-shares ได้เช่นกัน แต่จะถูกบังคับใช้เฉพาะเมื่อโฮสต์ถูกจำกัดด้าน CPU เท่านั้น
    • หากโฮสต์ยังมีทรัพยากรเหลือ คอนเทนเนอร์สามารถใช้ได้มากกว่าจำนวน CPU core ที่จัดสรรไว้
    • เมื่อโฮสต์อยู่ในภาวะถูกจำกัด แอปพลิเคชันจึงจะถูกจำกัด
  • Linux Completely Fair Scheduler(CFS) ถูกนำมาใช้ใน Linux 2.6.23 และเป็น scheduler เริ่มต้นจนก่อน Linux 6.6
  • CFS เป็น proportional share scheduler โดยกำหนด weight ของโปรเซสตามสัดส่วนกับจำนวน CPU core ที่สามารถใช้ได้
    • โปรเซสที่ใช้ CPU core ได้ 4 คอร์จะมี weight เป็น 4
    • โปรเซสที่ใช้ CPU core ได้ 2 คอร์จะมี weight เป็น 2
  • CFS แบ่งเวลา CPU ออกมาแจกจ่าย
    • ระบบ 4 คอร์สามารถแจกจ่ายเวลา CPU ได้เทียบเท่า 4 วินาทีในทุก ๆ 1 วินาที
    • การจัดสรรจำนวน CPU core ให้คอนเทนเนอร์ เท่ากับการขอเวลาเทียบเท่า CPU n ตัวจาก Linux scheduler
    • --cpus=4 หมายความว่าคอนเทนเนอร์ได้รับเวลา CPU เทียบเท่า 4 วินาทีในทุก ๆ 1 วินาที

ทำไม STW จึงยาวขึ้น

  • Go runtime จะสร้าง OS thread หนึ่งตัวต่อ CPU core ตอนเริ่มต้น
  • บนเครื่อง 16 คอร์ อาจสร้าง OS thread 16 ตัว ได้โดยไม่ขึ้นกับข้อจำกัด CPU ของ CGroup
  • runtime จะ schedule goroutine ลงบน OS thread เหล่านี้
  • แม้ข้อจำกัด CPU ของคอนเทนเนอร์จะเป็น 4 คอร์ Go ก็ยังสามารถวาง goroutine บน OS thread ทั้ง 16 ตัวได้
  • ในสภาพนี้ runtime จะคาดหวังว่าสามารถใช้เวลา CPU ได้เทียบเท่า 16 วินาทีในทุก ๆ 1 วินาที
  • เวลา STW ที่ยาวเกิดจากต้องหยุด goroutine ทั้งหมด รวมถึง goroutine ที่อยู่บนเธรดซึ่งกำลังรอให้ Linux scheduler กลับมารันอีกครั้ง
  • หลังจากคอนเทนเนอร์ใช้ CPU quota ไปหมดแล้ว เธรดเหล่านั้นจะไม่ได้รับการ schedule

ตั้ง GOMAXPROCS ให้ตรงกับ CPU quota

  • Go สามารถจำกัดจำนวน CPU thread ที่ runtime จะใช้ได้ด้วย environment variable GOMAXPROCS
  • ในคอนเทนเนอร์ที่มี CPU quota เป็น 4 ให้ระบุ GOMAXPROCS=4 ควบคู่กัน
docker run --cpus=4 -e GOMAXPROCS=4 -p 8080:8080 $(ko build -L main.go)
  • เมื่อปรับ GOMAXPROCS ให้ตรงกับ CPU quota ภายใต้แอปพลิเคชันและโหลดเดียวกัน เวลา GC ก็สั้นลง
  • ใน trace พบว่า GC cycle ลดลงเหลือต่ำกว่า 1ms และช่วง STW อยู่ที่ 26μs
  • ซึ่งคิดเป็นประมาณ 1/10 เมื่อเทียบกับเวลา STW ตอนที่ไม่ได้จำกัด GOMAXPROCS
  • ควรตั้ง GOMAXPROCS เป็นจำนวน CPU core ที่คอนเทนเนอร์สามารถใช้ได้
    • เมื่อจัดสรร CPU แบบเศษส่วนให้ปัดลง
    • เมื่อจัดสรรน้อยกว่า 1 CPU ให้ปัดขึ้น
    • สูตรคำนวณคือ GOMAXPROCS=max(1, floor(CPUs))
  • automaxprocs ของ Uber เป็นไลบรารีโอเพนซอร์สที่คำนวณค่านี้จาก cgroups ของคอนเทนเนอร์โดยอัตโนมัติ
  • มีการเปิด GitHub Issue เพื่อให้ Go runtime รองรับสิ่งนี้เป็นค่าเริ่มต้น

สิ่งที่ควรตรวจสอบในบริการ Go ที่ทำงานในคอนเทนเนอร์

  • การตั้งข้อจำกัด CPU เพียงอย่างเดียวไม่เพียงพอ ต้องปรับ GOMAXPROCS เพื่อให้ Go runtime สะท้อนข้อจำกัดนั้นด้วย
  • หากคำนวณเองได้ยาก สามารถใช้ไลบรารีอย่าง automaxprocs เพื่อตั้งค่าอัตโนมัติจาก cgroups ได้
  • บริการ Go ที่ไวต่อ latency ควรตรวจสอบเวลา STW ใน GC trace เพื่อดูว่า CPU quota กับการตั้งค่า runtime ไม่คลาดเคลื่อนกัน

1 ความคิดเห็น

 
GN⁺ 2023-11-09
ความคิดเห็นจาก Hacker News
  • ปัญหาที่พบได้ร่วมกันในหลายภาษาคือแอปพลิเคชันดู /proc/cpuinfo เพื่อหาจำนวนคอร์ของเครื่อง
    แต่ภายใน Docker container หรือเทคโนโลยี container อื่น ๆ ไฟล์นี้จะดูเหมือนกับบน host ของ container ทุกอย่าง และจะแสดงรายการคอร์ทั้งหมด ไม่ว่าจะจัดสรรให้ container จริง ๆ แค่กี่คอร์ก็ตาม
    ช่วงหนึ่งเคยคิดว่า Docker อาจสร้าง /proc/cpuinfo ปลอมที่แสดงเฉพาะ “Docker CPU” ที่จัดสรรให้งานได้หรือไม่ แต่พอลองคิดดูอีกที น่าจะทำได้ไม่ดีด้วยหลายเหตุผล

    • เมื่อใช้ข้อจำกัดแบบ quota container สามารถใช้คอร์ CPU ทั้งหมดของ host ได้
      สิ่งที่ถูกจำกัดคือ ใช้งานคอร์เหล่านั้นได้นานแค่ไหน
      มีข้อยกเว้นด้วย และเอกสารอยู่ที่นี่: https://kubernetes.io/docs/tasks/administer-cluster/cpu-mana...
    • ใช้แค่ nproc และเคยเห็นใช้ใน container อื่น ๆ ด้วย เช่น bundle install -j $(nproc)
      สิ่งนี้เคารพ การจัดสรร CPU จึงให้ฟังก์ชันที่ต้องการได้
      ไม่แน่ใจว่าแอปพลิเคชันทั่วไปจะใช้ nproc เมื่อทำได้หรือไม่
      “แสดงจำนวนหน่วยประมวลผลที่พร้อมใช้งานสำหรับโปรเซสปัจจุบัน ซึ่งอาจน้อยกว่าจำนวนโปรเซสเซอร์ที่ออนไลน์อยู่ หากไม่สามารถดึงข้อมูลนี้ได้ จะแสดงจำนวนโปรเซสเซอร์ที่ติดตั้งอยู่”
      https://www.gnu.org/software/coreutils/manual/html_node/npro...
      https://www.flamingspork.com/blog/2020/11/25/why-you-should-...
    • Go ไม่ได้ทำแบบนั้น
      ตอนเริ่มต้น Go จะดูจำนวนใน CPU mask แล้วหลังจากนั้นจะไม่ดูอีก
      ใน Kubernetes CPU ที่มองเห็นได้ระหว่างที่โปรเซสทำงานอยู่อาจเปลี่ยนไปได้ จึงกลายเป็นปัญหา
    • /proc/cpuinfo ปลอมมีอยู่แล้ว: https://github.com/lxc/lxcfs
      lxcfs คือระบบไฟล์ FUSE ที่อนุมานค่าจาก cgroup แล้วจำลอง /proc เพื่อให้แอปพลิเคชันและไลบรารีไม่ต้องสนใจว่ากำลังรันอยู่ใน container หรือไม่
      ตัวอย่างเช่น /proc/uptime ควรสะท้อน uptime ของ container ไม่ใช่ของ host และ /proc/cpuinfo จะสะท้อนขีดจำกัดที่ต่ำกว่าระหว่างการผสมกันของ cpu.max กับ cpuset.cpus เป็นจำนวน CPU
      การอนุมานจำนวน CPU ทำได้ด้วย system call sched_getaffinity เช่นกัน และวิธีนี้ไม่พึ่งพา /proc/cpuinfo
      ดังนั้นจึงอาจลำบากได้ ขึ้นอยู่กับไลบรารีที่ใช้
    • เห็นแบบนี้แล้วสรุปได้ว่า container เป็น abstraction ที่หยาบ ๆ และ VMware พลาดโอกาสไป
  • คำอธิบายนี้ผิดในรายละเอียดเล็กน้อย
    จากมุมมองของ Docker ส่วนขยาย CFS cgroup มีปุ่มปรับได้หลายตัว: cfs_quota_us, cfs_period_us (ค่า default ทั่วไปคือ 100ms ไม่ใช่ 1 วินาที) และ shares
    ถ้าตั้งค่า shares จะใช้การ scheduling แบบสัดส่วนตามน้ำหนัก แต่จะมีความหมายก็ต่อเมื่อมีการแย่งทรัพยากรกัน
    สองค่าข้างหน้าจะบังคับ quota อย่างเข้มงวด
    แทนที่จะใช้ flag --cpu ของ Docker โดยทั่วไปควรใช้ --cpu-shares เพื่อเลี่ยงการบังคับ quota ที่แทบไม่มีประโยชน์
    ตามเอกสาร Linux cpu.shares คือค่าน้ำหนักของแต่ละกลุ่มในลำดับชั้นเดียวกัน, cpu.cfs_period_us คือช่วงเวลา scheduler สำหรับการพิจารณา bandwidth และค่า default คือ 100000us หรือ 100ms
    cpu.cfs_quota_us คือเวลาสูงสุดที่กลุ่มปัจจุบันสามารถรันได้ในแต่ละ cfs_period_us และค่านี้เป็นเวลารวมข้าม CPU ทั้งระบบ ดังนั้นถ้าต้องการให้ใช้ CPU 2 ตัวได้เต็มที่ ต้องตั้งเป็นสองเท่าของ cfs_period_us

    • ถ้อยคำว่า “อย่าใช้ flag --cpu ของ Docker แต่ให้…” นั้นแรงเกินไปหากไม่มีเงื่อนไขประกอบ
      มันไม่ควรถูกมองว่า “แทบไม่มีประโยชน์” อย่างเด็ดขาด
      shares กับ quota มีไว้สำหรับกรณีใช้งานที่ต่างกัน ดังนั้นควรเข้าใจกรณีใช้งานของตัวเองแล้วเลือกให้เหมาะ
    • จุดที่ควรระวังคือถ้าใช้ --cpu แอปพลิเคชันจะตรวจจับสิ่งนี้ได้
      น่าจะเป็นเพราะใช้ cpuset
      ถ้าใช้ quota แอปพลิเคชันจะตรวจจับไม่ได้ จึงมีโอกาสสูงที่จะสร้าง thread มากเกินจำเป็น
    • ผมเป็นผู้เขียนบล็อก ขอบคุณสำหรับ feedback
      จะลองทำให้ส่วนนี้ชัดเจนขึ้น
      คิดว่าอาการจะปรากฏออกมาในลักษณะนี้ แต่ควรใช้ถ้อยคำให้ชัดกว่านี้
    • คนที่ใช้ Kubernetes ไม่ได้ปรับหรือเปลี่ยนการตั้งค่าเหล่านี้เอง
      แอปพลิเคชันต้องทำงานอย่างถูกต้อง
  • ถ้าใช้ CPU reservations แทน CPU limits ก็ไม่จำเป็นต้องปรับแต่งแบบนี้: https://home.robusta.dev/blog/stop-using-cpu-limits
    จริง ๆ แล้ว CPU reservations ก็เป็น limits โดยปริยาย เพียงแต่ประกาศเป็นขีดจำกัดและการรับประกันแบบแฝง
    ดังนั้นปล่อยให้ Go runtime ใช้ CPU ทั้งหมดที่มีได้ และถ้าเกิดการแย่งใช้ CPU ก็ให้ Linux scheduler จำกัดตาม reservations ที่ประกาศไว้ก็พอ

    • เหตุผลที่ตั้ง limits ไม่ใช่เพราะกลัวว่า pod ใด pod หนึ่งจะไปกระทบ pod อื่น
      แต่เพราะไม่อยากชินกับสภาพที่ใช้ CPU ส่วนเกิน ที่ไม่ได้รับการรับประกันได้
      เมื่อมี pod อื่น ๆ เข้ามาเติมเต็ม node มากขึ้นเรื่อย ๆ pod ที่เพิ่งรันได้ดีเมื่อครู่อาจช้าลงอย่างกะทันหัน
      การใช้ limits จะจำลองพฤติกรรมเดียวกันนี้ และช่วยให้เตรียมรับมือด้วยการวางแผน capacity ที่ถูกต้องได้
      ไม่ใช่วิธีเดียว แต่เป็นวิธีที่ง่ายที่สุด
    • ผมรันบางอย่างบนคอนฟิก 128 คอร์ โดยตั้ง CPU limits ให้สูงกว่า request มาก แต่ก็ยังตั้งไว้เพื่อกันไม่ให้บางอย่างพุ่งจนควบคุมไม่ได้
      อยากรู้ประเด็นนี้เพิ่มเติม แต่บทความที่ลิงก์ดูเหมือนจะพูดแค่ว่าคนคิดว่าต้องมี limit เพื่อรับประกัน CPU ให้ทุก pod
    • ในชุมชน Kubernetes รู้สึกเหมือนมีการถกเรื่องนี้กันทุกสองสัปดาห์
      บทความนี้ไม่ได้ผิดในตัวมันเอง และโดยรวมค่อนข้างเป็น content marketing แต่ข้อสรุปกว้างเกินไปและมองข้ามเหตุผลดี ๆ หลายข้อในการตั้ง limits
      บทความอื่นจากที่เดียวกันบางอันก็ผิดไปเลย: https://home.robusta.dev/blog/containers-dont-use-chroot
      มี workload ที่ใช้ burst capacity จนหมดทั้งที่ได้ประโยชน์เพียงเล็กน้อย และบางครั้งก็ควรให้ความสำคัญกับ burst capacity ของ HTTP server มากกว่า cronjob ที่ต้องจบภายในเวลาที่กำหนด
      เคยมีกรณีที่นักพัฒนาไม่อัปเดต requests ทั้งที่ความต้องการของแอปเพิ่มขึ้น แล้วพอเวลา CPU ว่างลดลงกะทันหันก็เกิด incident
    • reservations ไม่ใช่ limits แต่เป็น ข้อจำกัดปริมาณการใช้ CPU ขั้นต่ำที่รับประกัน
      ตามทฤษฎีคือทรัพยากรขั้นต่ำที่รับประกัน แต่ถ้ามีคอนเทนเนอร์ที่งานหนักหลายตัวรันอยู่บนโฮสต์เดียวกัน tail latency และ latency เฉลี่ยอาจเพิ่มขึ้นผิดปกติได้
      latency ตอน CPU utilization 50% กับ 90% บน EC2 instance 4 คอร์นั้นต่างกันพอสมควร
      ในกรณี reservations ก็คล้ายกัน แม้แต่ละคอนเทนเนอร์จะได้รับ reservation ของตัวเอง แต่เพราะมี process อื่นที่งานหนักบนโฮสต์เดียวกัน ทำให้การใช้ CPU ในเชิงสัมพัทธ์สูงมาก
    • น่าสนใจ แต่ไม่ใช่ว่าใช้กับ memory ไม่ได้หรือ?
      OOMKiller อาจมาเก็บไปได้
      ถ้าไม่มีทั้ง CPU และ memory limits ก็จะไม่ได้ Guaranteed QoS class ดังนั้นในบางจุด pod อาจถูกขับออกได้
  • ใช้คอนเทนเนอร์กับ cgroup แล้วโดน CFS scheduler เล่นงานมาหลายครั้ง
    อยากรู้ว่า scheduler ตัวใหม่คืออะไร
    มีใครที่นี่เคยใช้ใน production cluster ไหม?
    ตอนนี้เสียคอร์ทิ้งมาเกือบ 20 ปีแล้ว: https://people.ece.ubc.ca/sasha/papers/eurosys16-final29.pdf

    • ปัญหาตรงนี้ไม่ใช่ scheduler
      ปัญหาคือคอนเทนเนอร์ตั้ง resource limit ไว้ แต่ Go ซึ่งเป็น process ภายในคอนเทนเนอร์ ไม่ตรวจสอบฟีเจอร์ของระบบปฏิบัติการที่ใช้ตั้ง limit นั้นตอนคำนวณปริมาณ parallelism ที่ใช้งานได้
    • https://kernelnewbies.org/Linux_6.6#New_task_scheduler:_EEVD...
  • นอกจาก GOMAXPROCS แล้ว Go รุ่นล่าสุดยังมี GOMEMLIMIT ด้วย
    ใช้ https://github.com/KimMachineGun/automemlimit เพื่อกำหนด limit นี้อัตโนมัติได้ คล้ายกับ https://github.com/uber-go/automaxprocs

  • ปีก่อนตอนทำงานเก่าผมเป็น platform engineer ดูแล on-premises Kubernetes cluster และโครงสร้างพื้นฐาน CI/CD pipeline แล้วพบเรื่องนี้
    เคยเห็นว่าความไม่ตรงกันระหว่าง CPU จริงกับ CPU ที่จัดสรรทำให้เกิดปัญหา โดยเฉพาะ CPU throttling แต่หาวิธีแก้ที่ขยายผลได้และมีผลกับ deployment ของ Go ทั้งหมดใน cluster ได้ยาก
    การให้ developer ทุกคนในหลายร้อยโปรเจกต์ใส่ dependency autoprocs ไม่ใช่ทางเลือก
    อีกทางคือปรับ CPU request/limit ทั้งหมดให้เป็นจำนวนเต็ม แล้วใส่ค่านั้นเป็น environment variable GOMAXPROCS ใน Kubernetes manifest ซึ่งก็ยุ่งยากและทำจริงไม่ได้
    สุดท้ายจึงใส่ตัวแปร GOMAXPROCS ให้เฉพาะบางแอปพลิเคชันที่ใช้หลายเธรดหนัก ๆ และได้ผลดีขึ้น แต่ยังไม่เจอวิธีแก้ที่ใช้ได้กับ deployment ทั้งหมดในสถาปัตยกรรม microservices ที่แต่ละโปรเจกต์มีความต้องการ CPU ผันผวนมาก

    • เรื่องนี้ไม่มีคำตอบเดียว
      ถ้าจำกัด GOMAXPROCS อาจเกิด ปัญหา latency รุนแรง เมื่อ traffic ไหลเข้า process จำนวนมากและการ queuing เรียบง่าย
      ไม่ว่าคุณจะคิดอย่างไรเกี่ยวกับเวลาที่ process จะใช้โดยเฉลี่ย จริง ๆ แล้วการตั้ง GOMAXPROCS ตามค่าที่ hardware ให้มานั้นดีที่สุด
    • สามารถนิยาม mutating webhook เพื่อ inject GOMAXPROCS เข้าไปในคอนเทนเนอร์ของทุก pod ได้
  • ในฐานะคนที่ไม่คุ้นกับ Docker หรือ Go อยากรู้ว่าพฤติกรรมนี้ตั้งใจให้เป็นแบบนี้หรือไม่
    ทำให้ทีม Go รับรู้ CGroups limit ได้ไหม?
    runtime อื่น ๆ ก็ทำงานคล้ายกันหรือเปล่า?

    • ค่อนข้างมั่นใจว่า .NET ก็ต้องจัดการปัญหานี้ และจำได้ว่า Java ก็เคยมีหรือยังมีปัญหาอยู่
      หรือหมายถึง runtime อย่าง containerd?
    • เคยเจอปัญหาเดียวกันใน JVM
      ตอนนั้นเป็น Scala
  • ยังมี เทคนิค GC ที่ทำให้การหยุดชั่วคราวสั้นลงได้ด้วย
    เช่น ทำงานที่ต้องทำระหว่างหยุดชั่วคราวไปพร้อมกันก่อน แล้วค่อยทำซ้ำอีกครั้งที่ safe point
    ความคาดหวังคือด้วยการทำงานแบบพร้อมกัน งานที่ safe point จะกลายเป็นแค่การตรวจสอบง่าย ๆ ว่า “ไม่มีอะไรต้องทำ”
    หากทำงานซ้ำเป็นสองเท่า throughput ของ GC ก็อาจแย่ลงได้

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