Free Hosting

วันเสาร์ที่ 28 มีนาคม พ.ศ. 2558

มาเขียน PHP ให้ทำงานอย่างประสิทธิภาพ (Performance) กันเถอะ (ตอนที่ 2)

มาเขียน PHP ให้ทำงานอย่างประสิทธิภาพ (Performance) กันเถอะ (ตอนที่ 2) บทความนี้นำเสนอสิ่งที่ควรรู้เกี่ยวกับภาษาPHP ทั้งสิ่งที่ควรใช้ และไม่ควรใช้ ที่จะทำให้โปรแกรมทำงานได้เร็วขึ้น




6. ใช้การคูณแทนการหารในกรณีที่ให้ผลลัพธ์เหมือนกัน

ไม่ว่าจะในภาษาโปรแกรมใดก็ตาม การดำเนินการหาร (division) จะทำงานช้ากว่าการคูณ (multiplication) เสมอ
ดังนั้นเราควรใช้การหารในเฉพาะกรณีที่จำเป็นต้องใช้เท่านั้น ซึ่งมีหลายกรณีที่เราสามารถใช้การคูณแทนการหารได้ เช่น


การหาร 2
$x = $y * 0.5; // $x = $y / 2;

การหาร 4
$x = $y * 0.25; // $x = $y / 4;

การหาร 10
$x = $y * 0.1; // $x = $y / 10;


หรือเราสามารถใช้ bitwise shift right ในการหารไม่เอาเศษได้กับเลขที่เป็นค่ายกกำลังของ 2 (2, 4, 8, 16, 32 ...)

$x = $y >> 1; // $x = floor($y / 2); $x = $y >> 2; // $x = floor($y / 4); $x = $y >> 3; // $x = floor($y / 8); $x = $y >> 4; // $x = floor($y / 16); $x = $y >> 5; // $x = floor($y / 32);


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




7. ใช้ === กับการเปรียบเทียบที่รู้ชนิดของค่านั้นๆ แน่นอน

=== นั้นเร็วกว่า == มาก เพราะ === จะไม่แปลงค่าที่นำมาเปรียบเทียบ ในขณะที่ == จะแปลงค่าฝั่งขวาให้เป็นชนิดเดียวกับฝั่งซ้ายก่อนที่จะนำมาเปรียบเทียบ

การเปรียบเทียบ '0' กับ 0
หากใช้ '0' == 0 สิ่งที่ PHP จะทำคือ
- ตรวจว่า '0' เป็นชนิดเดียวกันกับ 0 หรือไม่ (string กับ int)
- และเพราะมันไม่ใช่ชนิดเดียวกัน ก็จะแปลง 0 ให้เป็น string
- และเปรียบเทียบ แล้วจึงให้ผลลัพธ์คืนมา (true)

แต่ '0' === 0 สิ่งที่ PHP จะทำคือ
- ตรวจว่า '0' เป็นชนิดเดียวกันกับ 0 หรือไม่ (string กับ int)
- และเพราะมันไม่ใช่ชนิดเดียวกัน ก็จะให้ผลลัพธ์คืนมาทันที (false)

การเปรียบเทียบ '0' กับ array()
หากใช้ '0' == array() สิ่งที่ PHP จะทำคือ
- ตรวจว่า '0' เป็นชนิดเดียวกันกับ array() หรือไม่ (string กับ array)
- และเพราะมันไม่ใช่ชนิดเดียวกัน ก็จะแปลง array() ให้เป็น string และเปรียบเทียบ
- และเมื่อพบว่า array() ที่แปลงเป็น string ได้ 'Array' ไม่ตรงกับ '0' PHP ก็จะพยายามแปลง array ให้เป็น int ซึ่งได้ 0
- และเปรียบอีกครั้ง แต่เพราะมันไม่ใช่ชนิดเดียวกัน PHP จึงต้องแปลง 0 ให้เป็น '0' อีกครั้งเพื่อเปรียบเทียบ แล้วจึงให้ผลลัพธ์คืนมา (true)

แต่ '0' === array() สิ่งที่ PHP จะทำคือ
- ตรวจว่า '0' เป็นชนิดเดียวกันกับ array() หรือไม่ (string กับ array)
- และเพราะมันไม่ใช่ชนิดเดียวกัน ก็จะให้ผลลัพธ์คืนมาทันที (false)

ดังนั้นการใช้ === จะมีประโยชน์ในเพิ่มความเร็วให้กับโปรแกรมได้มาก โดยเฉพาะกรณีที่เรารู้แน่ชัดว่าค่าที่จะนำมาเปรียบเทียบนั้นเป็นชนิดอะไร และต้องการชนิดอะไรที่จะเปรียบเทียบ เช่นตัวแปรจำพวก $_GET, $_POST, $_COOKIE ที่แน่นอนว่าค่าของสมาชิกของมันจะเป็น string เสมอ


แบบนี้ถือว่าไม่ดี
if ($_GET['action'] == 'delete') { // do something }


แบบนี้เร็วกว่า
if ($_GET['action'] === 'delete') { // do something }


นอกจากนี้ การใช้ === จะช่วยป้องกันความผิดพลาดที่อาจจะเกิดขึ้นได้ด้วย

$s = 'Hello World'; if (strpos($s, 'Hello') == false) { // not found }


จากตัวอย่างข้างบน เป็นการใช้ฟังก์ชั่น strpos() เพื่อตรวจว่ามีคำว่า 'Hello' อยู่ในตัวแปร $s หรือไม่
โดย strpos() จะคืนค่าตำแหน่งของคำที่ค้นหามาเป็น int และคืนค่า false กลับมาในกรณีที่หาไม่เจอ
ดังนั้น strpos('Hello World', 'Hello') จะให้ค่า 0 กลับคืนมา เพราะเจอ 'Hello' อยู่ที่ตำแหน่ง 0
แต่เมื่อเอา 0 ไปเปรียบเทียบกับ false ก็จะให้ผลลัพธ์เป็นจริง เพราะ == แปลง false ให้เป็น 0 ดังนั้น 0 กับ false จึงมีค่าเท่ากันในการเปรียบเทียบด้วย == ซึ่งจะทำให้โค้ดดังกล่าวจะทำงานไม่ตรงตามที่ตั้งใจไว้


แบบนี้ถึงจะทำงานถูกต้องเพราะ 0 ไม่เท่ากับ false
$s = 'Hello World'; if (strpos($s, 'Hello') === false) { // not found }


นอกจาก PHP แล้ว ในภาษาอื่นที่ === มีความหมายเดียวกันกับใน PHP คือ "เปรียบเทียบชนิดและค่า" เช่น JavaScript, ActionScript ก็สามารถใช้เทคนิคนี้ได้เช่นเดียวกัน

แต่สิ่งที่ต้องระวังคือ ใน PHP int และ float จะไม่เท่ากันเสมอ
0 จะไม่เท่ากับ 0.0 เมื่อเปรียบเทียบด้วย ===
แต่ใน JavaScript หรือ ActionScript 0 จะเท่ากับ 0.0 เมื่อเปรียบเทียบด้วย ===


PHP
0 === 0.0; // false 1 === 1.0; // false 50 === 50.0; // false 2000 === 2000.0; // false


JavaScript
alert(0 === 0.0); // true alert(1 === 1.0); // true alert(50 === 50.0); // true alert(2000 === 2000.0); // true


ที่เป็นเช่นนี้เพราะอะไร เพราะว่าใน JavaScript (ECMAScript) ไม่มีชนิด int และ float มีแค่ Number
และแม้ใน ActionScript 3.0 จะมี int และ uint แต่เมื่อเปรียบเทียบ int หรือ uint กับ Number ด้วย === จะมีวิธีพิเศษในการตรวจสอบ (ซึ่งก็ยังทำงานเร็วอยู่ดี)


ActionScript 3.0
var a:int = 0; var b:int = 1; var c:uint = 50; var d:uint = 2000; trace(a === 0.0); // true trace(b === 1.0); // true trace(c === 50.0); // true trace(d === 2000.0); // true





8. พยายามใช้ isset() หรือ empty() ในการตรวจสอบค่าของตัวแปรที่ต้องการแค่ผล จริง/เท็จ

isset() และ empty() เป็น Language Construct ที่มีหน้าที่ในการตรวจสอบการมีอยู่ของตัวแปรและค่าของมัน
โดย isset() จะตรวจสอบว่าตัวแปรนั้นๆ ถูกสร้างขึ้นมาหรือยัง และมีค่าที่ไม่ใช่ null หรือไม่
ส่วน empty() จะตรวจสอบว่าตัวแปรนั้นๆ ถูกสร้างขึ้นมาหรือยัง และมีค่าแปลงแล้วได้ผลเป็น false หรือไม่
ดังนั้นเมื่อมองดูจากการทำงานแล้วจะพบว่า empty() นั้นทำงานช้ากว่า isset() เล็กน้อย เพราะต้องมีการแปลงค่า

แต่ทั้ง isset() และ empty() จะทำงานเร็วกว่าการตรวจสอบค่าตัวแปรแบบธรรมดา โดยเฉพาะในกรณีที่ตัวแปรนั้นถูกสร้างขึ้นมาแล้ว


การตรวจสอบแบบธรรมดา
// ถ้ายังไม่มีการกำหนดค่าให้กับ $_SESSION['logged_in'] จะเกิด error if ($_SESSION['logged_in']) { // do something }


การตรวจสอบด้วย isset()
// ถ้ายังไม่มีการกำหนดค่าให้กับ $_SESSION['logged_in'] ก็จะไม่เป็นไร if (isset($_SESSION['logged_in'])) { // do something }


จากตัวอย่างข้างบน การตรวจสอบด้วย isset() จะทำงานเร็วกว่าแบบธรรมดา เพราะ isset() ตรวจแค่ว่า มีการกำหนดตัวแปรนี้ขึ้นมาหรือยัง และมีค่าเป็น null หรือไม่
ในขณะที่การตรวจสอบตัวแปรแบบธรรมดา จะตรวจว่ามีการกำหนดตัวแปรนี้ขึ้นมาหรือยัง หากมี ก็จะแปลงค่าของมันให้เป็น boolean true หรือ false และหากว่ายังไม่มีการกำหนด ก็จะแสดง error ซึ่งหากมี error ก็จะทำให้โปรแกรมทำงานช้าลงไปอีก แม้จะปิด error_reporting ก็ตาม

ดังนั้นเราควรใช้ isset() หรือ empty() ในกรณีที่ต้องการตรวจสอบความเป็นจริง/เท็จ และป้องกัน error ที่จะเกิดขึ้นโดยไม่ตั้งใจ

และการใช้ isset() ยังช่วยประหยัดหน่วยความจำด้วย เพราะเราไม่จำเป็นต้องสร้างตัวแปรเพื่อบอกความเป็นเท็จ แต่สร้างแค่ตัวแปรที่บอกความเป็นจริงเท่านั้น เช่น การตรวจสอบว่าผู้ใช้ได้ login แล้วหรือยัง ก็ทำแค่ หาก login แล้ว ก็สร้างตัวแปร $_SESSION ขึ้นมาตัวหนึ่งให้มีค่าที่ไม่ใช่ null และตรวจสอบมันด้วย isset() ซึ่งหากยังไม่ login ก็จะไม่มีตัวแปรตัวนี้เกิดขึ้นมา และไม่ใช้หน่วยความจำใดใด


การเขียนแบบทั่วไป
$done = false; // ตัวแปรที่จะทำให้เงื่อนไขข้างล่างเป็นจริง // จากนั้นทำอะไรสักอย่างที่อาจจะมีการเปลี่ยนค่าของ $done // ... // ... // ... if ($done) { // do something }


ใช้ isset() เพื่อเพิ่มความเร็วและประหยัดหน่วยความจำ
// ทำอะไรสักอย่างที่อาจจะมีการสร้างตัวแปร $done เพื่อทำให้เงื่อนไขข้างล่างเป็นจริง // ... // ... // ... if (isset($done)) { // do something }


isset() และ empty() อาจจะดูมีหน้าตาคล้ายฟังก์ชั่น แต่จริงๆ แล้วมันไม่ใช่ มันเป็น op code ดังนั้นมันจะไม่เสียเวลาในการค้นหาฟังก์ชั่นในระบบเพื่อทำงาน แม้อาจจะทำให้ต้องพิมพ์โค้ดยาวขึ้น แต่การฝึกใช้มันให้คล่องจะทำให้โค้ดของคุณมีคุณภาพและมีประสิทธิภาพมากขึ้น

อ่านเพิ่มเติม isset() และ empty() ว่ามันมีการทำงานและประโยชน์อย่างไร">มาทำความเข้าใจ isset() และ empty() ว่ามันมีการทำงานและประโยชน์อย่างไร





9. อย่าใช้ array_push()

เพราะ array_push() เป็นฟังก์ชั่น การเรียกใช้ฟังก์ชั่นในทุกๆ ภาษาโปรแกรมนั้นใช้เวลามากกว่าโค้ดปกติเสมอ
ใน PHP มี operator []= ที่จะทำการเพิ่มสมาชิกของ array ในตำแหน่งท้ายสุด


ช้ามาก
$arr = array(); array_push($arr, 1); array_push($arr, 2); array_push($arr, 3); array_push($arr, 4); array_push($arr, 5);


เร็วกว่ามากๆ
$arr = array(); $arr[] = 1; $arr[] = 2; $arr[] = 3; $arr[] = 4; $arr[] = 5;


แล้ว array_push() มีไว้ทำไม?
เราควรใช้ array_push() ในกรณีที่เราต้องการเพิ่มสมาชิกมากกว่า 1 ตัวในคราวเดียว


ประโยชน์ของมันจริงๆ
$arr = array(); array_push($arr, 1, 2, 3, 4, 5);


แต่ถึงกระนั้น จากตัวอย่างข้างบน การเพิ่มสมาชิก 5 ตัว (หรือมากกว่านั้น) ใช้ operator []= ก็ยังเร็วกว่าการใช้ array_push() อยู่ดี

ทดสอบความเร็ว
<?php $t = microtime(true); for ($i = 0; $i < 100000; ++$i) { $arr = array(); $arr[] = $i; $arr[] = $i; $arr[] = $i; $arr[] = $i; $arr[] = $i; } $time['[]='] = microtime(true) - $t; $t = microtime(true); for ($i = 0; $i < 100000; ++$i) { $arr = array(); array_push($arr, $i, $i, $i, $i, $i); } $time['array_push()'] = microtime(true) - $t; print_r($time);


ดังนั้น array_push() จึงแทบจะไม่มีประโยชน์เลย ยกเว้นว่าจะใช้เป็น variable function หรือทำให้โค้ดสั้นลงโดยการเรียกผ่านcall_user_func_array() เพื่อเพิ่มสมาชิกหลายๆ ตัวในกรณีที่ไม่รู้จำนวนที่แน่นอน

แต่กรณีดังกล่าวใช้ array_merge() จะเร็วกว่า เพราะเรียกใช้ฟังก์ชั่นแค่ครั้งเดียว


$some_array = array(4, 5, 6); $arr = array(1, 2, 3); $arr = array_merge($arr, $some_array); print_r($arr);


ผลลัพธ์
Array ( [0] => 1 [1] => 2 [2] => 3 [3] => 4 [4] => 5 [5] => 6 )


สรุป อย่าใช้ array_push() ครับ




10. ใช้ foreach แทน for ในการเข้าถึงสมาชิกของ array

ลักษณะนี้เห็นกันบ่อยๆ
$arr = array(1, 2, 3, 4, 5, 6, 7, 8, 9, 10); $n = count($arr); for ($i = 0; $i < $n; $i++) { if ($arr[$i] > 5) { echo $arr[$i]; } }


จากตัวอย่างข้างบนมีการทำงานที่ดูเหมือนจะปกติในลักษณะของการเขียนโปรแกรมในภาษาอื่นๆ โดยทั่วไป
แต่สำหรับใน PHP นั้น การเขียนในรูปแบบนี้ถือว่าไม่จำเป็น เพราะ PHP มี foreach


ใช้ foreach
$arr = array(1, 2, 3, 4, 5, 6, 7, 8, 9, 10); foreach ($arr as $value) { if ($value > 5) { echo $value; } }


จากตัวอย่างข้างบนเมื่อเปรียบเทียบกับตัวอย่างก่อนหน้า จะประหยัดการทำงานไปได้ถึง 2 ขั้นตอน
1. ไม่ต้องเรียกใช้ count()
2. ไม่ต้องสั่งให้ PHP ค้นหาสมาชิกใน array เพื่ออ่านค่า ($arr[$i])
เพราะ foreach ทำทุกอย่างให้หมดแล้ว ตั้งแต่การหาจำนวนสมาชิก และการอ่านค่า และเป็นการทำงานในระดับ op code ซึ่งเร็วกว่ามาก


ถ้าอยากได้ค่า index ด้วย
$arr = array(1, 2, 3, 4, 5, 6, 7, 8, 9, 10); foreach ($arr as $i => $value) { if ($value > 5) { echo "$i = $value"; } }


จริงๆ แล้ว foreach ไม่ใช่เรื่องระดับสูงแต่เป็น Control Structure พื้นฐานของ PHP เลยก็ว่าได้ แต่ที่ต้องกล่าวถึงเพราะเห็นหลายๆ คนในบอร์ดยังเขียนการเข้าถึง indexed array ด้วย for อยู่ ซึ่งอาจจะเป็นเพราะเคยเรียนรู้ภาษาอื่นๆ มาก่อน เช่น C หรือ Java และไม่ทราบว่า PHP มี foreach

หรืออาจเป็นเพราะในบางครั้งต้องการเขียนการเข้าถึงไปพร้อมๆ กับการเปลี่ยนแปลงค่านั้นๆ


เข้าถึงและเปลี่ยนแปลงค่าด้วย for
$arr = array(1, 2, 3, 4, 5, 6, 7, 8, 9, 10); $n = count($arr); for ($i = 0; $i < $n; $i++) { if ($arr[$i] > 5) { $arr[$i] = $i * 50; } }


ซึ่งในกรณีนี้ PHP มี syntax พิเศษที่จะทำให้สามารถเปลี่ยนแปลงค่าของสมาชิกใน array ได้

เข้าถึงและเปลี่ยนแปลงค่าด้วย foreach ด้วยการ assign by reference
$arr = array(1, 2, 3, 4, 5, 6, 7, 8, 9, 10); foreach ($arr as &$value) { // สังเกตว่ามี & หน้า $value if ($value > 5) { $value = $i * 50; } }


จากตัวอย่างข้างบน $value นั้นจะชี้ไปยังสมาชิกปัจจุบันใน $arr เมื่อเปลี่ยนแปลงค่า $value สมาชิกในตำแหน่งปัจจุบันของ $arr ก็จะเปลี่ยนแปลงด้วย กล่าวคือ $value คือ reference ของสมาชิกแต่ละตัวใน $arr

แต่ข้อควรระวังคือ เมื่อจบการทำงานของ foreach แล้ว reference ดังกล่าวจะคงอยู่


$value ยังคงเป็น reference อยู่แม้จะจบ foreach แล้ว
$arr = array(1, 2, 3, 4, 5, 6, 7, 8, 9, 10); foreach ($arr as &$value) { if ($value > 5) { $value = $i * 50; } } $value = 1234; // ตรงนี้จะทำให้สมาชิกตัวสุดท้ายใน $arr มีค่าเท่ากับ $value


ดังนั้นเมื่อจบ foreach และอาจจะมีการใช้ตัวแปรชื่อเดียวกันกับตอน assign by reference เราควร unset() ตัวแปรนั้นก่อน

unset() เพื่อทำลาย reference
$arr = array(1, 2, 3, 4, 5, 6, 7, 8, 9, 10); foreach ($arr as &$value) { if ($value > 5) { $value = $i * 50; } } unset($value); // ทำลาย reference $value = 1234; // จุดนี้จะไม่มีผลต่อ $arr แล้ว



0 ความคิดเห็น:

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


พื้นที่โฆษณา

Free Hosting

พื้นที่โฆษณา

Free Hosting
 

Copyright © สอนเขียนโปรแกรม html php css Java SQL jQuery XML Ajax Design by ScriptMasterWebDesign | Theme by ScriptMasterWebDesign | Powered by HosTing