Thursday, August 25, 2016

ทำไงให้ GHC เร็วขึ้น

งานที่ผมทำอยู่ (ยังไม่เสร็จ) code ของ app จริง ๆ ตอนนี้แค่ 3 พันกว่าบรรทัด แต่มีใช้ template haskell กับ quasiquotation หลายที่ + lens นิดหน่อย  compile ทั้ง project ล่อไป 1 นาที ถ้าไปแก้ module ที่ module อื่นมายุ่งแล้ว compile ใหม่ ก็รอไปครึ่งนาที ไม่รู้มันช้าเอง เครื่องห่วย หรือผมเขียนอะไร noob ๆ ทำให้ compiler มันงงก็ไม่รู้

แต่จะนั่งขุดรายละเอียด GHC หาทางเอาใจ compiler ก็คงไม่ไหว.. เลยลอง google แล้วก็เจอคนเขียนเรื่องนี้ไว้พอดี เค้าลองปรับ options ต่าง ๆ  ของ GHC แล้วเอามาเทียบกันให้เราดู ว่าทำอะไรแล้วมันเร็วขึ้นแค่ไหน https://rybczak.net/2016/03/26/how-to-reduce-compilation-times-of-haskell-projects/

สำหรับใครขี้เกียจอ่าน ขอคำตอบแบบ stackoverflow:
  • ปิด optimizer (-O0) นอกจากเรื่อง correctness แล้ว GHC ยังใช้เวลาไม่น้อยไป optimize code อย่างหนักหน่วง เพื่อให้ code เราให้ทำงานได้เร็ว แต่เวลาเรา develop เราไม่ได้ต้องการเร็วอะไรขนาดนั้น ขอแค่เช็คว่า type ถูกมั้ย test พอได้ก็พอ ดังนั้นก็ปิด optimizer มันซะ
  • เพิ่ม heap (+RTS -A128m -n2m -RTS) เพราะ GHC เสียเวลาไปกับ garbage collection (GC) ตอน compile เยอะมาก ประมาณ 25 วิ จาก 65 ถ้าเพิ่ม heap ให้เยอะ ๆ GC ก็จะวิ่งน้อยลง เป็นการแลก memory กับ time
  • parallel compilation (-j) ใส่แล้ว GHC จะหาเองว่า module ไหนไม่มี dependency ก็จะ compile พร้อม ๆ กันไปเลย

ผลคือเร็วขึ้น เยอะมาก จาก 65 วิ เหลือแค่ 23 วิ... สบายละ

ปล: ใครคิดว่าเขียน haskell แล้ววงจร code feedback มันจะช้า จะขาดตอน ใจเย็น ๆ อย่าตื่นตูม แค่ลง intero (http://commercialhaskell.github.io/intero/) ตอนนี้มีแต่เป็น emacs plugin คุณก็จะได้ editor ที่ขีดเส้นตัวแดง ๆ ตรงที่ code ผิด เราเอา cursor ไปแหย่ก็รู้ทันทีว่าต้องแก้เพราะอะไร โดยไม่ต้อง compile ใหม่แม้แต่ตัวเดียว โค้ดทั่วไปรอประมาณครึ่งวิ โค้ดหนืด ๆ อย่าง template haskell รอไม่เกิน 3 วิ  หรือจะลอง test บาง function ก็มี repl ให้ใช้ใน emacs  ที่ intero มันเร็วเพราะหลังบ้านมันใช้ GHCi ในการ interpret code เรา ก็เลยเร็วเหมือนภาษา dynamic แต่เป๊ะ เหมือนวิ่งผ่าน compiler

Tuesday, July 19, 2016

ตอบเรื่อง java thread safety

จาก post ในกลุ่ม THJUG บน facebook มีคนชวนคุยเรื่อง thread safety ใน java ตามนี้ครับ





เหมือนเดิมครับ ตอบยาว เลยเอามาแปะบน blog เพื่อไม่ให้มันหายไปกับกาลเวลา...


เรื่อง thread safety ผมเห็นว่าเป็นรูปแบบนึงของผลกระทบจาก mutability + concurrency ครับ ปัญหานี้มีทุกภาษา แต่จะชัดและปวดหัวมากกับภาษาที่ไม่มีวิธีบังคับ immutability

เรื่องนี้หลักการไม่ยาก ถ้าคิดถูกแต่แรกก็เขียนให้ถูกง่าย แต่การ test ว่าถูกรึเปล่าบางทีก็ไม่ง่ายครับ เพราะเวลาผิด มันไม่ได้ตายทุกครั้งเนี่ยแหละ  ... "ระบบผิด ไม่โหดเท่า ระบบผิดบางครั้ง"

การแก้ปัญหา มีหลายแบบ แต่ละแบบก็มีข้อดีข้อเสียและลักษณะปัญหาที่ถูกโฉลกต่างกันไป ทำให้ถูกอย่างเดียวไม่ยากครับ lock แหลก แต่ทำยังไงให้ ทั้งถูก ทั้งเร็ว ทำยังไงให้ระบบเรารับ request จำนวนมาก ๆ ที่อิงกับ resource ที่ต้องใช้ร่วมกันแล้วถูกเสมอ นี่คือปัญหา

การ lock หรือที่เรียกกันใน java ว่า synchronized จะที่ method หรือที่ instance ก็เป็นเรื่องห้ามใช้ ถ้าจะทำระบบที่ high-performant (ไม่ถูก grammar เนอะ) เพราะ thread มันแพง server 1 ตัว รองรับ os thread ได้จำกัด ถ้า thread ไหนโดน lock ปุ๊บ thread นั้นจะนอนนิ่ง ๆ ทันที ไม่มีใครเอาไปใช้งานได้ ถ้า app ไหนมี lock แบบนี้แล้ว thread มากองเยอะ ๆ นี่ app หนืดได้ในไม่กี่วินาที นอกจากนี้ยังต้องระวังเรื่อง deadlock อีก ต้องเล็งดี ๆ เมื่อไหร่ wait เมื่อไหร่ notify เกิด lock พลาดเจอ deadlock ระบบก็หลับยาว..

ส่วนเรื่องทำให้ immutable โดยไม่ให้มี setter อย่างเดียวก็อาจจะไม่รอดครับ เช่น ถ้า private field เป็น final list แล้วมีการเรียก list.add(..) ต้องเพิ่มนิดนึงว่า field ใต้ instance นั้นต้อง immutable ด้วย

จริง ๆ ต้องบอกว่า immutability แค่ทำให้เราวิเคราะห์พฤติกรรมของโปรแกรมเราได้ง่าย มันไม่ได้รับประกันว่าจะไม่เจอปัญหานะครับ เพราะ software ที่ใช้งานจริง ยังไงก็ต้องมีการ mutate ค่า ถึงเราทำชิ้นย่อย ๆ ของเราให้ immutable แต่ยังไง layer บน ๆ ก็ต้อง mutate อะไรซักอย่าง

แล้วทางแก้ race condition ดี ๆ แบบ lock-free ล่ะ ? เอาเท่าที่ผมเคยใช้นะครับ

1. actor ตัวนี้ต้นตอมาจากไหนไม่รู้ แต่ดังโดย erlang และลอกโดย scala ถ้าชาว java จะใช้อาจจะต้องดู lib ชื่อ akka จริง ๆ มันทำประโยชน์ได้หลายอย่างมาก แต่สำหรับเรื่อง race condition หลักการมีแค่ว่า มี resource ที่จำกัดกี่ตัว ก็ให้มี actor เป็นตัวคุม resource เท่านั้นตัว เช่น ระบบเรา read x พร้อมกันได้เยอะ ๆ แต่ห้าม mutate x พร้อมกัน เราก็ออกแบบให้มี mutator actor ตัวเดียวที่คอย mutate x ใครอยาก mutate x ให้บอก mutator actor ให้ทำให้ หรือ ระบบ legacy หลังบ้านเรารับ api ได้แค่ 5 concurrent เท่านั้น เราก็มี actor สำหรับคอยยิง api 5 ตัวแบ่งงานกันทำ แค่นี้ทำยังไงก็ไม่มีโอกาสที่ใครจะมา mutate x หรือยิง api > 5 ตัวพร้อมกันแล้ว ข้อเสียมีหลายอย่างเหมือนกัน แต่สำหรับชาว java ไม่ต้องสนครับ java มีข้อเสียพวกนั้นครบอยู่แล้วถึงไม่ได้ใช้ actor ก็ตาม

2. software transactional memory (stm) นึกถึง optimistic locking ใน hibernate ครับ ตอนเรา load row มา เราจะมี tag เก็บไว้ (version) พอเราจะ commit ถ้า tag ไม่ตรงก็คือมีคนอื่นชิง write ก่อนเรา เราเจ๊ง ต้อง load data สดใหม่มาทำขั้นตอนเดิมอีกรอบเอง แต่ stm คือเราเขียน code แล้วบอกว่า code นี้ให้เช็ค tag ให้ด้วย ถ้า tag ตอน load กับตอน commit ไม่ตรง มันจะ re-run code block ให้เราเองไปเรื่อย ๆ จนกว่าจะผ่าน ข้อดีคือเราเขียน code ได้ธรรมชาติมาก เหมาะใช้กับงานที่เรา "คิดว่าคง conflict ไม่บ่อย" เพราะถ้า conflict บ่อยจะเปลืองตรงการทำอีกครั้ง ข้อเสียคือ ใช้กับ IO ไม่ได้ เช่น เราเขียน socket เสร็จ แต่เจอว่าระหว่างนั้นมีใคร mutate ตัวแปรเรา เราดึงข้อความที่เราเขียนกลับมาไม่ได้แล้ว รู้สึกตัวนี้จะมีใน akka เหมือนกัน

3. channel ไม่กล้าเรียก communicating sequential processes (csp) เพราะผมไม่รู้ว่า implement/prove แบบไหนถึงเรียกว่า csp แค่ได้ยินว่า channel ใน golang เป็น csp พอดีผมใช้ channel ตอนเขียน haskell (เป็นแค่ lib ตัวนึง) ดู implementation แล้วก็ไม่ได้มีอะไรพิเศษ  (implementation จริงของ haskell โกงครับ ใช้ lock แต่เป็น lock บน green threads ก็เลยไม่กิน os thread)  การใช้งานก็คล้าย ๆ actor ครับ มีคนเขียน มีคนอ่าน มี fan-out ถ้ามี resource อะไรจำกัดก็ให้มีคนจัดการ resource นั้นเท่านั้นตัว ใครอยากยุ่งกับ resource นั้นก็ส่งข้อมูลผ่าน channel ข้ามไปข้ามมา  แต่อันนี้ไม่รู้มีใน akka รึเปล่า

Thursday, June 23, 2016

Functor คืออะไร

จากคำถามใน group "Thai Functional Enthusiasts" https://www.facebook.com/groups/310209089128699/ มีคนถามว่า..

ผมขอคำอธิบาย 3 คำนี้หน่อยครับ
Functor, Applicative Functors, Monad รู้สึกยัง งงๆ กับ 3 คำนี้
ตามที่ผมเข้าใจ คือ
1. Functor มันคือการจำกัด type เพื่อจะเช็คว่า type ที่ส่งไปถูกต้องหรือไม่ ถ้าสร้างไว้แต่แรกๆ จะไม่ต้องมาเช็คพวก null, nil อีก
2. Applicative Functors เหมือน Functor แต่ทำกับพวก function 3. Monad นี้ งง สุดเลยถ้าอธิบายแล้ว งง ขออภัยครับ งง เหมือนกัน
อีกคำถามครับ Monad, Monoid, Monadic มันคืออันเดียวกันใช่ไหมครับ

พิมพ์ตอบใน facebook ท่าจะยาวเกิน เลยเขียน blog ครับ เผื่อมีประโยชน์ต่อคนอื่นบ้าง

คือ functor, applicative และ monad เป็น typeclass แปลว่าสิ่งที่มันกำหนดจริง ๆ มีแต่ชื่อ function กับ type ที่ function ไปยุ่ง ซึ่งถือว่า abstract มาก ถ้าเราพยายามหาตัวอย่างที่มัน concrete เราอาจจะยิ่งงง เพราะเราจะไปยึดติดกับของที่มัน concrete ทำให้เอาไปใช้กับอย่างอื่นไม่ได้ ซึ่งผมว่านี่เป็นปัญหายอดฮิตของคนที่เขียนภาษาอื่นก่อนมาหัด haskell เลย

ผมว่าก่อนจะรู้ functor, applicative, monad และ monoid จริง ๆ ต้องรู้เรื่อง value/type/kind และ typeclass ก่อนครับ มันสัมพันธ์กันหมด อธิบายด้วยอะไรที่จับต้องไม่ได้ แต่ผมจะลองอธิบาย functor แบบบ้าน ๆ ดู โดยจะพยายามไม่ให้เสีย concept ครับ

วันนี้ขอเรื่อง functor ก่อน.. (เรื่องอื่นไว้ post หน้าครับ) functor คืออะไร  functor คือ "context ของ <อะไรซักอย่าง> ที่เราสามารถเปลี่ยนไอ้ <อะไรซักอย่าง> เป็น <อะไรอีกอย่าง> ได้ แต่มีข้อแม้ว่า <อะไรอีกอย่าง> ยังต้องอยู่ใน context เดิม" ยิ่งฟังยิ่งงงเนอะ 555 ไม่เป็นไร พักไว้ก่อน

ดู definition ของ Functor บ้าง..

class Functor f where
  fmap :: (a -> b) -> f a -> f b

อ่านออกเสียงเป็นภาษาไทยว่า ถ้า f เป็น context นึง และ f เป็น functor แล้ว เราสามารถแปลง (fmap) มัน โดยวิธีการแปลง จะให้คนอยากแปลงส่ง function 1 ตัว (a -> b) เพื่อเปลี่ยน "อะไร" (a) ใน context f (f a) ให้เป็น "อีกอย่าง" (b) ใน context f (f b) เหมือนเดิม เช่น

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

in: fmap (\น้ำ -> แช่แข็ง น้ำ) (ขวด น้ำ)
out: (ขวด น้ำแข็ง)

เอาใหม่ให้มันเบลอ ๆ กว่าเดิม ถ้าผมมี "ความรู้สึกดี ๆ กับอะไรซักอย่าง" และผมรู้ว่า "ความรู้สึกดี ๆ" เป็น functor แปลว่าผมสามารถเปลี่ยนความรู้สึกดี ๆ ต่อนาย ก เป็นความรู้สึกดี ๆ ต่อนาย ข หรืออาจจะเปลี่ยนไปรู้สึกดี ๆ กับหมาข้างถนนก็ได้ จะเห็นว่าผมสามารถเปลี่ยน "อะไร" ที่ผมรู้สึกดี ๆ ต่อได้ แต่ผมไม่สามารถเปลี่ยน context จากความรู้สึกดี ๆ เป็นความรู้สึกเกลียดชังได้

กลับมาตัวอย่างที่ใช้จริง เช่น Maybe .. context ของ Maybe คือ "ความอาจจะมีหรือไม่มีก็ได้" และ Maybe เป็น functor แปลว่าเราสามารถแปลง (fmap) ความอาจจะมีหรือไม่มีอะไรซักอย่าง ให้เป็นความอาจจะมีหรือไม่มีอะไรอีกอย่างได้ เช่น Maybe String (อาจจะมี string) กลายเป็น Maybe Int (อาจจะมีตัวเลข) ถ้าเราจะเปลี่ยนอะไรมัน เราก็เปลี่ยนได้เฉพาะกรณีที่มีค่าเท่านั้น

in: fmap (\s -> length s) (Just "hello")
out: (Just 5)

in: fmap (\s -> length s) (Nothing)
out: (Nothing)

ส่วน list .. context ของมันคือ "ความเป็น collection" หรือ "ความเป็นไปได้หลายอย่าง" การที่เราจะเปลี่ยนอะไร เราก็ไล่เปลี่ยนทีละตัว ถ้าไม่มีอะไรเลย (list ว่าง) เราก็ไม่ต้องเปลี่ยน (คืน list ว่าง)

in: fmap (\n -> show (n * 2)) [1,2,3,4,5]
out: ["2", "4", "6", "8", "10"]

in: fmap (\n -> show (n * 2)) []
out: []

หรือ async (ถ้าไม่เคยใช้ใน haskell นึกถึง promise ของ javascript) .. context ของ async คือ "เดี๋ยวชั้นทำงานเสร็จแล้ว ชั้นจะคืนค่าอะไรซักอย่างให้แก" ถ้าเราจะแปลง async เราก็ต้องแปลงไอ้ค่าที่ async จะคืนมา ตอนมันทำงานเสร็จนั่นเอง

in: data Student = Student { name :: String }
in: do
in:   name <- async (plankFor10MinsAndGetStudentName)    -- เดิม เป็น Async String
in:   let student = fmap (\s -> Student s) name               -- fmap แล้วกลายเป็น Async Student
in:   wait student
out: IO (Student "John")


ทิ้งท้าย.. monad กับ monadic ถ้าไม่ซีเรียส มองว่ามันเหมือนกันก็ได้ครับ ภาษาไทยเราใช้ noun กับ adj ปนกันอยู่แล้ว

ส่วน monoid แปลเป็นไทยคร่าว ๆ ว่า "อะไรก็ได้ที่ถ้าเราเอามัน 2 ตัวที่เป็น type เดียวกันมารวมกัน มันจะรวมกันได้ และต้องได้ type เดิม" คำว่ารวมนี่ไม่ใช่การ + อย่างเดียวนะครับ ถ้า int monoid เราอาจจะบวกหรือคูณ ถ้า string หรือ list monoid เราก็เอามา concat ฯลฯ

จะสังเกตว่า monoid ไม่ใช่ context ของ "อะไรซักอย่าง" (kind: * -> *) แต่มันคือ ไอ้ตัว "อะไรซักอย่าง" เองเลยครับ (kind: *)