Zkrácení názvu vestavěných funkcí

Školení, která pořádám

V phpMinAdminu jsem přemýšlel o tom, jak by se daly zkrátit názvy vestavěných funkcí za účelem minimalizace velikosti souboru. Základní myšlenka je jednoduchá – obalit funkci s dlouhým názvem obálkovou funkcí s kratším názvem a v kódu volat tuto obálku. Při realizaci této myšlenky ale narazíme na několik problémů.

Řada funkcí má proměnný počet parametrů a ne vždy lze určit výchozí hodnotu parametru, protože funkce se interně nerozhodují podle hodnoty parametrů, ale podle toho, kolik parametrů bylo předáno. Např. funkce fgets přijímá v druhém parametru délku a pokud tuto délku nepředáme, tak ji automaticky detekuje. Obálku nad funkcí tedy nelze napsat takto:

<?php
function fgets_default($handle, $length = 0) {
    return fgets($handle, $length);
}
?>

Nulová délka (stejně jako např. hodnota null) je totiž něco jiného, než kompletní vynechání daného parametru. Funkci tedy musíme napsat univerzálněji:

<?php
function fgets_get() {
    $args = func_get_args();
    return call_user_func_array('fgets', $args);
}
?>

Hodnotu func_get_args nelze předávat jiným funkcím, ale je nutné ji přiřadit do pomocné proměnné.

Situace se komplikuje u funkcí s parametry předávanými referencí. Funkce func_get_args totiž vrací argumenty hodnotou, takže se nedá použít. A když jedna funkce přijímá proměnlivý počet parametrů (bez určitelné výchozí hodnoty) a některé z nich referencí, nezbývá nic jiného, než nějaké takovéto řešení:

<?php
function preg_match_switch($pattern, $subject, &$matches = null, $flags = null, $offset = null) {
    switch (func_num_args()) {
        case 2: return preg_match($pattern, $subject);
        case 3: return preg_match($pattern, $subject, $matches);
        case 4: return preg_match($pattern, $subject, $matches, $flags);
        case 5: return preg_match($pattern, $subject, $matches, $flags, $offset);
    }
}
?>

Mezi uvedenými variantami by se dalo rozhodovat pomocí reflexe, ta ale např. před PHP 5.3 vrací pro preg_match pouze tři parametry (i když jich je podporovaných pět).

Pracnost implementace spolu se zpomalením kódu (volání obálkové funkce spojené navíc ještě s nepřímým voláním pomocí call_user_func_array) bylo důvodem, proč jsem ke zkracování vestavěných funkcí nepřistoupil. Možná alespoň ručně zkrátím nějaké často používané funkce s dlouhým názvem (správně, především htmlspecialchars).

V běžných PHP skriptech zkracování vestavěných funkcí nedoporučuji používat, protože to kromě snížení výkonnosti také zhoršuje čitelnost kódu.

Jakub Vrána, Řešení problému, 1.5.2009, diskuse: 29 (nové: 0)

Diskuse

ikona zakjan:

Jednou mě taky napadlo nějak sjednotit názvy běžných PHP funkcí (třeba str_replace x strtr) a našel jsem tohle:
http://cz2.php.net/manual/en/function.rename-function.php
PECL balík má snad každý vývojář.

ikona Jakub Vrána OpenID:

Na přítomnost extenze APD se obecně samozřejmě nedá spoléhat. Stejný účel má i funkce runkit_function_rename(), ale vestavěná není pochopitelně ani ta.

Dundee:

Používat jakýkoliv debugger (APD, Xdebug) na produkčním stroji bych nedoporučoval. Dost se tím ztrácí výkon.

Miroslav Hruška:

Neřekl bych že je dobrej nápad takhle zkracovat názvy funkcí. Jednou sem měl upravovat kód po někom, kdo si takhle krátil ťuky na klávesnici a bylo to hotové peklo. Například místo str_replace tam mél něco jako srplc ... a já ani na první pohled nevidim, jestli se jedná jen o zkráceninu nebo jestli tam je ještě nějaký obálkový kód a tak není na první pohled zřejmé, co to vlastné udělá. Krácení má pouze 3 výhody a milion nevýhod:

1) Kdykoliv si můžu přidat nějaký nadstavbový kód pro danou funkci (to má výhodu funkční ale ne v čitelnosti)
2) Dá se zkrátit volání dlouhýh metod a funkcí (což mě nevadí, u funkce htmlspecialchars stačí v mém PDT napsat "htmls" a pak jen odbouchnout alt+mezerník a je to)
3) Zmenšení velikosti kódu - nevim do jaké míry je tohle výhoda, kód se sice zmenší ale náklady na jeho volání se zvýší (když vezmu v potaz kolikrát volám magickou funkci htmlspecialchars tak už by se ten propad výkonu dal i naměřit).

Myslim že oproti dobře čitelnému kódu jsou tyhle výhody jen minimální.

ikona Jakub Vrána OpenID:

Se všemi poznatky se ztotožňuji.

O zkrácení názvů funkcí jsem uvažoval v phpMinAdminu, kde je velikost zásadní. Zkrácení by navíc probíhalo automaticky v době kompilace, zdrojové kódy by zůstaly s originálními názvy funkcí. Čitelnost zkompilovaného souboru by se snížila z "nečitelný" na "zcela nečitelný".

Srigi:

Osobne si myslim, ze skusat dalsiu minimalizaciu velkosti kodu PhpPMinAdmin-a je zbytocnost. Ak by sa tym zmensila dnesna dlzka 160kB na povedzme 80-120kB, nema to zmysel na ukor zmensenej rychlosti skriptu.

Tych 160kB sa cez 56k linku prenesie za 24s, takze aj pri najhorsej moznej konektivite je cas uploadu akceptovatelny.

Osobne by mi nevadilo keby mal nastoj aj okolo 800kB.

ikona Jakub Vrána OpenID:

Souhlasím s tím, že nemá smysl program zmenšovat, pokud by se zpomalil. Jinak se ale kloním k tomu, že čím menší, tím lepší. Otázka také je, jak moc by se program vlastně zpomalil, nemám to změřené.

ikona Miloslav Ponkrác:

Pokud namísto přímého volání funkce jí obalíte a voláte nepřímo, je to kód, který se musí vykonat navíc a paměť, která se musí zabrat navíc. I když je to zcela zanedbatelné.

Nejpomalejší je stejně databázová operace, která nakonec rozhodne o celé rychlosti. A možná rychlost sítě.

To, co pane Vráno děláte, tomu se říká premature optimalization.

ikona Jakub Vrána OpenID:

V článku je uveden přesně ten stejný argument. Nechápu tedy, s čím polemizuješ, možná jsi článek jen ledabyle četl.

Nešlo by o premature optimization, protože by se neměnil samotný zdrojový kód, ale jen jeho zkompilovaná podoba.

A do třetice nepozornosti - postup jsem nikde nepoužil, jak je v článku jasně uvedeno.

ikona Miloslav Ponkrác:

A kromě obfuskace a znepříjemnění luštění PHP sobě i jinému autorovi má toto zkracování jaký význam?

A jako vedlejší efekt poté přistoupí samozřejmě chybovější skript, protože ani pro autora nebude tak jednoduché do něj zasahovat – a v konečném důsledku i potřebnost daleko více času při přidávání nových vlastností do skriptu (protože autor se bude déle rozkoukávat, a protože ho bude muset déle testovat a ladit a hůře se mu budou odstraňovat chyby).

Pokud autor bude muset takový skript opustit na další dobu třeba kvůli zaneprázdněnosti a pak se k němu vrátit – tak to je přesně to, čemu se pak říká sado maso.

Jestli mohu poradit ostatním, nedělejte tyto věci, nezkracujte názvy, v zásadě tím nic nezískáte, a případné problémy mnohonásobně převýší jakýkoli (a to pochybný) zisk.

Pokud nečitelnost vyrábí nějakým transformačním programem, pak při ladění do chyb skriptu opět vstupují i případné chyby transformačního programu.

Zkrátka: Myslím, že tato aktivita není příliš hodná následování. Snad v jediném případě, kdy chcete ztížit až znemožnit čtení Vašeho zdrojáku.

ikona Jakub Vrána OpenID:

Zjevně to není z článku patrné, takže to do něj doplním: o zkrácení názvů funkcí jsem přemýšlel ve zcela specifickém případě, kdy je potřeba co nejvíc minimalizovat velikost souboru. Navíc ani v onom případě jsem k tomu nepřistoupil. Toto je teoretický článek, který se zabývá tím, jak by se něco takového obecně vůbec dalo udělat.

Zkrácení vestavěných funkcí je pro obfuskaci nedostatečné, protože se snadno dají dosadit původní názvy. Naopak zkrácení vlastních funkcí a proměnných se pro obfuskaci použít dá.

ikona Miloslav Ponkrác:

A k čemu je vlastně potřeba zminimalizovat velkost souboru? Neberte to jako kritiku, ale já programuji 20 let a potřeboval jsem to jenom v případech:

1) při nahrávání do microcontrollerů s omezenou pamětí (ale to se php netýká), či byla potřeba soubor nahrát do nějakého storage s brutálně omezenou pamětí

2) při přenosu přes síť o rychlosti 100 baudů / sekundu, kde to velmi zrychlovalo přenos (ale to určitě není dnešní problém).

3) Je to sportem u assembleristů, ale i jim se dá snadno dokázat, že nejkratší asm program je bez problém 5× pomalejší, než to co vypadne z kompilátoru vyššího jazyka, to je obvykle dost zdrtí. Takže v tomto případě je to spíše sport a hec, ale bez praktického významu.

Jinak já nenapsal, že zkrácení vlastních funkcí je dostatečné pro obfuskaci, ale pouze, že porozumění zdrojového kódu mírně ztíží. Obfuskace se skládá z řady opatření dohromady.

Jakákoli věc, která je nezvyklá pro programátora v daném jazyce je „ztížení čtení kódu“.

Jinak nejkratší program nebývá vždy nejrychlejší, ani nejefektivnější program – skoro vždy je zde nějaká matematická závislost blížící se nepřímé úměře mezi velikostí programu v bajtech a výslednou rychlostí a efektivitou programu (pro běžná a rozumná čísla, pro extrémy to neplatí).

ikona Miloslav Ponkrác:

Tu nepřímou úměru jsem zmastil, raději toho nechám. :-) Má tam zkrátka být, že příliš krátké programy bývají dosti neefektivní a pomalejší.

Giacomo:

Ještě byste mohl blíže vysvětlit, jakže měříte tu přenosovou rychlost, v baudech za sekundu?

ikona david@grudl.com:

function h($s) { return htmlSpecialChars($s); } bylo v každém mém skriptu, než jsem začal používat pokročilejší techniky ;)

Dnes už jsem mohem dál. Dnes používám function d($x) { return Debug::dump($x); }  :-)

Martin:

A nebylo by lepší celý minadmin za-gzipovat a při každém volání rozbalit a spustit (eval)? Už sama myšlenka ušetření pár bajtů zkrácením názvů funkcí mi přijde jako pochybná...

ikona Jakub Vrána OpenID:

Myšlenka minimalizace zdrojového kódu je skutečně pochybná, jak dokazují v podstatě všechny JavaScriptové frameworky, které ji používají... Ono totiž těch "pár ušetřených bajtů" dá ve výsledku zkrácení o 53 %.

O kompresi jsem také uvažoval, problém ale je, že na přítomnost žádné rozbalovací extenze se nedá spolehnout a psát si vlastní by znamenalo program zase prodloužit. Navíc použití evalu by znamenalo dramatické zpomalení dané hlavně tím, že na jeho kód se neaplikují PHP akcelerátory.

ikona Miloslav Ponkrác:

U JavaScriptových frameworků je situace zcela odlišná. Vzhledme k tomu, že JavaScript se natahuje po síti mnhonásobně častěji, než PHP – a velmi zpomaluje zejména první spuštění stránky, pak zkrácení této čekací doby má jistý smysl. Snad nemusím připomínat, že JS se vykonává na klientovi, a také se k němu tahá jako součást webové stránky, zatímco PHP zůstává umístěné na serveru a nikam se netahá.

Ale u PHP smysl zkracování naprosto nevidím. Ono je úplně jedno, jestli Váš skript bude mít 100 KB, nebo 500 KB, ve výsledku v tom nebude moc rozdíl.

Rychlost PHP obaleném a zkrácením funkcí jednoznačně zpomalíte, protože PHP nejdříve zkompiluje zdrojový kód do byte kódu, který pak vykonává – a ten je v případě zkrácených funkcí delší, pomalejší a komplikovanější, než kdybyste to nechal tak jak to je. A je úplně jedno, že jste zkrátil zdrojový kód o pár bajtů, protože výsledný byte kód, který se vykonává jste tím prodloužil a zkomplikoval.

Jinak Vám fandím s Vaším phpminadminem, sice nejsem jeho uživatelem, ale vážím si každého, kdo něco tvoří. Držím Vám moc a moc palce a přeji hodně spokojených uživatelů a též abyste sám byl spokojen.

ikona Jakub Vrána OpenID:

phpMinAdmin je určen i pro lidi, kteří potřebují databázovou administraci na různých místech, kam si ji nejdřív musí nainstalovat. Opakované kopírování phpMyAdmina byl důvod, proč jsem chtěl kompaktní jednosouborovou verzi nástroje, který by přitom měl všechny potřebné funkce.

Takže minimalizace velikosti je důležité kritérium a 53 % velikosti ušetřených minifikací (odstranění bílých znaků, komentářů a zkrácení vlastních proměnných a funkcí) je myslím dobrý výsledek. Dalších 5 % by mohla ušetřit minimalizace vestavěných funkcí, z důvodů uvedených v článku jsem k tomu ale nepřistoupil.

Nikde v článku není uvedeno, že bych se velikost snažil zmenšovat kvůli zvýšení výkonnosti, i když u aktuální minifikace to je nepatrný vedlejší efekt.

lopata:

až bude final php 5.3 použij phar, bude součást core a je to extenze přesně na to, co chceš - kompaktní verze.

Používejte diakritiku. Vstup se chápe jako čistý text, ale URL budou převedeny na odkazy a PHP kód uzavřený do <?php ?> bude zvýrazněn. Pokud máte dotaz, který nesouvisí s článkem, zkuste raději diskusi o PHP, zde se odpovědi pravděpodobně nedočkáte.

Jméno: URL: Reakce na: lopata

ikona Jakub Vrána OpenID:

Možnost použití Pharu už jsem také zkoumal, ale kompresi zajišťuje pochopitelně také pouze s extenzí. Pro phpMinAdmin je k nepotřebě, protože vše je v jednom souboru už samo o sobě, dá se ale použít např. pro phpMyAdmin (už jsem viděl ukázku Pharu právě s ním).

ikona v6ak:

Co takhle použít tu náhradu názvů v kombinaci s automatickým rozbalováním - ten kód by pak nemusel být tak dlouhý. Stačilo by pouze přeložit jednotlivé T_STRINGy na jiné. V extrémním případě je možné asi využít T_INVALID nebo co. Pak je už jen potřeba mít tabulku. Je potřeba si při kompresi dát pozor na konstanty.

ikona Jakub Vrána OpenID:

Bohužel nerozumím. Zkus prosím myšlenku lépe vysvětlit.

ikona v6ak:

(Snad teď bude reakce OK.)
No, můj nápad byl ve stručnosti toto:
* použít metodu automatického rozbalení
* použít jednoduchý kompresní algoritmus
* tento komprimát půjde snadno (de)komprimovat i bez extenzí pomocí token_get_all.
Je srozumitelný tento tříbodový stručný popis?
Těm třem bodům jsem ve již věnoval. Vpodstatě jde o nahrazení T_STRINGů kratšímí variantami a zpět.
Doufám, že pokud je něco stále nejasné, tak to aspoň půjde lépe lokalizovat.

ikona Jakub Vrána OpenID:

Už je to jasnější. Máš i nějakou implementaci?

ikona v6ak:

Nemám, takovéto věci jsem zatím nepotřeboval řešit, napadlo mě to včera při cestě na nákup vstupenek a pak jsem měl na práci něco jiného.

xxx:

takto by to nešlo ? ale priznám sa celé som to nečítal :D

http://php.mirrors.ilisys.com.au/manual/en/…-functions.php

ikona Jakub Vrána OpenID:

To by šlo, dobrý náppad. Jediný problém je s voláním vestavěných funkcí zevnitř vlastních funkcí - tam by bylo potřeba použít globální proměnné.

Megaloman:

Při čtení tohoto článku jsem se nejdřív vyděsil (a vzpomněl jsem si na vaše slova o "zbytečné/samoúčelné/nevzpomínámsijaké obfuskaci" z jiné diskuze pod článkem), ale pak jsem se na konci zase uklidnil.

Napadá mě jedna kacířská možnost, a to zavést zkracené aliasy funkcí podporovaných přímo PHP, ale to je buď prasení jazyka, nebo nutnost vlastních (= nepřenositelných) úprav v zdrojáku PHP.
avatar © 2005-2018 Jakub Vrána. Publikované texty můžete přetiskovat pouze se svolením autora. Ukázky kódu smíte používat s uvedením autora a URL tohoto webu bez dalších omezení Creative Commons. Můžeme si tykat. Skripty předpokládají nastavení: magic_quotes_gpc=Off, magic_quotes_runtime=Off, error_reporting=E_ALL & ~E_NOTICE a očekávají předchozí zavolání mysql_set_charset. Skripty by měly být funkční v PHP >= 4.3 a PHP >= 5.0.