Comparatia/parcurgerea unor array-uri foarte mari

Salut,

V-ati lovit de problema compararii/parcurgerii unor array-uri foarte mari (ex peste 10 000 de randuri) ?

Am doua array-uri (primul are 36 coloane si aprox 1.000 de randuri, 1.5 MB, cel de-al doilea are 10 coloane si aprox 13.000 de randuri, 5.5 MB).

Trebuie sa introduc datele din array-ul 2 in array-ul 1, dupa un camp field_id, in arrayul 1 acest camp este unic, insa in array 2 nu este unic.

foreach($array1 as $key_a => $a)
foreach($array2 as $key_b => $b)
if($a[‘field_id’] == $b[‘field_id’])
{
unset($b[‘field_id’]);
$array1[$key_a][‘foo’] = $b;
unset($array2[$key_b]);
}

Nu imi ofera performata dorita. Rezultatul dureaza aprox 10-15 secunde pe server local, ceea ce este enorm.

In cazul in care v-ati lovit de asa ceva, cum ati rezolvat situatiile similare?

De unde iei datele pentru aceste două arrays?

Le iau din MySQL, însă interogările și construcția celor două arrayuri nu durează mai mult de 1,5 secunde.

Păi și nu poți face o interogare mai deșteptă care să facă această potrivire fără intervenția PHP?

1 Like

Pai e vb de o relationare 1-n, nu 1-1. Si daca as face totul intr-un singur query, ar exista repetitii la campul field_id, si tot as avea nevoie de PHP pentru ca array-ul rezultat din query sa aiba forma dorita.

Momentan e asa:

Array 1:

field_id
111
112
113
114

Array 2:

field_id, foo
111, aaa
111, bbb
111, ccc
112, aab
112, aac
113, bbb
114, vvv
114, qqq
114, qqw

Eu am nevoie de o structura arborescenta de forma http://snippi.com/s/88aw82b

Cred ca indiferent daca as folosi un singur query pentru un singur array, sau doua query-uri pentru doua array-uri, datorita faptului ca exista relationarea 1-n, in final tot as fi nevoit sa parcurg chiar si unicul array rezultat pentru a obtine structura dorita.

Dar daca cumva ma insel sau n-am inteles eu bine, sunt deschis la sugestii alternative.

Dacă ai reuși să le iei cu un singur query, vei avea de făcut un loop, nu două.

Dar ai nevoie de acest array dintr-o bucată, într-o pagină? Sau trebuie doar să-l procesezi la un anumit interval? Întreb pentru că dacă ai nevoie de 10k rânduri într-o pagină probabil faci ceva greșit, iar dacă trebuie doar procesat, îl poți pune într-un cron executat independent de pagina propriu-zisă (caz în care nu contează prea mult dacă durează un minut execuția)

1 Like

Daca poti sa detaliezi exact problema fara nici o influenta tehnica (relationare, array, etc) pentru ca asa cum spune Ionut este posibil ca solutia pe care mergi sa nu fie optima.

Pentru a se intelege de ce e nevoie de atatea date, este vorba de raspunsul dat de server catre client. In cazul de fata, clientul Android functioneaza putin mai inedit: pentru a putea avea o oarecare functionalitate offline, in momentul cand se initializeaza aplicatia, aceasta cere si salveaza local toate datele, pentru ca pe viitor sa poata functiona si fara acces la net.

Exista vreo metoda mai buna decat clasicul foreach, din perspectiva performantei? Folosisem in trecut array_filter insa fara a observa o diferenta semnificativa (ba din contra, mi s-a parut in anumite situatii chiar mai incet decat foreach).

Nu poți prepara acele date astfel încât să fie în cache?

Dar poate că o altă abordare ar fi o soluție mai bună:

  1. http://stackoverflow.com/questions/5035132/how-to-sync-iphone-core-data-with-web-server-and-then-push-to-other-devices

Astfel încât poți transfera datele în bucăți mai mici (mai degrabă pierzi un pachet de 10kb și încerci iar decât să pierzi un pachet de 5mb) și, mai important, poți afișa datele deja transferate.


Să-ți dau un exemplu concret: eu folosesc YNAB, o aplicație care se sincronizează prin dropbox cu toate platformele (windows/osx/android/ios). Sunt sute de fișiere micuțe (1-2kb) ce conțin câte una-două tranzacții stocate în fișiere json. Nu știu exact ce algoritm folosește, dar sigur nu transferă toate datele la fiecare start al aplicației (bugetul actual are vreo 30mb; s-ar simți serios la planul de date fiecare deschidere a aplicației :smiley: )

Dacă ai timp/chef, ai putea face o decompilare a aplicației desktop (este făcută în Adobe Air) să vezi cum tratează toate datele respective

Este in plan modificarea serverului sa raspunda la HTTP header-ul “If-Modified-Since”, insa asta va fi abia pe viitor, ceea ce ar reduce semnificativ datele transferate. Insa s-ar putea sa folosim si solutiile oferite de tine (impartirea raspunsului in pachete mai mici).

Momentan pe termen scurt e necesar sa optimizez acest apel, si ceea ce ingreuneaza raspunsul este foreach-ul de care ma lovesc.

Am uitat să întreb: toată povestea asta se întâmplă de fiecare dată când pornește aplicația? Sau doar la prima pornire?

Pentru că dacă este doar prima dată, 1-2 minute de așteptare este un timp rezonabil (zic eu).

De asemenea, nu există nici o șansă să faci caching la array?

Se intampla la fiecare pornire a aplicatiei, ceea ce nu este bine deloc.

Cachingul nu prea ar fi o solutie (ce se intampla cu datele editate intre cache-uri, se poate ajunge la problema dublei adaugari de date, etc), insa impartirea pe pachete si folosirea header-ului If-modified-since ar imbunatati performanta.

Pana cand se va modifica tot sistemul de sincronizare atat server-side cat si client-side, e nevoie sa fac o optimizare in ceea ce priveste array-ul.

Intotdeauna sa te feresti de doua foreach-uri.
Ultima data cand am avut doua foreach-uri am reformatat $array2 incat sa-l pot accesa pe cheie, de forma $array2[$b['field_id']]

“Sparge” unul din foreach-uri, o sa mearga mult mai rapid.

1 Like

Cred ca ai nevoie de un model pentru stocarea de arbore in DB intr-o singura tabela.
Exemple:

Update
Ar mai fi o varianta
SELECT GROUP_CONCAT() … JOIN … GROUP BY field_id
in asa fel incat sa primesti valorile din array-ul secundar concatenate intr-un singur string din care faci un array din PHP.

1 Like

În primul rând, nu e deloc o idee bună ceea ce faci tu cu sincronizatul a 'nșpe mii de rânduri la fiecare pornire a aplicației.

În al doilea rând, nu mai folosi MySQLi::fetch_all() să iei toate rezultatele într-un array (în cazul în care o faci).

În al treilea rând, ar fi mult mai rapid dac-ai lua toate datele dintr-un singur query, cu LEFT JOIN.

În ultimul rând, următorul exemplu mi-a construit rezultatul cu structura pe care o dorești tu în 0.19864797592163 secunde față de aproximativ 90 de secunde cât îmi lua să combin același set de date folosind două array-uri distincte și două foreach-uri. :smile:

Aici sunt datele de test folosite (generate random): http://1drv.ms/1BOnfCe


<?php

$mysqli = new mysqli('localhost', 'root', 'root', 'test');

$start = microtime(true);

$data = array();

$query = 'SELECT t1.field_id, t2.foo FROM t1 LEFT JOIN t2 ON t1.field_id = t2.field_id';

if ($result = $mysqli->query($query)) {
    while ($row = $result->fetch_assoc()) {
        if (empty($data[$row['field_id']])) {
            $data[$row['field_id']] = array(
                'field_id' => $row['field_id'],
                'foos' => array()
            );
        }
        
        $data[$row['field_id']]['foos'][] = array('foo' => $row['foo']);
    }
    
    $result->free();
}

$stop = microtime(true);

$time = $stop - $start;

echo "<pre>$time\n";
print_r($data);

?>
2 Likes

Ca si idee pe viitor, cand te tine in loc PHP-ul, faci ceva foarte gresit si trebuie sa regandesti solutia cu totul. Also, cand sunt seturi mari de date, array-urile nu sunt o solutie.

2 Likes