ก้าวแรก HPC #02 : C++

เกริ่นนำ

หนึ่งในภาษาที่นิยมใช้งานทางด้าน HPC นั่นคือ C++ แต่ก็ไม่ใช่ว่าเขาใช้กันเราก็เอาบ้าง มีบ่อยครั้งที่เขาใช้กันผมก็ไม่เอาด้วย ผมก็เลยมาขอแจงรายละเอียดให้ท่านอ่านกัน ถ้าท่านจริตเดียวกับผมก็มาใช้กันครับ แต่ถ้าคิดว่าไม่ใช่ อย่าฝืนนะครับตัวเลือกยังมีอีกมาก

หน้าประวัติศาสตร์ภาษา C++

เป็นธรรมเนียมของผมก่อนจะคุยเรื่องอะไรต้องมีการการเท้าความไล่ประวัติกันก่อน อยากรู้ที่ไปก็ต้องรู้ที่มา จะได้เห็นภาพรวมของทั้งหมด C++ นั้นเกิดหลังจาก C มาประมาณ 10 ปี โดยผู้คิดค้นคือ Bjarne Stroustrup เป็นวิทยานิพนธ์ระดับปริญญาเอก โดยผสม C เข้ากับ Simula กลายเป็น “C with Classes” และก็เปลี่ยนมาเป็น C++ ในที่สุด จากนั้นก็ผนวกเข้ากับ STL (The Stanard Template Library) ซึ่งถือว่าเป็น Class Library ที่ใหญ่ที่สุดในยุคนั้น ผมเองก็เริ่มรู้จัก C++ ครั้งแรกก็น่าจะปี 1990 จำได้ว่าไปอ่านจะเป็นบทความใน Dr. Dobbs Journal งงมากมายครับทั้ง Inheritance/Polymorphism กว่าจะเข้าใจแนวคิดมันจริงๆ ก็หลังจากนั้นไปเกือบสิบปี  C++ เองก็เช่นเดียวกับภาษาคอมพิวเตอร์อื่นๆ ที่ยอดนิยม เมื่อดังแล้วก็ต้องมีคณะกรรมการไปกำหนดมาตรฐาน C++ จึงมีมาตรฐานแรกคือ C++98 และมาตรฐานที่สองคือ C++03  ซึ่งก็คือ ปี 1998 และ 2003 ตามลำดับ

ผมเองกับ C++ ผมไม่ค่อยชอบมากนักเมื่อเทียบกับภาษาอย่าง C# ในยุคต้นทศวรรษ 2000 มันดูติดๆ ขัดๆ ไปหมด และดูเหมือนเป็นภาษาที่ตายแล้ว เพราะไม่มีมาตรฐานใหม่ๆ เกิดขึ้นมา จนกระทั่งเกือบสิบปีให้หลัง ก็มีมาตรฐานใหม่ออกมา C++11  น่าสนใจมากครับ หลายสิ่งหลายอย่างที่ดูติดๆ ขัดๆ ถูกนำไปแก้ไขให้ดูดีขึ้นมากครับ ถึงแม้ว่ายังมีบางส่วนที่ยังติดๆ ขัดๆ อยู่ แต่ก็อยู่ในเกณฑ์ที่ยอมรับได้ครับ ณ วันที่เขียนบทความนี้ C++14 เพิ่งประกาศตัว มีการเปลี่ยนแปลงไม่มากนัก ในบทความที่ผมจะเขียนต่อๆ จะยึดมาตรฐาน C++11 และ C++14 เป็นหลักครับ

ปรัชญาของ C++

C++ นั้นสืบสายมาจาก C ซึ่ง C เน้นที่ประสิทธิภาพเป็นหลัก การออกแบบภาษา C นั้นคำนึงเสมอว่าจะแปลเป็นภาษาเครื่องที่มีประสิทธิภาพได้อย่างไร ซึ่งปรัชญาข้อนี้สืบทอดมาถึง C++ ด้วย แต่เนื่องจาก C++ นั้นมีความซับซ้อนกว่า C มาก ดังนั้นการที่จะรักษาประสิทธิภาพให้ได้นั้นเป็นไปได้ยาก ไม่น่าแปลกใจที่บางทีโครงสร้างของภาษาจะดูไม่ค่อยเนียนตาดูขัดๆ เนื่องจากต้องการที่จะรักษาประสิทธิภาพสูงสุดให้ได้นั่นเอง เรื่องนี้ถือได้ว่าเป็นดาบสองคม ทีม C++ จะไม่ยอมเพิ่มความสามารถใดๆ เข้าสู่ภาษา ถ้ายังหาวิธีในการทำให้มันมีประสิทธิภาพไม่ได้ ดังนั้นชนิดตัวแปรที่ใช้ในงานการเงินอย่าง money ที่มีทศนิยม 4 หลักจึงยังไม่มีใน C++ แม้กระทั่งปัจจุบัน แต่ก็ใช่ว่า C++ จะไม่สามารถทำได้นะครับ เพียงแค่เราต้องไปหา Library ภายนอกมาใช้งาน อยากได้อะไรก็มีหมด เพียงแต่ต้องไปหาเพิ่มเท่านั้น ด้วยเหตุผลเรื่องประสิทธิภาพนี้เอง จึงเป็นปัจจัยหลักที่ผมเลือกมาใช้งานในเว็ปนี้

เทียบกับ Java และ C#

เมื่อพูดถึง C++ ก็อดเทียบกับ Java และ C# ไม่ได้ ภาษาทั้งสองนี้ถือว่าสืบเชื้อสายมาจาก C++ โดยการเอา C++ มาปรับปรุง ปฏิเสธไม่ได้เลยครับว่า C++ เสียสาวกไปให้แก่ภาษาทั้งสองนี้ไม่น้อย ผมเองก็หนีไปพักใหญ่ๆ ครับ จนกระทั่งเมื่อ C++11 ออกมา มันได้ใจผลเลย วันนี้ก็กลับมาสู่ C++ อีกครั้งครับ ขอใช้พื้นที่ตรงนี้คุยถึงเรื่องความเชื่อของสามภาษานี้หน่อยครับ จะได้ลบล้างความเชื่อผิดๆ หลายประการ มาไล่เป็นข้อๆ กันเลยครับ

ความเชื่อที่ 1: Garbage Collection

เราสามารถใช้ C/C++ จองเนื้อที่หน่วยความจำจาก OS ได้โดยตรง โดยใช้ malloc() หรือ new ซึ่งนับได้ว่ามีประสิทธิภาพสูง แต่ก็มีข้อเสียครับ คือผู้เขียนโปรแกรมต้องรับผิดชอบในการขอปลดปล่อยหน่วยความจำจาก OS เองเมื่อเลิกใช้ มิฉะนั้นถ้าลืม หน่วยความจำนั้นจะถูกจองค้าง เอาไปใช้งานอื่นไม่ได้ จนกว่าจะปิดเครื่องคอมพิวเตอร์ จึงเป็นที่มาของ “หน่วยความจำรั่ว” หรือ memory leak เมื่อรั่วมากๆ ความเร็วของเครื่องจะเริ่มลดลง เพราะมีการสลับ paging กับ harddisk บ่อยขึ้น งานการไม่ค่อยได้แต่ Harddisk ทำงานหนัก จนกระทั่งถึงจุดหนึ่ง ระบบค้างไปในที่สุดไม่มีการตอบสนอง นั่นเป็นเหตุผลว่า ทำไม Windows รุ่นเก่าๆ เมื่อใช้งานตอนเช้าใช้ได้ดี แต่ใช้ได้ไม่ถึงเย็นครับ เครื่องจะเริ่มอืดมาก ต้อง boot ซักรอบ ก็ด้วยเหตุผลนี้ครับ

เมื่อมาถึงยุคของ Java เราไม่สามารถขอจองเนื้อที่หน่วยความจำได้จาก OS โดยตรง ต้องทำผ่าน JVM โดยที่ JVM จะไปขอจองเนื้อที่จาก OS เป็นพื้นที่ขนาดใหญ่แบบขายส่ง แล้วมาซอยแบ่งปลีกให้แต่โปรแกรมเท่าที่ร้องขอ JVM จะตรวจสอบดูว่าหน่วยความจำไหนไม่ได้ใช้ก็จะปลดอัตโนมัติโดยที่ผู้เขียนโปรแกรมไม่ต้องยุ่งยากจัดการ แถมยังมีตัวเก็บขยะ ในกรณีที่หน่วยความจำเริ่มไม่พอใช้ ตัวเก็บขยะก็จะไปย้ายตำแหน่งของข้อมูลที่จองใหม่ให้มันติดกัน (เหมือนการ defrag harddisk) เมื่อหน่วยความจำติดกันแล้วจะมีพื้นที่ข้างล่างเหลือสำหรับงานต่อไป ดังนั้น Java จึงต้องคิดตัวแปรชนิดใหม่ขึ้นมา เรียกว่าตัวแปร reference ซึ่งต่างกับ Pointer ในภาษา C/C++ กล่าวคือ Reference จะไม่ยอมให้เราเข้าถึงเลขตำแหน่ง address ของข้อมูล ทั้งนี้เพราะตำแหน่งเอาแน่ไม่ได้ เมื่อถูกตัวเก็บขยะจัดการ อาจย้ายไปยังตำแหน่งอื่นก็ได้

นี่กระมังที่เป็นมนตรามหาระรวยของ Java/C# การมีการจัดการหน่วยความจำที่ดีแบบนี้ ทำให้เครื่องคอมพิวเตอร์ไม่จำเป็นต้องบูททุกวัน เปิดได้เป็นเดือนๆ ทำให้เด่นกว่าภาษา C/C++ ที่มี memory leak  ซึ่งเหล่านี้เป็นความเชื่อ แต่ก็ฝังหัวกันมานานครับ ผมเลยขอชี้แจงดังนี้

ความเชื่อแรก: เมื่อภาษา C/C++ ระหว่างการทำงานเกิด error โปรแกรมหยุด หรือจะหยุดโปรแกรมโดยลืม free หน่วยความจำ จะทำให้เกิด memory leak  เรื่องนี้บอกได้เลยครับ ความเชื่อผิดครับ งานนี้เป็นเรื่องของ OS ล้วนๆ ครับ OS นั้นรู้อยู่แล้วว่าใครจอง ถ้าคนจองเกิดตายกระทันหันหรือตายด้วยเหตุธรรมชาติก็ตาม OS ต้องปลดหน่วยความจำที่จองค้างไว้ทั้งหมดนั้นได้ด้วยตัวเองครับ Windows ตั้งแต่ XP เป็นต้นมา ปลดหน่วยความจำได้เองครับ ส่วนตระกูล UNIX ทำได้ก่อนนั้นนานแล้ว ทำให้มีความเชื่อผิดๆ ซ้อนเข้ามาว่า Linux เสถียรกว่า Windows  ถ้ายุคก่อน XP มันก็จริงแท้แน่นอนครับ แต่หลังจากนั้นก็ไม่อาจพูดอย่างนั้นอย่างชัดเจนได้ครับ

ความเชื่อที่สอง:  Java/C#:  ภาษาที่มี Garbage Collector จะไม่มี memory leak เรื่องนี้มโนล้วนๆ เลยครับ ไม่ leak เมื่อจบโปรแกรมจริงครับตามความเชื่อข้อแรก แต่ระหว่างรันโปรแกรมมัน leak ได้ครับ ตราบได้ reference มีชีวิตอยู่ หน่วยความจำที่จองก็ยังคงอยู่ ไม่ว่าจะไม่ได้ใช้งานมันแล้วก็ตาม สำหรับงานเล็กๆ อาจจะไม่ต้องใส่ใจ แต่ในงาน HPC บ่อยครั้งที่จำเป็นต้องใช้หน่วยความจำขนาดใหญ่ ถ้ารั่วก็จะไม่มีหน่วยความจำเหลือพอใช้อีก ดังนั้นเราจึงจำเป็นต้องจัดการปลดปล่อยหน่วยความจำเองครับ ว่ากันถึงจุดนี้ บางท่านก็บอกว่า อ้าวแบบนี้ จะจัดการหน่วยความจำแบบ JVM หรือไม่จัดการก็ไม่ต่างกันใช่ไหม ไม่ใช่ครับ Java/C# ยังมีจุดเด่นกว่าก็คือ เมื่อตัวแปร Reference หมด Scope มันจะตายลง สิ่งที่มันชี้ หน่วยความจำที่มันจอง ถ้าไม่มีใครชี้ร่วม มันจะถูกปลดปล่อยอัตโนมัติ  ซึ่งนับได้ว่าเป็นจุดเด่น ใน C++ แก้ไขปัญหานี้โดยที่ Stroustup เสนอแนวคิดในการเขียนโปรแกรมที่เรียกว่า Resouce Acquisiton Is Intitialization (RAII) กล่าวคือ ให้จองเนื้อที่ในตอนเริ่มต้น และตอนท้ายในหน่วยโปรแกรมหน่วยเดียวกันให้จัดการปลดปล่อยเสีย เขียนให้เป็นนิสัย แบบนี้จะไม่เกิดปัญหา ซึ่งแนวคิดนี้ได้รับการพัฒนาต่อเนื่องมาจน C++11  โดยมีแนวคิดของ smart pointers ที่จองหน่วยความจำอย่างเดียว พอตายลงมันจัดการปลดปล่อยให้เองอัตโนมัติเหมือนใน Java/C# เลย รวมถึง lock ต่างๆ ก็ปลดให้เองด้วย C++ ไปไกลแล้วนะครับ

มาถึงจุดนี้จะเห็นได้ว่า ในเรื่องการจัดการหน่วยความจำของ Java/C# ตัวเก็บขยะเท่านั้นที่ถือได้ว่าเป็นจุดเด่นที่ยังคงหลงเหลืออยู่ ซึ่งอย่างที่กล่าวแล้วคือ ตัวเก็บขยะจะทำให้เราได้หน่วยความจำเพิ่มขึ้น แต่ไม่ใช่ของฟรีนะครับ เมื่อตัวเก็บขยะทำงาน มันก็ต้องเสียเวลาในการทำงาน แบ่ง CPU time ไปทำ นี่ก็เสียแรก ส่วนเสียที่สอง ในระหว่างทำงาน โปรแกรมจะถูก lock มิฉะนั้นการอ้างอิงจะมั่วไปหมด จนจัดหน่วยความจำเสร็จ โปรแกรมต่างๆ จึงจะกลับเข้าไปทำงานได้ แม้ว่าเป็นเหตุการณ์ไม่เกิดบ่อยนัก แต่เมื่อเกิด ก็อาจจะเป็นประเด็นได้ โดยเฉพาะอย่างยิ่งงาน real time ที่ต้องการตอบสนองทันที หรือแม้จะเป็น Soft Realtime อย่างเกมส์คอมพิวเตอร์ ระหว่างตัวเก็บขยะทำงาน เกมส์อาจสะดุดทำงานไม่ต่อเนื่องทำให้เสียอารมณ์ได้

ความเชื่อที่สอง : Java/C# ช้ากว่า C++ มาก

ความเชื่อว่า  C++ เร็วกว่า Java/C# มากนั้น เรื่องนี้เป็นเรื่องเข้าใจผิดครับ เราจะเห็น Benchmark ออกมาหลายค่าย บางค่ายก็ได้ผลลัพธ์มาว่า Java เร็วกว่า C++ ไม่น่าเชื่อใช่ไหมครับ เพราะที่ผ่านมามา Java ขึ้นชื่อว่าเป็นภาษาที่ช้า จริงครับช้าจริงครับ แต่เป็นอดีต ปัจจุบันเร็วมากแล้วครับ แต่ชื่อเสียยังค้างอยู่  เมื่อเคยติดคุกแล้วยากที่จะทำให้เชื่อว่ากลับตัวแล้ว มาครับเรามาดูที่มาจริงๆ กัน ขอย้อนอดีต สมัยเครื่องคอมพิวเตอร์ยังช้าอยู่ เวลาเริ่มรันโปรแกรม Java จะเสียเวลาในการแปลง bytecode ให้กลายเป็นภาษาเครื่อง เวลาในส่วนนี้เห็นๆ เลยครับมากินเวลามาก การแปลจะแปลแบบให้เนียนให้เร็วก็ไม่ได้ เวลามันจำกัดอยู่ แต่ในยุคปัจจุบันที่เครื่องคอมพิวเตอร์เร็วขึ้นมาก เวลาในส่วนเริ่มต้นนี้ก็แคบลง จนแทบไม่รู้สึกแล้วครับ และเมื่อแปลเป็นภาษาเครื่องแล้ว มันก็ไม่ต่างกับ C++ มากแล้วแล้วครับ ก็ทำงานระดับภาษาเครื่องด้วยกัน สิ่งที่เจ๋งสำหรับ Java/C# ก็คือมันจะแปลเป็นภาษาเครื่องให้เข้ากับ CPU ที่คุณใช้ปัจจุบันเลย จึงสามารถรีดกำลังเครื่องให้ทำงานได้เร็วมาก ผิดกับภาษา C/C++ ส่วนมากเราก็แปลกันแบบ default ไม่ได้ generate code ที่ดีที่สุดสำหรับ CPU เพียงเท่านี้ C++ ก็เสียเปรียบแล้วครับ แถมยังต่อให้ตั้งใจอยากจะปรับให้เลิศที่สุด คอมไพเลอร์ที่ใช้กลับไม่มี option ให้ปรับเสียอีก ถ้าจะเอา Compiler ที่เจ๋งที่สุดบน Windows ในการ generate code สำหรับ CPU Intel ก็ไม่หนี Intel C++ Compiler ของผู้ผลิต CPU เองราคาก็หลายตัง เห็นได้ว่าเท่านี้ C++ ก็เสียเปรียบแล้วครับ แถม Java ยังมีจุดเด่นอีกหลายอย่างเช่น

  • การจองเนื้อที่ทำได้เร็วกว่า เพราะจัดการเอง ไม่ต้องไปถึง OS
  • Math Library บางส่วนเขียนดีกว่าของ C++ จึงเร็วกว่า
  • Optimization ของ Java มีความ “ก้าวร้าว” มาก อาจจะข้ามสิ่งที่ทำแล้วไร้ค่าเช่นวนเฉยๆ หรือทำซ้ำแล้วซ้ำอีกโดยทำเพียงครั้งเดียว

แต่ที่กล่าวไปทั้งหมดนี้ ไม่ได้หมายความว่า Java เร็วกว่าภาษา C++ ถ้าดูภาพรวมที่ทำงานจริงๆ ไม่ใช่ Benchmark ง่ายๆ C++ ยังเร็วกว่ามากครับ และถ้าเขียนแบบเข้าทาง C++ สมัยใหม่่จริงๆ ประสิทธิภาพจะยิ่งเร็วขึ้นมากครับ  ว่ากันว่าตัวเลขที่ยอมรับกันอย่างกว้างขวางคือ C++ เร็วกว่า Java 2 เท่า ว่าแต่ว่า แล้ว C# หละ ไม่เห็นพูดถึงเลย เรื่องนี้จริงๆ แล้วก็เป็นความเชื่อผิดๆ อีกข้อครับ ว่า Compiler ของ Microsoft ประสิทธิภาพสูง จริงๆ ไม่ได้ดีกว่าที่เป็นของฟรีครับ GCC  ยิ่ง Intel ยิ่งไม่ต้องพูดถึงเลย  Microsoft มีดีที่เครื่องมือครับ IDE Visual Studio เจ๋งมาก และ Debugger ที่ยอดเยี่ยม ดังนั้น C# จึงไม่ได้เป็นภาษาที่เน้นความเร็วนัก ออกแนวไปทางธุรกิจมากกว่า ไม่เหมือน Java ที่ยังคงพุ่งเป้าไปสู่ HPC ด้วย

แม้ว่าความเร็วของ Java ทำได้ดีขึ้น แต่จุดอ่อนที่ยังแก้ไม่ตกของ JVM ก็คือการกินหน่วยความจำ RAM เยอะมากครับ อันนี้แหละครับข้อเสียจริงๆ ของ JVM วึ่งต่างกับ C++ ที่เป็น Native มากครับ แต่ถ้ายอมรับได้ก็ไม่มีปัญหาครับ และการกินหน่วยความจำมากนี้เองจึงเป็นฟางเส้นสุดท้ายให้ผมเลือกใช้ C++

เทียบกับ C

C++ เกิดมาในยุคที่เครื่องคอมพิวเตอร์ที่ความเร็วจำกัดมาก ความนิยมในตอนแรกจึงน้อย เนื่องจากเมื่อเทียบกับ C แล้วมันดูอ้อมค้อมกว่ามาก เหมือนมันมีชั้น indirect Pointer เพิ่มขึ้นมาหนึ่งชั้น แต่ในยุคปัจจุบันชั้น indirect pointer เป็นเรื่องเล็กน้อยบนเครื่อง PC แทบไม่มีผลต่อความเร็ว คนสนใจความสามารถที่เพิ่มขึ้นมากกว่า ทำให้ภาษา C ลดบทบาทลง แต่มีงานอีกสาขาหนึ่งที่ยังนิยมใช้ C อยู่มากคืองาน Embedded โดยเฉพาะอย่างยิ่งขนาดเล็ก ก็พวกเขียนโปรแกรมไมโครคอนโทรลเลอร์นั่นแหละ ทั้งนี้เป็นเพราะความเชื่อที่ว่า C นั้นเร็วกว่า C++ นั่นเอง แต่ถ้าผมบอกว่านั่นเป็นแค่ “ความเชื่อ” หละ คุณจะเชื่อผมไหม

ถ้าคุณพูดว่า C เร็วกว่า C++ แล้วเกิดคำพูดคุณไปถึงหูของ Bjarne Stroustup ผู้สร้าง C++ เชื่อว่าเขาจะจ้องหน้าคุณนิ่งๆ แล้วถามว่าคุณเขียนยังไง ไม่แปลกใจเลย ถ้าคุณเอา Source code ที่เขียนด้วย C มาคอมไพล์โดยใช้ C++ จะพบว่าความเร็วไม่ได้แตกต่างกัน แต่ถ้าทำอย่างนั้นแล้วบอกว่า C กับ C++ นั้นเร็วไม่ต่างกันมันจะดูเป็นเดี่ยวไมโครโฟนเกินไป คุณอาจจะถามว่าแล้วที่สร้างเป็น class โน่นนี่นั่นมันมีการใช้ indirect pointer แบบนี้ C++ ย่อมต้องช้ากว่าอย่างแน่นอน เพื่อพิสูจน์ให้เห็นครับ ผมเลยทดสอบโดยการให้คอมไพเลอร์แปลและแสดงผลลัพธ์เป็นภาษา Assembly เพื่อดูว่ามันสร้างอะไรออกมา อ้อมมากอ้อมน้อยแค่ไหน ผมเลือกที่จะสร้าง Code Cortex-M0 ของ ARM เพราะมันอ่านง่ายดี เป็น RISC ถ้าเป็น x86 จะซับซ้อนมากมายเป็น CISC อ่านยาก ก็เลยต้องใช้เครื่องมือ Keil รุ่นฟรี ลองเขียนโปรแกรมภาษา C โดย

เอามาแปลเป็น Assembly เอาเฉพาะบรรทัดที่ 10 จะได้ผลลัพธ์ดังนี้

add_c

อ่านไม่ยากใช่ไหมครับ Cortex-M จะมี Register ซึ่งก็คือตัวแปรที่ทำงานเร็วที่สุดมีชื่อขึ้นว่า r0, r1, ..    คำสั่ง MOVS คือกำสั่งกำหนดค่าในบรรทัดแรก  r1=0x14  หรือ 20 นั่นเอง  จากนั้นมันก็ BL.W  คือการ ไป call function Add ซึ่งผมจะไม่แสดง code ให้ดู แต่รับรู้ว่า ผลลัพธ์ในการบวกจะเก็บไว้ที่ r0 และถ่ายลง r4 เพื่อเอาไว้ใช้งานในบรรทัดต่อไป

จากนั้นผมก็ใช้ Keil เช่นเดิมเขียนภาษา C++ ดังนี้

เมื่อขอดู Assembly จะได้ผลลัพธ์ดังนี้ครับ

 

add_cpp

 

ผลลัพธ์น่าสนใจมากครับ เหลือแค่เพียงสองบรรทัดเท่านั้น ซึ่งเมื่ออ่านโปรแกรมก็จะเข้าใจได้ไม่ยากครับ r0, r1 เก็บค่าคงที่ 10 กับ 20 เอาไว้ ในบรรทัดแรกมันจับบวกสดเลยครับ  r2 = r0 + r1 แล้วบรรทัดต่อไปก็ r4 = r2  ซึ่งถ้าพูดจริงๆ แล้วมันก็กิน 4 บรรทัดเหมือนเดิม เพราะในนี้ไม่ได้แสดงการกำหนดค่า r0, r1  แต่หัวใจมันอยู่ที่การไม่มีการเรียก call function แต่อย่างใด คอมไพเลอร์สามารถยุบ class ทั้ง class ได้ ทั้งสองโปรแกรมผมไม่ได้ใช้งาน Optimizer โดยการกำหนด -O0 อย่าไปสนใจเลยครับว่าทำไมภาษา C มันถึงเรียกฟังก์ชันแต่ภาษา C++ กลับยุบไม่เรียกฟังก์ชัน หัวใจของเรื่องนี้มันควรอยู่ที่ว่า แม้แต่ class มันก็ยุบได้ ที่เราเขียนเป็น Class มากมาย มันอาจจะไม่เกิด Indirect Pointer อย่างที่คิด คอมไพเลอร์สมัยนี้เก่งขึ้นมากครับ  ทำให้ความต่างระหว่าง C กับ C++ แคบลงเรื่อยๆ ครับ

C++ กับ Microsoft

ทางฝั่งของ Linux นั้นไม่ต้องห่วง ยังใช้ C/C++ กันมากอย่างต่อเนื่อง ทางค่ายของ Apple ก็ผูกอยู่กับภาษาตระกูล C มาโดยตลอดเพิ่งมีแนวโน้มเปลี่ยนเป็น Swift เมื่อไม่นานมานี้ แล้วของ Microsoft หละ ทางค่ายนี้ทิ้ง C++ ไปนานแล้วครับ Visual Studio 2010 ออกมา ทำให้หลายคนช้ำใจมากครับ เครื่องมือใน IDE ที่ช่วยเติมหรือแสดงรายการคำสั่งที่มี ที่เรียกว่า syntax completion หรือที่ Microsoft เรียกว่า IntelliSense นั้น สำหรับภาษา C++ ทำไม่ได้ครับ ซึ่งในรุ่นก่อนหน้าก็ยังทำได้อยู่เลย ส่วนภาษาอื่นในชุดเดียวกันทำได้หมด ซึ่งการทำเช่นนี้เป็นการไล่แขกครับ ไล่ให้ย้ายไป C# บังคับให้ C++ เป็นพลเมืองชั้นสอง และแสดงเป็นนัยว่า C++และอาจสูญพันธุ์จากเครื่องมือของ Microsoft ไปในที่สุด

แต่สิ่งที่แปลกใจผมมากครับ ต้นเดือนกุมภาปี 2012 Microsoft จัดงานสัมมนาใหญ่สองวันตั้งชื่อว่า “Going Native” ที่ Redmond โดยเป็นการเปิดตัว C++11 โดยเชิญ Stroustrup เป็นวิทยากรคนแรกเปิดรายการ จากนั้นก็กระหน่ำ C++11 กับ Microsoft อย่างเต็มที่ทั้งสองวัน จากปากของหัวเรือใหญ่ C++ ของ Herb Sutter กล่าวว่า Microsoft ไม่ได้คิดจะทิ้ง C++ กลับยังสนับสนุน C++ ให้เต็มรูป ผลลัพธ์เห็นได้ชัดครับว่า Visual C++ นั้นมีการปรับปรุงให้รองรับ C++11 ให้เต็มรูปมากที่สุด โดย Visual C++ 2012 มีการปรับปรุง 4 ครั้ง และ Visual C++ 2013 มีการปรับปรุง 3 ครั้ง ทั้งนี้ ณ วันที่พิมพ์บทความนี้ ก็ยังรองรับ C++11 ไม่ครบครับ แต่ก็เห็นความตั้งใจจริงครับ  ย้อนกลับมาเรื่องงานสัมมนาครับ ปี 2013 ก็จัดอีก แสดงว่าเอาจริง

เรื่องนี้น่าสนใจครับ Microsoft คิดอย่างไรถึงเอา C++ กลับมา เอากลับมาใช้กับอะไร ตลาดเดิมที่ C++ ยังอยู่บน Windows ก็คือเกมส์ครับ ถ้าใครต้องการใช้ DirectX ส่วนมากก็ต้องมาที่ Visual Studio อยู่แล้ว ส่วนตลาดการเขียนโปรแกรมอื่นๆ ของ Microsoft ก็พุ่งเป้าไปที่ .net มาเป็นสิบปีแล้วครับ ก็คงไม่เปลี่ยนในระยะเวลาอันใกล้ แต่ก็มีตลาดหนึ่งครับที่ Microsoft ยังแพ้เขา และถ้าแพ้อย่างนี้ไปเรื่อยๆ อาจจะถึงสูญพันธุ์และกระทบกับตลาด PC ซึ่งเป็นกล่องดวงใจของ Microsoft ก็ได้ นั่นคือตลาด smartphone และ tablet นั่นเอง ซึ่ง Microsoft พบปัญหาคือ OS ของตัวเองไม่เหมาะกับอุปกรณ์ดังกล่าว

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

พอคุ้นอะไรทำนองนี้ไหมครับ HANDLE, PDWORD  ลักษณะนี้คือ Win32 API ซึ่งเป็นการติดต่อ OS โดยตรง ตั้งแต่จัดการแฟ้ม Threads รวมถึง GUI และอื่นๆ อีกมาก ซึ่ง UNIX เองก็มี API ระดับเดียวกันนี้ชื่อว่า POSIX  API ระดับนี้เขียนด้วยภาษา C ครับ ดูวุ่นวายอย่างที่แสดงเป็นตัวอย่างให้เห็น ต่อมา Microsoft ก็ไปซื้อตัว Anders Hejlsberg มาจาก Borland ผู้สร้าง .net และ C# นั่นแหละครับ แต่งานแรกที่ Hejlsberg ได้รับมอบหมายให้ทำเป็นงานแรกคือสร้าง API ขี่บน Win32 API อีกที เขียนด้วย C++ เป็น OOP เพื่อให้ใช้งาน C++ ได้สะดวก ผลลัพธ์เป็นที่รู้จักการในชื่อของ MFC นั่นเอง และต่อมาเป็นที่ทราบกันดีอยู่แล้วว่า .net ก็มาขี่บน MFC อยู่อีกที เพื่อให้สามารถใช้งานเป็น managed code ได้

ปัญหาก็คือในระดับ Win32 API การออกแบบมานั้นออกแบบมาสำหรับ Desktop ครับ จะมาเปิดปุ๊บติดปั๊บเข้า facebook ได้ทันใจเหมือน OS คู่แข่งก็ทำได้ไม่ค่อยดีครับ ดังนั้น Microsoft จึงตัดสินใจรื้อใหม่ตั้งแต่ฐานรากเลยครับ ล้ม Win32 API ทิ้งไป (อาจใช้วิธีการขี่ Win32 API อีกทีก็ได้ สำหรับ OS ที่มี Win32 API อยู่แล้ว) เขียน API ใหม่ ตั้งชื่อว่า Windows runtime (WinRT) ซึ่งเขียนด้วย C++ เห็นไหมครับ C++ เริ่มกลับมาแล้ว โดยเอา .net class library มาเป็นแม่แบบเลย ดังนั้นจึงใช้งานง่ายกว่า  Win32 API และ MFC มากครับ และสิ่งหนึ่งที่นึกไม่ถึงก็คือ Microsoft จับเอาภาษาที่อยู่บน .net เช่น C# VB.NET มาต่อตรงเข้ากับ WinRT เลยครับ โดยไม่ผ่าน .NET มองเห็นอะไรรึเปล่าครับ “Going Native” มาแล้วครับ C# และ VB.NET สามารถคอมไพล์เป็น Native ได้ซึ่งเลือกได้ระหว่าง x86 หรือ ARM ก็ได้ แต่ .net นั้น Microsoft ก็ยังรองรับนะครับ (แต่อนาคตไม่แน่) ที่พูดมาทั้งหมดสรุปรวมแล้วผลลัพธ์ก็คือ Metro Apps ที่เรารู้จักกันนั่นเอง ซึ่งถ้าถามย้อนกลับมา WinRT นั้นสมบูรณ์แค่ไหน คำตอบก็คือยังห่างกับ .net เยอะครับ ยังมี Library อีกหลายตัวที่ยังไม่ได้ port จาก .net มาสู่ WinRT เรื่องนี้ต้องใช้เวลา

ทำไม Microsoft ถึงทำอย่างนี้ กลับมาสู่ Native ดูเหมือนถอยหลังเข้าคลองรึเปล่า จริงๆ แล้วผมว่าไม่ใช่นะครับ ผมกลับคิดว่าแนวคิดมีตัว runtime จัดการอย่าง Java และ .net สิเริ่มเสื่อมความนิยม OS พัฒนาขึ้นมากแล้ว ความดีที่ตัวจัดการเคยมี หลายอย่าง OS ก็ทำได้เองหรือภาษาแบบ Native ก็เริ่มมีวิธีที่ชดเชยได้แล้ว ดังนั้นตัวจัดการจึงเริ่มถูกมองว่าเป็นส่วนเกิน ตัวเก็บขยะนั้นเป็นทางแก้แต่ก็ในเวลาเดียวกันก็เป็นตัวนำปัญหาใหม่ๆ มา มันก็เลยไม่ใช่คำตอบสุดท้าย

ในโลกของ Smartphone ถ้าพิจารณา Hardware ที่สูสีกัน iOS จะทำงานได้ไหลลื่นกว่า Android อย่างชัดเจน ปัญหามันอยู่ที่ jvm เองครับ Android เองก็ต้องเปลี่ยน JVM ของตัวเอง จาก Dalvik เป็น ART โดยเพิ่มความสามารถในการแปลล่วงหน้าเหมือนกับ ngen ของ .net สิ่งนี้เป็นสิ่งที่ชี้ชัดครับว่าคำว่าประสิทธิภาพนั้นยังคงสำคัญอยู่  มันอาจดูขัดๆ ว่าประสิทธิภาพเราจะได้มาเองจาก CPU ที่เก่งขึ้นทุกวัน นั่นมันก็จริงครับ แต่เชื่อว่าไม่นานก็ถึงทางตัน มันจะเร็วขึ้นได้ยากขึ้น แต่แม้สมมุติว่า CPU จะเร็วขึ้นได้เรื่อยๆ แต่ราคาก็ย่อมต้องแพงกว่าตัวที่ทำงานช้ากว่า ถ้า OS นั้นสามารถใช้งาน CPU ที่ช้ากว่าแต่ถูกกว่า โดยยังไหลลื่นได้ดีอยู่ ส่วนต่างของราคาก็คือความสามารถในการแข่งขันครับ นี่ผมพูดเฉพาะในส่วนของตัว OS นะครับ ส่วนความสำเร็จในการแข่งขัน มีอะไรมากกว่านั้นเยอะครับ ซึ่งก็เกินความสามารถของผมที่จะไปวิเคราะห์ได้

Microsoft เพิ่งประกาศตัว Windows 10 ที่จะรวม OS เข้าด้วยกัน ผมยังไม่มีข้อมูลนะครับ หลังๆ ก็ไม่ค่อยได้ตาม Microsoft เท่าไหร่ แต่เชื่อว่าน่าจะโปรโมทตัว WinRT นี้ขึ้นมาเป็นหลัก

C++ กับงาน HPC

กลับมาเรื่องของเราดีกว่า ที่เลือก C++ สำหรับงาน HPC ไม่อาจเป็นเรื่องผิดพลาดไปได้ ด้วยความสามารถของภาษาที่สูง เน้นประสิทธิภาพทุกกระบวนการ เครื่องไม้เครื่องมือที่ทันสมัย กลุ่มผู้ใช้ขนาดใหญ่มาก แถมยังมี Library ต่างๆ ที่เน้นประสิทธิภาพเป็นหลักสำหรับงาน HPC ดังนั้นคงไม่ต้องแจกแจงรายละเอียดครับ เชื่อว่าทุกท่านคงเล็งเห็นจุดนี้ดีอยู่แล้ว ส่วนเรื่องการทำ Operator Overload นั้นเป็นจุดขายของ C++ มาหลายสิบปีแล้ว จึงทำให้ C++ ได้รับความนิยมสูงในวงการ HPC

จุดอ่อนของ C++

ประเด็นนี้ก็ฟันธงได้ไม่ยากครับ “ใครๆ ก็รู้” ภาษา C++ มีความซับซ้อนสูง ซับซ้อนเนื่องมาจากความสามารถของภาษาผนวกกับความกระหายอยากได้ประสิทธิภาพสูงสุด และความอยากในการใส่ความสามารถของภาษาให้มากที่สุด ทำให้ซับซ้อนมาก แม้ว่าใน C++11 มีการปรับแล้วมากพอสมควร แต่อย่างไรก็คงซับซ้อนอยู่มาก เชื่อว่าก็คงยังซับซ้อนไปตลอดชั่วอายุของภาษานี้

จุดอ่อนอีกจุดก็คือคอมไพเลอร์แต่ละตัวมีความสามารถไม่เท่ากัน การรองรับในมาตรฐานใหม่ๆ ทำได้ไม่เท่ากัน ดังนั้นคอมไพเลอร์บางตัวก็ยังไม่รองรับความสามารถบางอย่าง ทำให้การเขียนโปรแกรมขาดความ Portable library บางตัวมีการใช้ความสามารถพิเศษของ OS อาจประสบความยุ่งยากในการ Port ข้าม OS หรือ Compiler

การติดตั้ง C++

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

สำหรับ GCC นั้น ถ้าจะใช้กับเว็ปนี้ต้องเป็นรุ่น 4.8 ขึ้นไปนะครับ มิฉะนั้นจะไม่รองรับ C++11 ได้ครบถ้วน อาจเกิดปัญหาได้ แต่ถ้าใครใช้ Clang ก็ 3.3 ขึ้นไปครับ ใครจะใช้ Microsoft ก็ไม่มีปัญหาครับ ผมคงไม่สอนวิธีลง เพราะน่าจะรู้อยู่แล้ว มันอยู่ในชุด Visual Studio แนะนำให้ใช้ รุ่น 2013 ขึ้นไปครับ จะลงเป็น Express ก็ได้ โปรแกรมที่ผมเขียนจะทดสอบกับ GCC เท่านั้นครับ

ส่วน OS ที่ผมจะใช้ก็เป็น Windows และ Linux ผมเลือก Ubuntu ครับ เพราะน่าจะเป็นตัวที่นิยมที่สุดแล้วครับ เรามาลองดูการลงโปรแกรมทั้งสอง OS เลยครับ

การติดตั้งสำหรับ Windows

เข้าไป download  Installer ได้ที่  http://www.mingw.org  ตัว installer ชื่อ mingw-get-setup.exe  รันโปรแกรมแล้วเลือก ให้หมดครับ ยกเว้น Ada ก็พอ ลงโปรแกรมที่ c:\mingw นะครับ เมื่อลงเสร็จให้ไปที่  Control Panel -> System -> Advanced -> Environment Variables

  • เพิ่มตัวแปร MINGW_HOME  ที่ c:\mingw
  • เพิ่ม Path ไปยัง c:\mingw\bin

เมื่อเสร็จแล้วลงเข้า shell พิมพ์ gcc -v  ดูว่าบรรทัดสุดท้ายเ ถ้าขียนว่า  gcc version 4.8.1  หรือสูงกว่า แสดงว่าการลงโปรแกรมถูกต้อง

การติดตั้งสำหรับ Ubuntu

  • sudo apt-get update
  • sudo apt-get upgrade
  • sudo apt-get install build-essentials

วิธีทดสอบใช้วิธีเดียวกันกับ Windows

Hello, World

ลองมาเขียนโปรแกรมกันครับ จะได้รู้ว่ารองรับ C++11 กันรึเปล่า ตามนี้เลยครับ hello.cpp

ใครจะใช้ editor/ide ตัวไหนเขียนก็ได้ครับ ผมแนะนำสองตัวครับคือ code::blocks และ clion ลงไปหาลงดูครับ ซึ่งผมจะไม่ใช้ความสามารถทาง IDE ของมัน ใช้มันแค่เขียนโปรแกรม ส่วนเวลาคอมไพล์จะใช้ command-line ครับเผื่อว่าบางท่านอาจจะไม่มี ide  แต่ถ้ามี ide ก็ต้องไปปรับแต่งให้เป็นเองนะครับซึ่งอาจเป็นเรื่องยุ่งพอสมควร ถ้าติดขัดอย่างไร ทดลองที่ command-line ดูก่อนครับครับ เราจะ คอมไพล์โดยใช้

gcc -std=c++1y -o hello hello.cpp

ซึ่ง -std=c++1y  คือให้คอมไพเลอร์ใช้ความสามารถทางภาษาสูงสุดเท่าที่มีตั้งแต่ C++11, C++14 หรือแม้กระทั่งสูงกว่านั้นที่มีแนวโน้มว่าจะเป็นมาตรฐานต่อไป  หรือจะจำกัดตัวเองที่ C++11 โดย -std=c++11  ก็ไม่ผิดกติกาอย่างใด

-o hello  คือผลลัพธ์ชื่อ hello  ถ้าเป็น Windows จะได้ hello.exe   ทดลองรันโปรแกรมดู ได้ผลลัพธ์ตามต้องการหรือไม่

ถ้าเป็น Visual C++ เข้าไปที่ Visual Studio 2013 -> Visual Studio Tools -> VS2013 x86 Native Tools command Prompt แล้ว compile โดย

cl /Fe:hello hello.cpp

สรุป

C++ มีจุดอ่อน แต่จุดอ่อนเหล่านี้เมื่อเทียบกับจุดแข็งที่มี ชั่งดูแล้วสำหรับงาน HPC จึงเป็นทางเลือกที่ดี ฟังธง! ในบทความนี้ผมแสดงเหตุผลของการเลือกใช้ภาษาที่สองนั่นคือภาษา C++ แจกแจงที่มาและที่ไป หวังว่าคงเป็นประโยชน์ ติดตามตอนต่อไปครับ

 

 

[Total: 5    Average: 4.2/5]

You may also like...

Leave a Reply

Your email address will not be published. Required fields are marked *