Diskuze: Optimalny (efektivnejsi) kod... PHP
V předchozím kvízu, Online test znalostí PHP, jsme si ověřili nabyté zkušenosti z kurzu.


- Tezko rici, nezname ucel.
- Neni mi jasne, proc si ukladas pointery zvlast do promenych $pr1 = $poleData[$i][$key1];.
- slo by misto toho pouzit list ($a, $b, $c) = $poleData[$i]
- misto global bych pouzil and. function vypocitaj(&$myarray, $i)
- ty potrebujes k necemu cele to pole uvnitr? Pokud ne, predal bych mu jen tu cast, kterou potrebujes vypocitaj( $poleData[$i])
$poleData obsahuje jednotlive riadky z CSV suboru, potrebujem z tych cca 35 stlpcov vybrat iba niektore podla danych kriterii, povedzme tak 10-15 stlpcov. Skript prechadza jednotlive riadky pola $poleData a podla kriterii vybera jednotlive polozky ako napr. $meno = $poleData[$riadok]['meno'] atd. . Dalej z nich vytvara nieco ako osobnu kartu podla sablony (v kode vyssie to je ta funkcia vypocitaj, ktorej sa predavaju jednotlive premenne )
Patrik Valkovič:14.5.2019 18:50
Hlavní věc, na kterou si musíš dát pozor je, že PHP předává pole
hodnotou (tj. celé pole se překopíruje). https://stackoverflow.com/…by-reference
Tj. druhá varianta je nejhorší
Jinak zbývající varianty jsou cca ekvivalentní (nebo alespoň zanedbatelně
rozdílné). Obdobně by to bylo u druhé varianty, kdyby jsi použil předání
referencí.
Podobnou otázku jsem řešil u tvorby výkazu práce a docházky jako
podkladu pro fakturaci a výplaty. Měla několik vrstev informací a pro
různé lidi platily mezi vrstvami různé podmínky výpočtu. Pak do toho
vlezl náhled do historie kvůli dovolené, svátkům tj. souvstažnostem
ročního charakteru. Podmínek bylo již tolik, že jsem vstupní data nakonec
rozsekal na minutové úseky, kde mi šlo situaci lépe vyřešit. Např.
někomu se třeba započítává doba před smluveným modelem pracovní doby
jinému ne, ale pokud tam má třeba vrstvu domluvené zakázky tak zase ano.
Nebo existují jiné sazby pro víkendy, nebo držení pohotovosti kdy
evidovaná v pracovní době není bonusově proplácena, ale mimo ano.. atd.
atd... Tím samo vzniklo obrovské množství dat a zdroje serveru, virtuální
mašina, která plnila hromady dalších úkolů díky nekonečným cron
úlohám, nebyly zrovna neomezené.
Přesto na problém s pamětí jsem nenarazil. Je fakt, že jsem to přechroupal
postupně.
Tj. ze surových data vybral a poskládal do struktury, která se bude dobře
procházet. Myšlenka je taková, že se pracuje už jen s relevantními daty v
dané úloze, výběr ze surových dat se provede jen jednou.
Ale konkrétně. Na zpracování textových vstupů tj. well-formed CSV (nevím jak řešíš validitu vstupu) se mi osvědčily regulární výrazy než nějaké for cykly. Takže csv bych projel regulárem a vytáhl informace, které potřebuji. Převést na objekt nebo assoc pole, ale dle mne máš pořešeno.
Filip:14.5.2019 18:59
Aktualne pouzivam tretiu variantu (globalne pole) , ta prva varianta mi pride chaoticka, v skutocnosti predavam tej funkcii az 15 roznych premennych, pride mi to neprehladne a skor nachylne na chybu. Druhu variantu som vylucil, tusil som ze predavat cele (velke) pole bude narocne na pamat a asi aj na rychlost ale bol som zvedavi na nazor. Este ma napadlo, z tych 15 premennych vytvorit pole a predat iba to jedno pole?
Patrik Valkovič:14.5.2019 19:18
Třetí varianta není taktéž nic moc, protože se spoléháš na nějaký
globální objekt. Nejlepší mi příjde předat pole referencí (pokud ho
máš již v paměti).
Úplně nejlepší by bylo nečíst celé CSV ale jen části, které
potřebuješ.
Filip:14.5.2019 19:18
Diky. Mne to funguje - zatial iba na localhoste, ked to nahram na server neviem ci nebude problem s memory_limit u PHP. Moja uloha nieje zlozita, nieco ako dochadzkovy system - v CSV subore moze byt az 40 tisic riadkov z toho asi 70-80% sa opakuje (rovnake meno+priezvisko), kde sa meni udaj iba v par stlpcoch ako napr. 'prichod', 'odchod', 'odpracovane hodiny', a pod. Skript z toho povodneho pola $poleData (v podstate RAW data z CSV suboru) vytahuje jednotlive unikatne zaznamy a uklada do noveho pola kde su uz spracovane aj ostatne vysledky podla roznych kriterii a nakoniec sa tie data (to nove pole) zobrazi v tabulke v html...
Jasně, takže pokud chápu správně jde skoro vždy až o 80% řádků a
pak cca 30% sloupců, které reálně potřebuješ? 1/3 sloupců může někde
znamenat úsporu až 2/3 nároků...
No musel bys to napsat více konkrétně... vstupní data vypadají nějak tak a
má z toho vypadnout tento formulář, kdy podmínky výběru jsou tyto a takto
vypadá typický dotaz, který je potřeba vyřešit. V teoretické rovině,
bych řekl, že je tu nápadů dost .
Filip:14.5.2019 19:37
jj, preto som sa pytal. Globalne premenne sa vo vseobecnosti nedoporucuje
robit. ... CSV citat po castiach - neviem si to predstavit, kedze z neho musim
vytiahnut unikatne zaznamy - prechadzam riadok po riadku a po spracovani riadku
(...nejake vypocty apod.) sa ulozi do noveho pola ktore sa vo finale zobrazi v
tabulke.... v PHP som toho moc nenapisal, musim este dostudovat
Filip:14.5.2019 20:22
... uloha je jednoducha ale rieseni moze byt viacej
Zjednoduseny priklad - format CSV suboru:
Meno; Priezvisko; Adresa; Datum narodenia; Nastup; Odpracovane hodiny; Prichod; Odchod; Odjazdene hodiny; SPZ; Trzba
Jan; Novak; Adresa 1; 01.01.1990; 05.05.2015; 7; 08:00; 15:00; 5; CGHU5; 174
Jan; Novak; Adresa 1; 01.01.1990; 05.05.2015; 4; 08:00; 12:00; 1; CGHU5; 151
Jan; Novak; Adresa 1; 01.01.1990; 05.05.2015; 3; 08:00; 15:00; 2; CGHU5; 114
Ivo; Klaun; Adresa 5; 01.05.1993; 01.01.2014; 6; 08:00; 15:00; 5; AFU1; 188
Jan; Novak; Adresa 1; 01.01.1990; 05.05.2015; 8; 08:00; 16:00; 2; CGHU5; 119
Ivo; Klaun; Adresa 5; 01.05.1993; 01.01.2014; 5; 08:00; 13:00; 1; AFU1; 120
Jan; Novak; Adresa 1; 01.01.1990; 05.05.2015; 6; 08:00; 14:00; 5; CGHU5; 174
Ivo; Klaun; Adresa 5; 01.05.1993; 01.01.2014; 7; 08:00; 15:00; 6; AFU1; 171
Ivo; Klaun; Adresa 5; 01.05.1993; 01.01.2014; 6; 08:00; 14:00; 2; AFU1; 120
Jan; Novak; Adresa 1; 01.01.1990; 05.05.2015; 7; 08:00; 15:00; 4; CGHU5; 154
Uloha je vytvorit zoznam s unikatnymi osobami, kde su spocitane celkove
odpracovane hodiny, celkove odjazdene hodiny, celkova trzba a priemerna trzba za
hodinu. (+ osobna karta kazdej osoby kde sa podla roznych podmienok bude
vyhodnocovat jej stav apod.)
Cize z povodneho zoznamu kde je 10 riadkov vznikne zoznam iba s 2. riadkami.
Tomáš Novotný:14.5.2019 20:59
Tak podle tohoto, mi vyvstalo na mysli více dotazů než odpovědí
Zase budu trochu vycházet z toho s čím jsem se již setkal...
1. který z údajů bereš za jedinečný? z uvedených podle mne není ani
jeden, ikdyž je pravda, že u některých je malá pravděpodobnost...ale
nikdy nevíš... takže nějaká kombinace vícero sloupců?
možná ta SPZ, pokud si nepůjčují mezi sebou auta
2 . mluvil si o 35 sloupcích? tu je jen 11? což podle mne ovlivňuje způsob
výběru dat... ty další jsou kde?
3. ještě je to jednorázový proces zpracování dat? nebo se s tím pracuje
vícekrát? náhled a zpětná analýza, jiná období apod..?
pokud popisuješ, že je tam několik funkcí pro osobní kartu atd.... to
už vidím spíše na nějakou databázi, pokud ji z nějakého důvodu
nechceš, tak by bylo vhodné alespoň csv rozsekat podle těch 'unikátních
osob' na samostatná csv (osobně preferuji spíše nějaký xml-like
format)
protože jinak osobní karta člověka s třeba 400 řádky bude i tak vždy
zpracovávat CSV se 40tis řádky což teda fuj...
Filip:14.5.2019 21:23
Prosim neber to doslovne. To je iba zjednoduseny priklad, ale podstata je ta
ista. Povodny subor tu zverejnit nemozem.
Ten CSV subor je pevne dany a ja stym nemozem nic spravit. Kazdym dnom pribuda
jeho velkost (pribudaju nove riadky)
- jedinecny udaj mam ID, na tom priklade to moze byt datum narodenia.
- v originale mam stlpcov 35, urcite pouzivat budem 15, dalsich cca 8-10 stlpcov prilezitostne, podla podmienok.
- je to jednorazovy proces, PHP skript posle vysledne pole (JSON) do html tabulky, tam uz filtrovanie funguje na klientovom PC.
V podstate z asi max. 40 tisic riadkov skript vygeneruje iba 200-300 riadkov, v ktorych uz mam vyfiltrovane (vypocitane) vysledky (jeden riadok = jedna osoba).
Osobna karta sa vygeneruje jednorazovo (ako string s HTML tagmi) a ta sa zobrazi v HTML tabulke (u klienta) v prvom stlpci ... Osobna karta je vlastne iba meno+priezvisko+adresa+datum narodenia apod., a potom nejake dynamicke udaje ako pohlavie, hodnotenie podla trzby, hodnotenie podla odpracovanych hodin, pozicia vo firme apod..
Tomáš Novotný:14.5.2019 23:00
Právě že jsem chtěl tímto naznačit, že ta podstata být stejná
nemusí, zvláště když používáš primárně 30% sloupců. V příkladu co
si dal používáš patrně většinu sloupců a to je dle mne docela zásadní
rozdíl hlavně v té paměti jak se v začátku ptáš. Rozdíl 100MB vs. 300MB
je dle mne zásadní.
Nikdo nepsal, že sem máš dát reálná data, ale hlavičku s pseuodo -
příkladovými daty určitě nikoho nezabije ve formátu, který tam skutečně
je.
No, takže za mně bych to řešil strčením do nějaké db třeba SQLite,
pokud nemáš k dispozici db server. Protože by za tebe vyřešila dost věcí,
viz primárně ty součty nad vybranou skupinou dat.
Píšeš, že je to jednorázově což teda musí být masakr. To totiž
znamená, že všechna data pro následné filtrování máš pak v
prohlížeči pro všech 250 lidí jak pro sumáře tak jejich detaily + osobní
údaje .
Pokud db fakt ne a ikdyž pro sumář, procházíš stejně celý soubor, jsem
pro to regulární výrazem přes ID vytáhnout řádky. Pak třeba explodem
přes ';' rozskat a vytáhnout si jen co potřebuješ.
Jinou možností, co mne napadá je využít parser, který je defaultně pro
csv v php a bude-li problém s pamětí tak vstupní soubor rozdělit na více
menších.
Jinak to 'jednorázově' se mi moc nepozdává, když píšeš, že se data
mění - přibývají každý den - tj. si vykládám, že je možné, že se na
ně každý den někdo podívá a provádí se znovu výpočty ze včera + se
přidají ty dnešní.
Jednorázovým jsem měl na mysli třeba import bankovních výpisů... ty
uděláš jednou a pak už ne.
Jak pisi kluci. Take bych primo z toho csv parseru tahal jen, co potrebuji. A k tomu tak ci tak je lepsi pouzit funkci, jakou pises. Jakoze ty mas nejspis kod:
$csv = file_get_contents($path);
$table = parsujCSV($csv); // function parsujCSV(&$csv);
// parsujCSV($csv, $table); // function parsujCSV(&$csv, &$table);
$table = filtruj($table, $kriteria);
To povazuji za spravny a prehledny postup. (podobne filtruji csv v js - uloz si to jako uplnou stranku, i s js kodem a pres prochazet si tam to sve csv otevri https://webapp.fpf.slu.cz/…as3-view.htm)
Ale kdybys to chtel rychlejsi, nebo mozna jen uspornejsi na pamet, tak by ten filtr musel byt v parseru. Minimalne usetris jeden cyklus.
function parsujCSV(&$csv);
{
$parser = new mujCsvParser();
$table = array();
while ($parser->end)
{
$row = $parser->next($csv)
$row = filtruj($row);
if ($row) $table[] = $row;
}
return $table;
}
A uplne idealni, kdybys to dal do parseru, pokud je to tvuj vlastni kod. Ale, to je asi vedlejsi.
- Co se tyce zpracovani 30.000 radku, to by melo probehnout v pohode. Kdyz mi stiha ten javascript asi 2000 radku, 30.000 by pro nej nebyl problem.
- Pokud to csv nahravas pres web-upload, tam se celkem vyplati pouzit zipovani pro velke soubory.
- Ty radky bych tam filtroval jako $new_row = array(); $new_row[] = $row[2]; $new_row[] = $row[5];
- Co jsem upravoval jedne firme parser i o zipovani, tak manik rikal, ze
predtim cela operace nasoukani csv do db trvala asi 30s, po uprave asi 2-3s. Tam
mel dost strasidelny a neusporny kod a taky nahraval 17MB csv, ktere se krasne
ziplo asi na 500k
- Jinak by slo to csv primo nasoukat do db a db to dokaze rychle filtrovat. Pokud by s tim php melo problem, mohlo by to byt tak o 30-50% rychlejsi.
Jo, a pokud mas pocit, ze je neco pomalejsi, a chces zjistit co, muzes si
zaznamenavat mirotime float a na konci to odecist a zjistit, ktera ta cast je
pomala. Ale to umi urcite i nejak php.
https://www.php.net/…icrotime.php (Example #1)
nebo...
$time = array();
$time[] = microtime();
$csv = file_get_contents($path);
$time[] = microtime();
$table = parsujCSV($csv); // function parsujCSV(&$csv);
$time[] = microtime();
$table = filtruj($table, $kriteria);
$time[] = microtime();
zpracujTimeAZobraz($time);
// nebo
$time[] = array('parsujCSV start', microtime());
$table = parsujCSV($csv); // function parsujCSV(&$csv);
$time[] = array('parsujCSV end', microtime());
Pani dakujem za hodnotne informacie.
Aktualny kod na precitanie CSV suboru mam takyto:
if (!empty($_FILES['csv_file']['name']))
{
$dataCSV = array();
if (($file_data = fopen($_FILES['csv_file']['tmp_name'], 'r')) === false)
{
throw new Exception('There was an error loading the CSV file.');
}
else
{
$headers=fgetcsv($file_data,600,";");
while (!feof($file_data)) {
$line = fgetcsv($file_data, 1000,";");
if (!empty($line)) $dataCSV[] = array_combine($headers, $line);
}
fclose($file_data);
}
}
Urcite to neni optimalny sposob...zbytocne vytvaram tak velke pole v ktorom
je vela nepotrebnych informacii.
Asi by sa mali ukladat len vyfiltrovane riadky hned pri nacitavani CSV suboru,
asi takto:
$line = fgetcsv($file_data, 1000,";");
if (!empty($line)) $dataCSV[] = filtruj($headers, $line);
Ale hned tu vidim problem, ako vyhladat duplicitny riadok v tom istom poly
Predat pole do funkcii
filtruj ... $dataCSV[] = filtruj($headers, $line, &$dataCSV) asi nebude
spravna volba.
$dataCSV[] = array_combine($headers, $line);
Ok, ale, pokud ta data z csv dal zpracovavas, tak bych tento radek zakomentoval
a pouzival ciselny index. A az nazaver, vystup, bych pridal textove indexy. Nebo
je tam vubec nedaval. Zalezi na tom, jaky by byl casovy rozdil zpracovani proti
ciselnym indexum. Ja bych videl rozdil tak mas 10-20%. Pri 3s bezvyznamne, pri
10s (cas celeho kodu) trochu problem.
Zobrazeno 18 zpráv z 18.