วันพุธที่ 16 กันยายน พ.ศ. 2552

การเขียนโปรแรมภาษาซี ในการประมวลผลแบบเวกเตอร์ ตอนที่ ๒

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

1 #include
2 #include
3 typedef int vector __attribute__
4 ((mode(V4SI)));
5 main() {
6 int tmp[4];
7 vector a;
8 tmp[0]=0; tmp[1]=1;
9 tmp[2]=2; tmp[3]=3;
10 memcpy(&a, tmp, sizeof(int)*4);
11 }
โปรแกรมที่ 3

จะเห็นว่ามีการกำหนดตัวแปร tmp ซึ่งเป็นอะเรย์ชนิด int และกำหนดที่สำหรับเก็บข้อมูลไว้ 4 ที่ (บรรทัดที่ 6) จากนั้นจึงกำหนดค่าให้กับอะเรย์ (บรรทัดที่ 8 และ 9) จากนั้นจึงใช้ฟังก์ชัน memcpy เพื่อเคลื่อนย้ายข้อมูลจาก tmp มาเก็บไว้ที่ตัวแปร a และเนื่องจากถือว่าตัวแปร a เป็น
ตัวแปรแบบปกติจึงต้องใส่เครื่องหมาย & เพื่อกำหนดให้หมายถึงที่อยู่ของตัวแปร a ในกรณีที่ต้องการนำค่าจาก ตัวแปรเวกเตอร์มาแสดงผล หรือใช้งานอื่นก็สามารถใช้ฟังก์ชัน memcpy ได้เช่นเดียวกัน ดังส่วนของโปรแกรมที่ 4
1 memcpy(tmp, &a, sizeof(int)*4);
2 printf(“%d %d %d %d\n”, tmp[0],
3 tmp[1], tmp[2], tmp[3]);
โปรแกรมที่ 4

โดยวิธีการดังกล่าวจะทำกลับกันกับโปรแกรมข้างต้นก็ได้ คือใช้ memcpy เคลื่อนย้ายข้อมูลจากตัวแปร a มายังอะเรย์ tmp แล้วจึงค่อยใช้ฟังก์ชัน prinft แสดงค่าของตัวแปร tmp ทีละตัว
แม้ว่าการใช้อะเรย์ร่วมกับ memcpy จะทำให้สามารถกำหนดค่า และเข้าถึงค่าที่เก็บอยู่ในเวกเตอร์ได้ แต่ก็ยังมีข้อจำกัดและไม่สะดวกเท่าที่ควร ยังมีวิธีการที่สามารถจัดการข้อมูลแบบเวกเตอร์ได้สะดวกกว่าก็คือ การใช้โครงสร้างแบบ union ซึ่งเป็นคุณสมบัติเฉพาะของ ภาษาซี โดย union จะแตกต่างจากโครงสร้างแบบ struct ที่วิธีการเก็บข้อมูลโดย union จะกำหนดตำแหน่งการเก็บ ข้อมูลของสมาชิกแต่ละตัวทับซ้อนกัน ทำให้สามารถกำหนดให้ตัวแปรอะเรย์เก็บในหน่วยความจำตำแหน่ง เดียวกับตัวแปรเวกเตอร์ ซึ่งจะทำให้สามารถเข้าถึงข้อมูลของตัวแปรเวกเตอร์ได้ทันที โดยอาศัยตัวแปรอะเรย์ที่ วางซ้อนทับกันอยู่ ดังส่วนของโปรแกรมที่ 5 ข้างล่างนี้

1 union va {
2 int a[4];
3 vector v;
4 };
โปรแกรมที่ 5

จะเห็นว่า union va ที่กำหนดขึ้น ประกอบด้วยสมาชิกสองส่วน คือ a ซึ่งเป็นอะเรย์แบบ int ขนาด 4 ที่ และ v ซึ่งเป็นตัวแปรเวกเตอร์ แต่เนื่องจากตำแหน่งในหน่วยความจำของสมาชิกทั้งสองตัวนี้ซ้อนทับกัน เมื่อกำหนดค่าให้อะเรย์ a ก็จะส่งผลให้ค่าของตัวแปร v เปลี่ยนไป ในขณะเดียวกัน เมื่อค่าของตัวแปร v เปลี่ยนไป ก็จะทำให้ค่าของอะเรย์เปลี่ยนไป ทำให้สามารถจัดการกับค่าของตัวแปรเวกเตอร์ได้สะดวกยิ่งขึ้น และเมื่อรวมโปรแกรม ทั้งหมดเข้าด้วยกัน จะสามารถเขียนโปรแกรม
เพื่อบวกเลข 4 ตัวพร้อม ๆ กันและแสดงผลบวกได้ ดังโปรแกรม ที่ 6 ต่อไปนี้


1 #include
2 #include
3 typedef int vector __attribute__
4 ((mode(V4SI)));
5 union va {
6 int a[4];
7 vector v;
8 };
9 main() {
10 union va a, b, c;
11 a.a[0]=1; a.a[1]=1;
12 a.a[2]=1; a.a[3]=1;
13 b.a[0]=2; b.a[1]=2;
14 b.a[2]=2; b.a[3]=2;
15 c.v = a.v + b.v;
16 printf(“%d %d %d %d\n”, c.a[0],
17 c.a[1], c.a[2], c.a[3]);
18 }
โปรแกรมที่ 6

จากโปรแกรมนี้จะเห็นว่า มีการกำหนดให้ ตัวแปรทั้งสามตัว เป็น union va (บรรทัดที่ 10) จากนั้นจึงกำหนดค่าโดยใช้อะเรย์ที่เป็นสมาชิกของ union (บรรทัดที่ 11-14) แต่เวลาคำนวณจะทำผ่านเวกเตอร์ซึ่งทำให้สามารถบวกเลขทั้ง 4 ตัวได้พร้อมกัน จากนั้นจึงใช้ฟังก์ชัน printf แสดงผลการบวก โดยอาศัยอะเรย์เช่นเดิม

ข้อกำหนดการใช้งาน
หัวข้อที่ผ่านมาได้แสดงวิธีบวกเวกเตอร์สองแบบ รวมทั้งวิธีกำหนดค่า และเข้าถึงค่าที่เก็บไว้ในเวกเตอร์ เพื่อให้เกิดความเข้าใจที่ตรงกันจึงขอสรุปข้อกำหนดต่าง ๆ ในการใช้ตัวแปรเวกเตอร์ ซึ่งข้อกำหนดนี้เป็นไปตาม จีซีซี ดังนี้
• ตัวแปรเวกเตอร์นั้นถือเป็นตัวแปรที่เก็บ ข้อมูลชุดเดียว สามารถนำไปใช้งานได้เช่นเดียวกับตัวแปรทั่ว ๆ ไป เช่น ส่งเป็นพารามิเตอร์ไปยังฟังก์ชัน หรือเป็นค่า ผลลัพธ์ของฟังก์ชัน ซึ่งกำหนดค่าให้แก่กัน
• ตัวดำเนินการที่สามารถใช้กับตัวแปรเวกเตอร์ได้คือ บวก (+) ลบ (-) คูณ (*) หาร (/) unary minus (-) ตัวดำเนินการตามบิต (bitwise operator) ได้แก่ and (&) or () not (~) และ xor (^)
• การดำเนินการต่าง ๆ ระหว่างตัวแปร เวกเตอร์ที่มีชนิดข้อมูลแตกต่างกัน จะต้องกำหนด การเปลี่ยนชนิดข้อมูล (type casting) ให้เรียบร้อยก่อน

การคำนวณหาระยะแบบยูคลิด
เนื่องจากการเขียนโปรแกรมแบบเวกเตอร์นั้น จะมีวิธีคิดที่แตกต่างจากการเขียนโปรแกรมแบบปกติ ดังนั้นเพื่อให้ผู้อ่านเกิดทักษะ และความเข้าใจวิธีการเขียนโปรแกรมแบบเวกเตอร์ยิ่งขึ้น ส่วนต่อไปของบทความนี้ จะขอยกตัวอย่างโปรแกรมสำหรับคำนวณค่า และ แก้ปัญหาต่าง ๆ เพื่อเป็นแนวทางในการศึกษาการเขียนโปรแกรมแบบเวกเตอร์ต่อไป โดยตัวอย่างนี้จะเป็น ตัวอย่างที่ไม่ซับซ้อนมากนัก เป็นตัวอย่างการคำนวณหาระยะทางระหว่างจุดสองจุด โดยกำหนดพิกัดของจุดนั้น ๆ มาให้ ทั้งนี้ระยะทางแบบที่รู้จักกันโดยทั่วไป ก็คือ ระยะทางแบบยูคลิด (Euclidean distance) ซึ่งคำนวณได้ ถ้ากำหนดจุด a มีพิกัด (x1,y1) จุด b มีพิกัด (x2,y2) และ ระยะทางระหว่างจุด a และ b มาให้ จะคำนวณได้จากสูตร

distance =

ตัวอย่างข้างต้นคือกรณีที่ต้องการหาระยะทางใน 2 มิติ ถ้าต้องการหาระยะทางใน n มิติ จะสามารถเขียนฟังก์ชันเพื่อหาระยะทางแบบทั่วไปได้ดังโปรแกรมที่ 7

1 float distance(int n, float *a,
2 float *b) {
3 int i;
4 float sum=0.0;
5 for(i=0; i0) {
13 memcpy(&va,a+i,16);
14 memcpy(&vb,b+i,16);
15 sum.v+=(va-vb)*(va-vb);
16 i+=4;
17 n-=4;
18 }
19 return sqrt(sum.f[0]+
sum.f[1]+sum.f[2]+
21 sum.f[3]);
22 }
โปรแกรมที่ 8
โปรแกรมข้างต้นมีการกำหนดแบบชนิด ข้อมูลใหม่โดยกำหนดแบบวิธีเป็น V4SF (บรรทัดที่ 1 และ 2) ซึ่งหมายถึงตัวแปรตัวเลขทศนิยมอย่างหยาบจำนวน 4 ตัว การทำงานของโปรแกรมนั้นก็มีลักษณะไม่ต่างจากโปรแกรมแรกมากนัก แตกต่างกันที่ต้องแบ่งข้อมูลออก ทีละ 4 ชุด ใช้ฟังก์ชัน memcpy เคลื่อนย้ายข้อมูลจากอะเรย์มาเก็บไว้ในตัวแปรเวกเตอร์ (บรรทัดที่ 13 และ 14) จากนั้นหาผลคูณของความแตกต่างและนำไปรวมกันไว้ที่ เวกเตอร์ sum (บรรทัดที่ 15) ซึ่งผลรวมนี้จะแยกกันอยู่ 4 ส่วนตามเวกเตอร์ เมื่อทำจนครบหมดทุกตัวแล้ว จึงนำค่าของ sum ทั้งหมดมารวมกัน และถอดรากที่สองก็จะได้ระยะทางแบบยูคลิดระหว่างจุดสองจุด

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

ไม่มีความคิดเห็น:

แสดงความคิดเห็น

DEVELOPER ZOne