Jump to content


* * * * * 1 votes

PHP ծրագրերի օպտիմիզացիա


2 replies to this topic

#1 SkyFlyer

    Administrator

  • Admin
  • PipPip
  • 940 posts

Posted 08 September 2007 - 03:03 PM

PHP ծրագրերի օպտիմիզացիա

Արագությունը որպես ծրագրի ամենակարևոր բաղկացուցիչ

Յուրաքանչյուր ծրագրավորող իր մասնագիտական գործունեության ընթացքում ի վերջո ստիպված է լինում օպտիմիզացնել (optimization or refactoring) իր սեփական ծրագրերը, քանզի նախագծերի հզորացմանը զուգընթաց կտրուկ մեծանում է նրանցում «դժվարանցանելի» մասերի ի հայտ գալը։ Ծրագիրը դառնում է ավելի դանդաղ ու անզգայուն օգտագործողի հրամաններին և նույնիսկ դադարում գործելուց, եթե հարցումների քանակը գերազանցում է կրիտիկական շեմը։

Ինչպես գիտենք ցանկացած խնդրի կանխարգելումը շատ ավելի «էժան» է քան նրա վերացումը։ Ծրագրավորումը բացառություն չէ այդ օրինաչափությունից։ Այսինքն եթե մի փոքր ավելի ուշադրություն դարձնենք նախագծի գրագետ պլանավորմանը և ժամանակի հետ հղկենք մեր ծրագրավորման սովորություններն ու ձևը, ապա ստիպված չեք լինի ամիսներ կամ տարիներ հետո նստել ու փորձել գլուխ հանել «խառնաշփոթից», որը ինքներտ եք ստեղծել։ Ի միջայլոց ասեմ, որ շատ ժամանակատար ու զզվելի մի բան է...

Գրագետ պլանավորումը գալիս է գիտելիքների ու տարիների փորձի հետ, իսկ գրագետ ծրագրավորման ձևը ցավոք շատ հազվադեպ է փոխվում տարիների հետ։ Դա մարդու ձեռագրի նման է ու եթե նա նախընտրում է ասենք ցիկլը կատառել for օպերատրով, ապա շատ դժվար է նրան համոզել փոխել այն foreach-ով, եթե իհարկե լուրջ փաստարկներ չկան (ծրագրավորողներին շատ դժվար է համոզելը իմիջայլոց)։ Հենց այդ նպատակով ստեղծել եմ այս բաժինը, որտեղ հավաքել եմ արագագործության օպտիմիզացման վերջին նորությունները։ Դրանք բուն լեզվի զարգացմանը զուգընթաց փոխվում են, սակայն կաշխատեմ միշտ թարմ պահել այն, քանի որ ես նույնպես հաճախ օգտվում եմ դրանից։

Իհարկե դժվար է միանշանակ խորհուրդներ տալ, որոնք կաշխատեն ցանկացած իրավիճակում, սակայն կան որոշակի «չգրված» օրենքներ, որոնց պահպանումը կարող է զգալիորեն իջեցնել ծրագրի բեռնավորվածությունը և արագացնել նրա աշխատանքը։ Եկեք փորձենք քննարկել դրանցից մի քանիսը։

Quote

Սույն հոդվածի օգտագործումը (վերատպումը) թույլատրվում է միայն բարի նպատակներով և եթե առկա է հղումը դեպի այս էջը


Մի՛ եղէց անպտուղ ի փոքր վաստակոյս`
Իբր ապաջան սերմանող անբերրի երկրի:
Նարեկացի

#2 SkyFlyer

    Administrator

  • Admin
  • PipPip
  • 940 posts

Posted 08 September 2007 - 08:23 PM

PHP հնարամտություններ ու խորհուրդներ

Ընդհանուր խորհուրդներ
  • Ծրագիրը բազմաթիվ բլոկների բաժանելը մի փոքր դանդաղեցնում է այն, քանի որ փարսերը ստիպված է առանձին առանձին մշակել դրանք։
    <?php
    	. . . . .
    	. . . . .
    	. . . . .
    ?>
    համեմատաբար արագ է քան
    <?php	. . . . .	?>
    <?php	. . . . .	?>
    <?php	. . . . .	?>

  • Մի օգտագործեք տեքստը միացնելու «կետ» (concatenate) օպերատորը, եթե դրա կարիքը իասկապես չունեք
    "SELECT * FROM table WHERE id = $SomeVar ORDER BY id"
    համեմատաբար արագ է քան
    "SELECT * FROM table WHERE id = " . $SomeVar . " ORDER BY id"

  • Պարզ տեքստերում օգտագործեք եզակի չակերտը կրկնակի չակերտի փոխարեն, քանի որ փարսերը փոփոխականներ է փնտրում կրկնակի չակերտի մեջ պարփակված տեքստում, դրանք իրենց արժեքներով փոխարինելու համար, որը մի փոքր դանդաղեցնում է այն
    'Hello World!'
    համեմատաբար արագ է քան
    "Hello World!"

  • Վերոհիշյալից ստացվում է, որ տեքստը միացնելու «կետ» (concatenate) օպերատորը զուգակցելով եզակի չակերտների հետ գրեթե արագության կորուստ չենք ունենում, իսկ երբեմն նույնիսկ շահում ենք
    'SELECT * FROM table WHERE id = ' . $SomeVar . ' ORDER BY id'
    տարօրինակորեն ավելի արագ է քան
    "SELECT * FROM table WHERE id = $SomeVar ORDER BY id"

  • echo համեմատաբար արագ է քան print քանի որ վերջինը ֆունկցիա է ի տարբերություն echo–ի, որը լեզվական կոնստրուկցիա լինելով չի վերադարձնում ֆունկցիայի կատարման կարգավիճակի (status) կոդը, որը մի փոքր դանդաղեցնում է այն։ heredoc շատ ավելի դանդաղ է քան նախորդ երկուսը, քանի որ պահանջում է շատ ավելի գործողություն տեքստը ցուցադրելու համար։
    echo 'Hello World!';
    համեմատաբար արագ է քան
    print 'Hello World!';
    համեմատաբար արագ է քան
    echo <<<TEXT
    Hello World!
    TEXT;

  • Եթե ցուցադրում եք տեքստի մի քանի մասեր, ապա ավելի արագ կստացվի եթե դրանք echo արվի ստորակետներով առանձնացված մասերով քան մեկ այլ ձևով
    echo 'Hello', ՛World!', $myText;
    համեմատաբար արագ է քան
    echo 'Hello' . 'World!' . $myText;

  • Օգտագործեք true կամ false, TRUE կամ FALSE փոխարեն, քանի որ նման հաստատունները ներքուստ պահվում են փոքրատառերով հաշ ցուցակում (hash table)
    $a = true;
    համեմատաբար արագ է քան
    $a = TRUE;

  • Խուսափեք ֆունկցիաների անիմաստ օգտագործումից եթե դրանք կարելի է փոխարինել հաստատունով (constant)
    $a = php_version();
    համեմատաբար արագ է քան
    $a = PHP_VERSION;

  • Ֆայլի հարաբերական ճանապարհը (path) շատ հարմար է սակայն զգալիորեն դանդաղ է լրիվ ճանապարհից (full path)
    require "/full/path/foo.php";
    զգալի արագ է քան
    require "foo.php";

  • Ֆայլի ներգրավման ժամանակ _once կիրառելու դեպքում կատարվում է որոշ գործողություններ, որոնցից կարելի է խուսափել հնարավորության դեպքում
    require_once "/full/path/foo.php";
    զգալի արագ է քան
    require "/full/path/foo.php";

  • Եթե ճշտությունը էական չէ, ապա time() ֆունկցիան կանչելու փոխարեն $_SERVER['REQUEST_TIME'] կանչելու դեպքում կունենանք նույն ժամանակը վարկյանի ճշտությամբ առանց որևէ ֆունկցիայի կանչի
    $time = time();
    մի քանի անգամ ավելի արագ է քան
    $time = $_SERVER['REQUEST_TIME'];

  • Եթե ցանկանում եք ստուգել տեքստի չափը, ներքոհիշյալ մեթոդը շատ ավելի արագ է քան strlen() ֆունկցիայի օգտագործումը, քանզի ոչ մի հաշվարկ չի կատարում
    if (!isset($foo{5})) {. . .}
    համեմատաբար արագ է քան
    if (strlen($foo) < 5) {. . .}

  • Համեմատության դեպքում տերնար (ternary) համեմատությունը շատ ավելի արագ է սովորական if()-ից
    code]echo $a >= 0 ? 'positive' : 'negative';[/code]համեմատաբար արագ է քան
    if ($a >= 0) {echo 'positive';} else {echo 'negative';}

  • Համեմատությունների մեջ նախընտրելի է հաստատուն (constant) արժեքը տեղադրել ձախ կողմում, քանի որ դա և արագ է և օգնում է միանգամից գտնել սխալը հավասար (= =) օպերատորի դեպքում վերագրել (=) օպերատորի կիրառման դեպքում, որը ահավոր դժվար է երբեմն նկատելը։ Ձախ կողմում գրելով և վերագրման օպերատոր օգտագործելու դեպքում փարսերը զգուշացում (warning) կցուցադրի ի տարբերություն հակառակ դեպքի երբ ոչինչ չի ցուցադրվի։
    if (42 == $foo) {. . .}
    համեմատաբար արագ է և «անվտանգ» քան
    if ($foo == 42) {. . .}

  • Օգտագործելուց հետո վերացրեք unset-ով շատ տվյալ պարունակող փոփոխականները՝ հատկապես մեծ շարքերը (array), հիշողությունը ազատելով դրանցից։ Դա զգալիորեն կթեթևացնի սերվերի ու փարսերի աշխատանքը։
Շարքեր (array)
  • Շարքի անդամները միշտ ներառեք եզակի չակերտների մեջ, այլապես փարսերը կկարծի թե նկատի ունեք հաստատուն (constant) ու բացի այդ եթե միացված են բոլոր սխալները, ապա կցուցադրի նաև նշում (notice):
    $arr['member']
    x7 անգամ արագ է քան
    $arr[member]

  • Հղումը (reference) կպարզեցնի և կրկնակի անգամ կարագացնի բազմաչափ շարքերի հետ կատարվող օպերացիաները
    $ref =& $a['b']['c'];
    for($i = 0; $i < 5; $i++)
    $ref[$i] = $i;
    կրկնակի անգամ արագ է քան
    $a['b']['c'] = array();
    for($i = 0; $i < 5; $i++)
    $a['b']['c'][$i] = $i;

  • Շարքի անդամները ստուգելուց միշտ օգտագործեք isset(), քանի որ այն միանգամից օգտվում է հիշողության մեջ գտնվող շարքի հաշ ցուցակից (hash table)
    if (isset($keys['member'])) { ... }
    մի քանի անգամ արագ է քան
    if(array_key_exists('member', $keys)) { ... }
Ցիկլեր
  • Ցիկլերում վերին կամ ստորին շեմը նախորոք հաշվեք, այլապես փարսերը այն հաշվելու է ամեն նոր ցիկլի ընթացքում, զգալիորեն դանդաղեցնելով ծրագիրը
    for ($i = 0, $max = sizeof($arr); $i < $max; $i++) {. . .}
    համեմատաբար արագ է քան
    for ($i = 0; $i < sizeof($arr); $i++) {. . .}

  • Դուրս հանեք ցանկացած հաշվարկ ցիկլի մարմնից, եթե հնարավոր է: Քանի որ n * m հնարավոր է հաշվել ցիկլի մարմնից դուրս, ապա կատարելով այն դրսում մենք չենք ստիպի փարսերին 1000 անգամ հաշվելու միևնույն n * m տվյալ օրինակում։
    $n = 10;
    $m = 20;
    $x = $n * $m
    for ($i = 0; $i < 1000; $i++) {$x + $i}
    համեմատաբար արագ է քան
    $n = 10;
    $m = 20;
    for ($i = 0; $i < 1000; $i++) {$n * $m + $i}
Ֆունկցիաներ
  • Կլասններում ստատիկ ֆունկցիայի հայտարարելու դեպքում կրկնակի կշահեք արագության մեջ
    class bench {public static function a() { return 1; }}
    կրկնակի արագ է երբ կանչենք bench::a() քան
    class bench {public function a() { return 1; }}

Մի՛ եղէց անպտուղ ի փոքր վաստակոյս`
Իբր ապաջան սերմանող անբերրի երկրի:
Նարեկացի

#3 SkyFlyer

    Administrator

  • Admin
  • PipPip
  • 940 posts

Posted 08 September 2007 - 08:49 PM

MySQL հնարամտություններ ու խորհուրդներ
  • Միայն ընտրեք (select) այն սյունակները, որոնք օգտագործելու եք, այլապես մնացած սյունակները իզուր տեղ են զբաղեցնելու հիշողության մեջ։
    SELECT a, b FROM table
    ավելի էֆեկտիվ է ու գրագետ քան
    SELECT * FROM table

  • Մի տեղադրեք տվյալներ բազայում, որոնք շատ հազվադեպ են փոխվում՝ ասենք կոնֆիգուրացիոն պարամետրեր։ Դրա փոխարեն դրանք տեղադրեք գլոբալ շարքերում (array) կամ ներգրավվող (include) ֆայլում
    $CONFIG = array('a'=>1, 'b'=>2);
    ավելի էֆեկտիվ է ու արագ քան
    SELECT a, b FROM config_table

  • Ինդեքսավորեք WHERE և ORDER BY պայմաննեռում օգտագործվող սյունակները (օրինակում c և d սյունակները)
    SELECT a, b, c FROM table WHERE b=1 ORDER BY c

  • Օպտիմիզացրեք յուրաքանչյուր հարցումը առանձին, օգտագործելով EXPLAIN։ Այն շատ կօգնի օպտիմիզացման աշխատանքներին երբեմն անհավատալի չափերով արագացնելով դրանք
    EXPLAIN select * from users where login LIKE '%SkyFlyer%';
    +----------+-------+-----------------+------+----------+------+--------+--------------+
    |   table  |  type |  possible_keys  | key  | key_len  |  ref | rows   |   Extra	  |
    +----------+-------+-----------------+------+----------+------+--------+--------------+
    |   users  |   ALL |		NULL	 | NULL |   NULL   | NULL | 27506  |  where used  |
    +----------+-------+-----------------+------+----------+------+--------+--------------+
    Ուշադրություն դարձրեք մշակված տողերի քանակ նվազումը 27506–ից մինչև 2–ը օպտիմիզացնելուց (ստեղծվել է login ինդեքսը) հետո
    EXPLAIN select * from users where login LIKE '%SkyFlyer%';
    +----------+-------+-----------------+------+----------+------+--------+--------------+
    |   table  |  type |  possible_keys  | key  |  key_len | ref  | rows   |   Extra	  |
    +----------+-------+-----------------+------+----------+------+--------+--------------+
    |   users  | range |	 login	   | login|	 50   | NULL |	2   |  where used  |
    +----------+-------+-----------------+------+----------+------+--------+--------------+

  • Եթե մշակելու եք եզակի գրանցում (record) ապա անպայման օգտագործեք LIMIT 1 հրամանը հարցման մարմնում։ Այն անմիջապես կդադարացնի բոլոր գործողությունները տվյալ գրանցման մշակումից հետո, հակառակ դեպքում բազան ստիպված է կարդալ բոլոր տողերը, նույնիսկ գլխի չընկնելով, որ իր փնտրած արդյունքը եզակի է ու ինքը արդեն գտել է այն: Ջնջելու գործողության ժամանակ այն նույնիսկ պարտադիր է, այլապես սխալ հարցման դեպքում կարող եք կորցնել ամբողջ տվյալները միայն մեկի փեխարեն։
    DELETE a FROM table WHERE a=1 LIMIT 1
    ավելի էֆեկտիվ է ու ապահով քան
    DELETE a FROM table WHERE a=1

  • Ինդեքսները շատ օգտակար են այն ցուցակներում (table), որտեղ շատ են տվյալների փնտրումը (գերազանցում են SELECT հարցումները) և անօգտակար, որտեղ շատ են տվյալների գրանցումն ու փոփոխումը (գերազանցում են INSERT, UPDATE, DELETE հարցումները) և դանդաղեցնում այդ օպերացիաները։

  • Ցուցակների առաջնային բալանին (primary key) պետք է հնարավորինս փոքր չափս ու համապատասխան տվյալների տեսակ ունենա յուրաքանչյուր գրանցումը եզակիորեն բնորոշելու համար։ Քանի որ դրանք ինդեքսավորվում են ավտոմատ կերպով իրենց ստեղծման պահից, ապա սխալ ընտրությունը շատ թանկ կնստի մեծ ցուցակների դեպքում։

  • Մի հարցրեք տվյալների բազային միևնույն բանը բազում անգամներ, այլ հիշեք հարցման արդյունքն ու օգտագործեք այն նոր հարցում կատարելու փոխարեն։

  • Մի օգտագործեք NOT NULL որպես դաշտի սկզբնական արժեք (default value) եթե հնարավոր է, քանի որ դրանով կարագացնեք հարցումները և կխնայոք 1 բայթ յքուրաքանչյուր գրանցման (record) համար։

  • Օգտագործեք հնարավորինս փոքր չափսեր ու ճիշտ տվյալների տեսակ տվյալների բազայի դաշտերը ստեղծելիս։ Մեծ դաշտերը ավելի դժվար են մշակվում ու ավելի դանդաղ են։ Ահա դրանց նկարագրությունն ու չափսերը MySQL 5 համար։
    Numeric Types
    Type		  size(bytes)					signed								unsigned
    –––––––––––––––––––------------------------------------–––––––––––––––––---------------------
    TINYINT		1							-128...127							  0...255  
    SMALLINT	   2						 -32768...32767						   0...65535  
    MEDIUMINT	  3					 -8388608...8388607						0...16777215  
    INT			4				 -2147483648...2147483647					0...4294967295  
    BIGINT		 8	   -9223372036854775808...9223372036854775807  0...18446744073709551615  
    
    FLOAT		  4  -3.402823466E+38...-1.175494351E-38, 0, 1.175494351E-38...3.402823466E+38
    DOUBLE		 8  -1.7976931348623157E+308...-2.2250738585072014E-308, 0, 2.2250738585072014E-308...1.7976931348623157E+308
    DECIMAL(M,D)   M+2 if D > 0, M+1 if D = 0 (D+2, if M < D) (65 characters maximum)
    Date and Time Types
    Type				size (bytes)			   range
    –––––––––––––––------------------------------––––––––––––––----------------------------------------–––---------------------
    DATE				3				 '1000-01-01'...'9999-12-31'
    DATETIME			8				 '1000-01-01 00:00:00'...'9999-12-31 23:59:59'
    TIMESTAMP		   4				 (depending on the MySQL version and the SQL mode the server is running)
    TIME				3				 '-838:59:59'...'838:59:59'
    YEAR				1				 1901...2155
    String Types
    Type													 size (bytes)											
    –––––––––––––––------------------------------––––––––––––––----------------------------------------–––---------------------
    CHAR(M) 					   M characters, 0 <= M  <= 255
    VARCHAR(M) 					L characters + 1 byte, where L <= M and 0 <= M <= 255 or L characters + 2, where L <= M and 256 <= M <= 65535. Prior to MySQL 5.0.3: L characters + 1 byte, where L <= M and 0 <= M <= 255.
    BINARY(M) 					 M, 0 <= M <= 255
    VARBINARY(M) 				  L + 1, where L <= M and 0 <= M <= 255 or L + 2, where L <= M and 256 <= M <= 65535. Prior to MySQL 5.0.3: L + 1, where L <= M and 0 <= M <= 255.
    TINYBLOB 					  L + 1, where L < 28
    TINYTEXT 					  L characters + 1 byte, where L < 28
    BLOB 						  L + 2, where L < 216
    TEXT 						  L characters + 2, where L < 216
    MEDIUMBLOB 					L + 3, where L < 224
    MEDIUMTEXT 					L characters + 3, where L < 224
    LONGBLOB 					  L + 4, where L < 232
    LONGTEXT 					  L characters + 4, where L < 232
    ENUM('value1','value2',...)	1 or 2, depending on the number of enumeration values (65,535 values maximum)
    SET('value1','value2',...) 	1, 2, 3, 4, or 8, depending on the number of set members (64 members maximum)

Մի՛ եղէց անպտուղ ի փոքր վաստակոյս`
Իբր ապաջան սերմանող անբերրի երկրի:
Նարեկացի