nantcom

ทำไมโปรแกรม WPF ของเราช้า

เรตติ้ง
เขียนโดย นันคอม เมื่อวันที่ 28 July 2009 ตอน 21:40

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

อาการก็คือ เวลาที่สร้าง Animation ในโปรแกรม แล้วโปรแกรมทำงานได้ช้า บางทีก็เห็น CPU ขึ้น 100% เลยก็มี บางทีอยู่เฉยๆ CPU ก็ขึ้น เป็นต้น อีกอาการก็คือ หน้าโปรแกรมโหลดช้ามาก ใช้งานบางทีก้อกระตุก กินแรมเยอะอีกต่างหาก แต่ถ้าลองจูนดูดีๆ ก็ปรากฏว่า ความผิดมันอยู่ที่เราเนี่ยละครับ หลายๆ อย่างเลย เขาก็เขียนบอกไว้อยู่แล้ว แต่เราไม่ได้ไปอ่านมัน

เพื่อไม่ให้เกิดประวัติศาสตร์ซ้ำร้อย ผมเลยมีเทคนิคเล็กน้อย จากประสบการการใช้งานเทคโนโลยี XAML (ทั้ง WPF/Silverlight) มาฝากครับ

1. อย่าใช่ BitmapEffect เด็ดขาด

ก่อนที่ผมจะอธิบายอะไรต่อไป ลองดูนี่ก่อนครับ (จาก MSDN)

imageโดยสรุปก็คือ BitmapEffect นั้น เรนเดอร์ด้วย CPU ครับ (Software Rendering) ไม่ได้ใช้การ์ดจอวาด (Hardware Rendering) อย่างที่มันควรจะเป็น และพาลทำให้วัตถุนั้น เรนเดอร์ด้วย Software ไปด้วยอีกต่างหาก ควรหลีกเลี่ยงอย่างที่สุดครับ แต่ใน WPF 3.5 จะมีแบบใหม่ ที่ชื่อว่า Effect เฉยๆ ที่ทำงานโดยใช้ความสามารถ Pixel Shader 2.0 ของการ์ดจอ เหมือนกับเอฟเฟค์ภาพเบลอ แสงฟุ้งๆ ที่เราเห็นในเกมแบบนั้นเลยครับ (สังเกตประกายไฟ หรือการ Glow ของโลหะบนมอเตอร์ไซค์ในรูปด้านล่าง)

image

สำหรับการ์ดจอที่ใช้ PS2.0 ได้ คือรุ่น  Intel GMA900, Nvidia GeForce 5XXX ขึ้นไป หรือ ATI Radeon X300 ขึ้นไป หรือจะแปลง่ายๆ ก็คือ หาเครื่องที่ใช้ไม่ได้ยากมากครับในยุคนี้ เพราะแค่การ์ดจอ OnBoard ก็มีแล้วครับ สังเกตง่ายๆ ว่า ถ้าเครื่องไหน เปิด Aero Glass ได้ ก็ใช้ PS2.0 ได้แน่นอนครับ

 

400px-SIMD.svg  เนื่องจาก GPU ทำงานอะไรที่เป็น SIMD (Single Instruction Multiple Data - คำสั่งทำกับหลายๆ ข้อมูล เช่น เปลี่ยนสีทุกพิกเซลเป็นสีขาว) แบบนี้ได้เร็วมากๆ การทำเอฟเฟคบน GPU จึงแทบไม่มีผลอะไรเลยครับ เกือบจะมี Performance Hit เป็น 0 เลยก็ว่าได้ (ย้ำว่า เกือบ) แต่สำหรับเอฟเฟคเบลอ พยายามอย่าใช้ Radius เกิน 10 ครับ เพราะเอฟเฟคเบลอ คือการไปดูพิกเซลข้างๆ แล้วเอามาหาค่าเฉลี่ย ถ้าใส่ไป 20 หมายถึงว่า การจะเรนเดอร์ 1 พิกเซล ก็จะต้องดูพิกเซลรอบๆ อีก 1600 พิกเซลเพื่อเอามาบวกกันหาค่าเฉลี่ย (20 พิกเซลจากทุกด้าน = 40x40) ต่อให้ GT200 ก็ยังอาจจะลำบากอยู่เหมือนกัน เพราะว่า WPF ไม่ได้ทำงานแบบเกม (ดูข้อ 10 ครับ) และเกมก็ไม่มีเอฟเฟคที่เบลอกระจายขนาดนี้ครับ (เขาใช้เทคนิคหลายๆ อย่างผสมกันน่ะครับ)

2. อย่าสร้าง Animation ที่เกี่ยวข้องกับ Layout

แม้ว่าเดี๋ยวนี้ CPU จะเร็วมากแล้ว แต่การสร้าง Animation ที่เกี่ยวข้องกับ Layout นั้น ใช้ CPU ล้วนๆ ครับ (และไม่มีทางทำบน GPU ได้ด้วย) นั่นก็คือ ทุกเฟรมที่โปรแกรมทุกงาน จะต้องมีการคำนวณ Layout ใหม่หมด ทุกเฟรม ดังนั้น ไม่ควรอย่างยิ่งครับ

แล้วแบบไหนคือ Animation ที่ใช้ Layout? ตัวอย่างเช่น

  • ขยาย Listbox มาจนเต็มที่ว่างในจอพอดี
  • เปลี่ยนขนาด Grid โดยตั้งค่า Width, Height
  • ขยับวัตถุ โดยใช้ Canvas.Left, Canvas.Top (มีหลายคนบอกว่า มีผลไม่มากนะ)
  • นึกไม่ออกละ….เพราะผมไม่เคยใช้เลย :P

เอาเป็นว่า ถ้าคุณทำอะไร ที่มันทำให้ Layout ขยับ นั่นแปลว่า CPU ล้วนๆ ครับ ดั้งนั้น ถ้าจะทำ Animation ให้มองลงมาล่างๆ หน่อยครับ จะเจอ Category ชื่อ RenderTransform นั่นละครับ ถ้าจะใช้ Animation ให้เปลี่ยนค่าตรงนี้เท่านั้น ซึ่งคุณสามารถเล่นกับอนิเมชั่น ประมาณนี้ได้ครับimage

  • ย่อ-ขยาย ขนาดของวัตถุ (ScaleTransform)
  • กลับด้านวัตถุ (ScaleTransform แล้วตั้งค่าเป็น –1 เช่น Y =-1 จะได้ภาพกลับหัว เหมือนเงาสะท้อน)
  • หมุนวัตถุ (RotateTransform) ไม่เหมือนกับการกลับด้านนะครับ ถ้าทำให้วัตถุหมุนไป จนกลับหัว มันจะกลับซ้ายเป็นขวา
  • ขยับวัตถุไปมา เป็นจำนวน Pixel คงที่ เช่น ขยับซ้าย 10 pixel (TranslateTransform) แต่ถ้า ขยับไปจนเต็มพื้นที่ แบบนี้จะทำไม่ได้ครับ
  • Opacity เปลี่ยนความโปร่งแสงได้
  • เปลี่ยนสี เนื่องจากการเปลี่ยนสีเป็นการเปลี่ยนสีบน Vertex ของวัตถุ

Animation เหล่านี้ จะไม่กิน CPU เลย เพราะมันทำงาน หลังจากที่ Layout ทำงานแล้ว มันจึงเป็นการย่อ ขยาย หมุน เลื่อน สิ่งที่ปรากฏบนจอเท่านั้นเองครับ ไม่มีการคำนวณอะไรใหม่ทั้งสิ้น

3. ทำ Background ให้มัน Simple!

อีกหลุมหนึ่งที่เป็นปัญหามาก คือ เรามักจะเผลอวาดคอนโทรลซ้อนทับกันไป ซ้อนทับกันมา แล้วใช้มันเป็นพื้นหลังครับ ซึ่งไม่ควรอย่างยิ่ง ถ้าเป็นไปได้ ให้สร้าง DrawingBrush แทน จะดีกว่า เพราะนอกจากจะไม่ทำให้ Visual Tree ของเราใน Blend ตีกันวุ่นแล้ว มันยังเร็วขึ้นด้วย หรือถ้าจะให้ดี ทำให้มันเป็นภาพนิ่งเลยครับ จะดีที่สุด

เช่นเดียวกัน ผมเคยเจอกรณีทีมีดีไซนเนอร์ท่านหนึ่ง ใส่เงาไว้ที่ตัวอักษรทุกตัวเลย นั่นเท่ากับว่า เราจะต้องสร้างเงาขึ้นมาสำหรับตัวอักษรแต่ละตัว ซึ่งผลลัพธ์ที่ได้ ก็มีค่าเท่ากับการใส่เงาที่ Canvas หรือ Grid ที่คลุมตัวอักษรนั้นอยู่

4. Reset Property ทุกครั้ง ถ้าจะไม่เปลี่ยนมัน

ลองดูโค๊ดของสี่เหลี่ยมนี้เทียบกันครับ

 image

จะเห็นว่า ทั้งสองสี่เหลี่ยมนี่ แสดงผลเหมือนกันทุกประการ แต่ว่าอันที่ Hilight อยู่โค๊ด XAML ยาวกว่า นั่นก็เพราะเวลาคุณเซ็ทค่าบางอย่างด้วย Blend แม้ว่าค่านั้น จะเป็นค่า Default เจ้า Blend ก็่ยังจะเซ็ทค่าให้ครับ (เช่น Margin 0, HorizontalAlignment=Stretch

อย่าลืมว่า XAML นั้น มันโดนแปลงเป็นโค๊ดอีกทีหนึ่ง การที่มีการตั้งค่าต่างๆ ก็เหมือนกับการมีโค๊ดที่เซ็ทค่าเหล่านั้นทำงานอยู่ด้วยนั่นละครับ แถมเสียเวลาทำแล้ว ก็ยังไม่ได้ผลอะไร (เพราะมีคือค่า Default อยู่แล้ว) ดังนั้น ถ้าจะไม่ตั้งค่าอะไร ให้ Reset ค่าเสมอครับ ซึ่งเป็นเรื่องน่ายินดีที่ Blend 3 มันรีเซ็ทให้เอง ถ้าพบว่าการตั้งค่านั้นเป็นการตั้งค่าด้วยค่า Default

5. อย่าเปิดหลายฟอร์ม

เพราะว่าการเปิด Form ใหม่ คือการสร้าง Render Surface เพิ่มครับ เนื่องจาก WPF นั้น Render ด้วย DirectX ซึ่งปกติเขาจะ Render กันอยู่ในหน้าต่างเดียว (สังเกตง่ายๆ ว่า โปรแกรมเดโมของ Microsoft นั้น ทำงานด้วยหน้าต่างเดียวเกือบทั้งหมด)

6. อย่าเขียนโค๊ดแทนระบบ Layout

อย่าลืมว่า WPF มีระบบ Layout อยู่นะครับ ถ้าต้องเขียนโค๊ดใน อะไรซํกอย่าง_Resized เช่น ตั้งให้คอนโทรลอยู่ตรงกลาง Form เนี่ย แสดงว่าต้องมีอะไรผิดพลาดแน่ๆ ลองจัด Layout ใหม่ ดีๆ ครับ และที User Control กับ Window เราสามารถทดลองย่อขยายขนาดของมันได้ เพื่อดูว่า Layout ของเราขยับตามได้หรือไม่ (Fluid Layout) โดยลากที่มุมด้านล่างของคอนโทรล หรือ Form ครับ (อย่าลืมตั้ง Width/Height เป็น Auto)

image

 

8. อย่าตกหลุมเดียวกับ HTML

นอกจากนี้ อีกหลุมนึงที่ผมเจอก็คือ มีหลายคนพยายามใช้ Grid ทำเป็นเหมือน Table เพื่อวางคอนโทรลครับ ซึ่ง HTML ก็ได้ผ่านจุดนั้นมาแล้วจากการใช้ CSS ดังนั้น อย่าพยายามไปทำความผิดซ้ำอีก การวางคอนโทรลใน Grid ที่มี Row และ Column นั้น ลำบากมากๆ ครับ ลองใช้การจัดชิดเต็ม ร่วมกับการใส่ Margin ดู อาจได้ผลเหมือนกันก็ได้ (เช่นเดียวกับการสร้างตารางที่มี 2 คอลัมน์ ที่ได้ผลเหมือนกับการวาง div float left)

9. ใช้ DataBinding และ AttachedProperty ให้มากที่สุด

จุดเปลี่ยนของ WPF เลย คือเรื่องระบบ Data Binding ที่ทำได้วิจิตรพิสดารมาก จนผมไม่สามารถกลับไปทนใช้ Windows Form ได้อีกแล้ว และระบบ Dependency Property ที่ไม่รู้ว่าคิดได้ยังไง อย่างเช่นโปรแกรม WaiV2 ของผมนั้น ใน UI เกือบจะไม่มีโค๊ดเลย เป็นการ Binding ล้วนๆ และ ระบบ Localization ก็ใช้การสร้าง Attached Property ไปแปะไว้กับคอนโทรล แทนการทำให้คอนโทรล Localize ได้ แจ่มมากครับสำหรับฟีเจอร์ทั้งสองนี้

อ้อ สำหรับ Blend 3 จะมีฟีเจอร์ชื่อ Interactivity ด้วย มันคือการนำเอา AttachedProperty แปะเข้าไปนั่นเองครับ อย่างโค๊ดด้านล่างนี่ คือการแปะ Property Interaction.Triggers ลงไปบน Rectangle ทำให้ Rectangle เป็นลิงค์ เปิดหน้าเว็บได้image

10. อย่าลืมว่า นี่ไม่ใช่ Windows Form

ทันทีที่คุณใช้ WPF คุณก็ได้ก้าวเข้าสู่อีกโลกหนึ่งแล้วครับ การเขียนโปรแกรมด้วย WPF นั้น ในความคิดของผม มันคือการเขียนเกมดีๆ นี่เองครับ เพราะคุณจะต้องคำนึงถึงเรื่อง Performance ตลอดเวลา เพราะการวาดภาพนั้น มีราคาค่างวดที่สูงมากครับ และต้องอย่าลืมว่า WPF ทำงานแบบ Retained Mode ซึ่งจะคงออบเจ็กต์ไว้ในแรมตลอดเวลา ต่างจากเวลาที่เราวาดภาพด้วย GDI บน Windows Form ซึ่งเป็นแบบ Immediate Mode ที่วาดภาพลงไปเลย โดยที่ไม่คงออบเจ็กต์ไว้ ถึงเวลาจบการวาดก็ล้างออบเจ็กต์ทิ้ง (เหมือนการสร้าง Visual Tree ใหม่ ทุกเฟรม)

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

และเมื่อคุณรู้แล้วว่า WPF ทำงานแบบ Retained Mode คุณก็ควรจะทำให้ Visual Tree ของคุณ ให้เป็น Tree น้อยที่สุด คืออย่าให้แขนงของมันเยอะมากจนเกินไป (เช่นทุกอย่างเป็นวัตถุหมด ไม่มี Path หรือ Drawing Brush เลย แม้แต่รูปก็ไม่ยอมใช้) และอย่าให้มันลึกจนเกินไป (มีคอนโทรลซ้อนกันอยู่มากมายก่ายกอง) เพราะมันจะช้าครับ

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

ตาคุณบ้าง

คุณมีข้อเสนอ หรือความเห็นอย่างไรบ้างครับ? มาคอมเม้นกันได้นะ มาแลกเปลี่ยนกันครับ

 

Mimi said:

"จุดเปลี่ยนของ WPF เลย คือเรื่องระบบ Data Binding ที่ทำได้วิจิตรพิสดารมาก จนผมไม่สามารถกลับไปทนใช้ Windows Form ได้อีกแล้ว "

เห็นด้วยกับประโยคนี้อย่างยิ่งฮะ

December 29, 2009 11:52 PM
 

22 said:

ผมว่าแกะ code ยากขึ้นนะ ระบบเก่าเปิด ด้วย il-dasm เห็น private หมดเลย แก้และ recomplier ง่าย crack

May 26, 2010 10:22 PM

Leave a Comment

(required)  
(optional)
(required)  
Add

About นันคอม

ผมชื่อนันคอม ทำงานไมโครซอฟท์ ชอบเล่น XBOX360 ลง Vista ฟัง Zune :D งานอดิเรกชอบไปถ่ายรูปกะเพื่อนๆ แล้วเก็บรูปไว้ดูคนเดียว เพราะไม่เคยว่างโพสซะที :'(