PDA

View Full Version : [js] jQuery.parseJSON


sottovento
05-03-2015, 16:53
La jQuery.parseJSON sembra avere uno strano comportamento: ho fatto un loop infinito all'interno del quale ho chiamato la jQuery.parseJSON per decodificare sempre gli stessi dati. Prendo il tempo ad ogni interazione.

Sorprendentemente ho notato che, nonostante i dati siano sempre gli stessi, il tempo di esecuzione di ogni ciclo diventa sempre piu' lungo, fino a diventare inaccettabile per la mia applicazione!!

Ad essere sincero, dubito che il problema sia la jQuery.parseJSON(): e' la prima volta che mi trovo ad usare queste cose, per di piu' su un progetto non mio.
Parto quindi dall'idea che sia io ad aver sbagliato qualcosa.

La domanda ovvia e': cosa? Cosa cerchereste, prima di tutto?

Nel frattempo, provo a copiare questo pezzo di codice in un piccolo file a parte, in modo da essere piu' che sicuro di quello che dico.

Qualsiasi suggerimento e' caldamente accettato!

wizard1993
05-03-2015, 18:12
su browser noti questo comportamento? noti per caso un aumento inconsueto del consumo di ram? Perchè la prima cosa che mi viene da pensare è un qualche memory leak in zone non raggiungibili dal gc che però portano ad una sempre più pressante invocazione dello stesso. Ad ogni modo, se puoi, posta la frazione di codice incriminata... così a sentimento è difficile dirti qualcosa

OoZic
06-03-2015, 01:22
senza codice non si puo' dire nulla

posta il codice su jsbin o jsfiddle
http://jsbin.com/?html,js,output
http://jsfiddle.net/

sottovento
06-03-2015, 09:40
Scusatemi, credo che stia andando a farfalle.
Non ho postato il codice perche' era parte di un progetto piuttosto grosso del quale non conosco quasi nulla. Purtroppo ho l'incarico di farlo funzionare correttamente, nonostante la mia ignoranza del progetto.

Avevo notato che il parsing della stringa JSON ricevuta dal server aveva bisogno di un tempo crescente, pur essendo la stringa sempre la stessa (l'ho fissata per avere un minimo di attendibilita' nei test).

Il modo con cui prendevo i tempi e' semplice (questo e' il codice del progetto):

function UpdateEMDData() {

$.ajax({
url: "ajax.aspx?type=LoadEMDData",
cache: false,
beforeSend: function (xhr) {
xhr.overrideMimeType("text/plain; charset=x-user-defined");
}
})
.done(function (data) {
var secStart = new Date().getTime();

var parsed = jQuery.parseJSON(data);

var parseTime = new Date().getTime();

UpdateTrackingImage(parsed.m_MeasData);
UpdateEdgeMaskingData(parsed.m_EMDLengthData);

var secEnd = new Date().getTime();
var duration = secEnd - secStart;
var durationParse = secEnd - parseTime;
var durationUpdate = duration - durationParse;
var length1 = parsed.m_EMDLengthData[0].m_StripLength.length;
var length2 = parsed.m_EMDLengthData[0].m_StripLength.length;
console.log("UpdateEMDData() [CALLBACK] executed in " + duration + "[ms] Parse=" + durationParse + "[ms] Update=" + durationUpdate + "[ms] L1=" + length1 + ", L2=" + length2 + ", data length=" + data.length);
// Print the data to be parsed
//console.log(data);
});
}

Ovviamente parseTime - secStart e' il tempo che mi interessa, e che ho visto incrementarsi fino ad oltre 2 secondi (poi ho chiuso il progetto perche' il browser non rispondeva praticamente piu'). Da notare che non c'e' altro fra la rilevazione di quei due tempi, solo il parsing della stringa (che e' di 85Kb, circa).

Come accennavo nel messaggio precedente, ho provato a copiare questo codice in un programma a parte, quindi di solo poche righe. Ho creato un timer che richiama il parsing ogni secondo (cosa richiesta anche dal programma "vero").

Il risultato che ottengo e' corretto: i tempi sono molto contenuti e non aumentano. Sto quindi andando a farfalle. Pero' non riesco ad ipotizzare una spiegazione su questi comportamenti cosi' diversi.

Questo e' il programma estrapolato, che fa le stesse cose ma ha tempi che sono una piccola frazione di quello del programma di partenza, nonostante la stringa da valutare sia la stessa (e soprattutto non sembrano aumentare):


function doDirtyJob() {
var data='<qui ci sono 85K caratteri!>';

var secStart = new Date().getTime();
var parsed = jQuery.parseJSON(data);
var secEnd = new Date().getTime();

var duration = secEnd - secStart;
console.log("ParseTime: " + duration);
}

function startTimer() {
var refreshId = setInterval(function () {
doDirtyJob();
}, 1000);
}


Ho provato il codice con Chrome, Firefox ed IE. IE e' quello che offre le prestazioni peggiori e dopo qualche minuto risulta buono da essere killato. I tempi di esecuzione con IE sono nettamente superiori agli altri due.

Grazie per le risposte precedenti. Se avete una idea, un sospetto... fatemelo sapere e provo a verificarlo.

A PROPOSITO: per motivi di leggibilita' del codice non ho postato la stringa lunga 85K caratteri. Posso farlo, se vi sembra importante

Tuvok-LuR-
06-03-2015, 10:38
beh se il parsing prende piu tempo dell'interval (1 secondo nel tuo caso) è ovvio che i cicli comincino a sovrapporsi e alla fine far crashare.
Prova anche col JSON.parse() nativo (anche se jQuery dovrebbe delegare a quello comunque dove supportato).
Inoltre puoi usare i chrome devtools per vedere cosa sta succedendo in memoria (fai registra sul tab timeline ed usa i vari strumenti nel tab profiles).

sottovento
06-03-2015, 12:22
Scusa il ritardo

beh se il parsing prende piu tempo dell'interval (1 secondo nel tuo caso) è ovvio che i cicli comincino a sovrapporsi e alla fine far crashare.

si, d'accordissimo. Quello che non mi spiego e' perche' l'applicazione di test impiega 2[ms] mentre quella ufficiale passa i 2 secondi.
Sto facendo un nuovo test, mettendo anche nell'applicazione ufficiale la stringa fissa, ed ignorando quella che arriva dal server, per vedere se cambia qualcosa (anche se pure quella del server era fissa)



Prova anche col JSON.parse() nativo (anche se jQuery dovrebbe delegare a quello comunque dove supportato).

Provero'.
In teoria non dovrebbe cambiare nulla, ma visto i risultati sconclusionati che sto ottenendo, non mi sento di fare nessuna previsione ;)


Inoltre puoi usare i chrome devtools per vedere cosa sta succedendo in memoria (fai registra sul tab timeline ed usa i vari strumenti nel tab profiles).
Provero' anche questo, nella speranza - se non di risolvere il problema - almeno di farlo diventare predittivo, riproducibile e magari comprensibile

Grazie

sottovento
06-03-2015, 15:35
beh se il parsing prende piu tempo dell'interval (1 secondo nel tuo caso) è ovvio che i cicli comincino a sovrapporsi e alla fine far crashare.
Prova anche col JSON.parse() nativo (anche se jQuery dovrebbe delegare a quello comunque dove supportato).
Inoltre puoi usare i chrome devtools per vedere cosa sta succedendo in memoria (fai registra sul tab timeline ed usa i vari strumenti nel tab profiles).

Ok, ho fatto e rifatto i test, ed oltretutto credo di averli fatti correttamente.
- JSON.parse() - purtroppo non cambia nulla, il comportamento e' assolutamente sovrapponibile. Sono quindi andato a controllare cosa faccia la mia versione di jquery.parseJSON e chiama appunto JSON.parse() e nient'altro. Peccato

- Ho provato a far girare un po' i tool di profiling di Chrome. Sono perplesso per quanto riguarda i risultati ottenuti! Per prendere confidenza con i tool, li ho prima fatti girare sul programmino di test che ho postato sopra.
Il profiling ha praticamente registrato le varie creazioni/chiamate del timer e nient'altro.
La cosa piu' interessante era la memoria (dinamica) che aveva un andamento a dente di sega, cosi' come ci si puo' aspettare da un sistema dotato di garbage collector. Tutto a posto.

- Ho poi fatto girare lo stesso tool sull'applicazione "vera", ed il risultato mi ha lasciato perplesso: c'e' un dente di sega iniziale (con un tempo di salita piuttosto lungo, diversi minuti), poi la memoria viene rilasciata.
A questo punto non parte un altro dente di sega, ma il grafico della memoria si sussegue con alti e bassi, che sembrano (sembrano, non posso esserne sicuro) allargarsi.
Questo risultato non mi fa escludere memory leak, ma non me li conferma nemmeno :(
Il profiling dice che il browser e' stato impegnato ad eseguire javascript per oltre il 40% del suo tempo.

Bel casino. Sono a corto di idee. A questo punto, piu' che una soluzione del problema sto cercando una serie di test per capire cosa stia succedendo.

A proposito di questo: nel programma "vero", ho provato a sostituire la stringa ricevuta dal server con una fissa (contenente lo stesso valore). Il risultato non cambia.

Il programma di esempio, invece, continua a funzionare come un orologio.

Cos'altro potrei provare per cercare di aggredire il sistema?

wingman87
06-03-2015, 15:57
function UpdateEMDData() {

$.ajax({
url: "ajax.aspx?type=LoadEMDData",
cache: false,
beforeSend: function (xhr) {
xhr.overrideMimeType("text/plain; charset=x-user-defined");
}
})
.done(function (data) {
var secStart = new Date().getTime();

var parsed = jQuery.parseJSON(data);

var parseTime = new Date().getTime();

UpdateTrackingImage(parsed.m_MeasData);
UpdateEdgeMaskingData(parsed.m_EMDLengthData);

var secEnd = new Date().getTime();
var duration = secEnd - secStart;
var durationParse = secEnd - parseTime; //Qui dovrebbe essere parseTime - secStart, il calcolo che hai fatto è per la durata dell'update
var durationUpdate = duration - durationParse;
var length1 = parsed.m_EMDLengthData[0].m_StripLength.length;
var length2 = parsed.m_EMDLengthData[0].m_StripLength.length;
console.log("UpdateEMDData() [CALLBACK] executed in " + duration + "[ms] Parse=" + durationParse + "[ms] Update=" + durationUpdate + "[ms] L1=" + length1 + ", L2=" + length2 + ", data length=" + data.length);
// Print the data to be parsed
//console.log(data);
});
}

Rileggendo il codice mi sono accorto che hai calcolato male durationParse e durationUpdate. Di fatto i significati delle due variabili sono scambiati rispetto al loro nome. Questo vorrebbe dire che stai cercando il problema nel parse mentre in realtà il problema è nell'update.

sottovento
06-03-2015, 16:04
Rileggendo il codice mi sono accorto che hai calcolato male durationParse e durationUpdate. Di fatto i significati delle due variabili sono scambiati rispetto al loro nome. Questo vorrebbe dire che stai cercando il problema nel parse mentre in realtà il problema è nell'update.

E' con la penna intinta nel mio stesso sangue che scrivo questa mail. Stavo per scrivere appunto quanto sono un poveretto (me ne sono accorto or ora) ma mi hai preceduto.
Ovviamente ti ringrazio tantissimo e continuo a sbattere la testa contro il paletto di frassino qui vicino per altri 10 minuti, poi rifaccio i test e vedo se riesco ad essere meno distratto.

Beh, quantomeno il problema non e' risolto ma ha un senso...

wingman87
06-03-2015, 16:23
ahahahah, questo genere di errori è proprio infido (è un po' come scrivere un test sbagliato) ma capita a tutti :D

OoZic
07-03-2015, 12:04
qualche considerazione:

il parse del JSON lato client devi farlo per forza? non puoi farti restituire gia' dal server un JSON? (e' quello che dovrebbe avvenire per fare le cose fatte bene)

non si vede dove chiami le funzioni e in generale credo tu stia utilizzando un approccio sbagliato, ad esempio la chiamata AJAX ha un tempo di latenza, se il server ti risponde dopo 100ms e' un caso ma cosa succede se ti risponde dopo 1,5secondi? Nel frattempo tu hai fatto partire un altra chiamata AJAX (lo fai ogni secondo) senza controllare se quella precedente e' andata a buon fine e quindi sovrapponendole, arriverai ad un certo punto che hai in "coda" dati da processare e per questo le performance degradano.

un modo di sistemare questa cosa e' di mettere in coda la seconda chiamata un secondo dopo che quella precedente e' stata completata, non sistematicamente ogni secondo.

usa un setTimeout sulla promise resolve (.done() ) e fai partire la chiamata successiva.

se poi vogliamo fare un grosso passo avanti, dovresti mandare solo i dati che cambiano e non sempre gli 85kb di tutti i dati, oppure se proprio vogliamo fare qualcosa di moderno ed efficace puoi usare websockets per mantenere attiva la connessione tra client e server e farsi mandare dal server gli aggiornamenti senza doverli "chiedere" di continuo.

sottovento
08-03-2015, 20:56
Carissimo

qualche considerazione:

il parse del JSON lato client devi farlo per forza? non puoi farti restituire gia' dal server un JSON? (e' quello che dovrebbe avvenire per fare le cose fatte bene)

Hai ragione. Controllo quello che posso fare: sto cercando di far funzionare un progetto non mio che attualmente funzionicchia. Metto questa cosa nella mia to-do list e vedo quello che riesco a fare dopo che il progetto e' diventato piu' affidabile (sempre se ce la faccio).


non si vede dove chiami le funzioni e in generale credo tu stia utilizzando un approccio sbagliato, ad esempio la chiamata AJAX ha un tempo di latenza, se il server ti risponde dopo 100ms e' un caso ma cosa succede se ti risponde dopo 1,5secondi? Nel frattempo tu hai fatto partire un altra chiamata AJAX (lo fai ogni secondo) senza controllare se quella precedente e' andata a buon fine e quindi sovrapponendole, arriverai ad un certo punto che hai in "coda" dati da processare e per questo le performance degradano.

un modo di sistemare questa cosa e' di mettere in coda la seconda chiamata un secondo dopo che quella precedente e' stata completata, non sistematicamente ogni secondo.

usa un setTimeout sulla promise resolve (.done() ) e fai partire la chiamata successiva.

Concordo al 100%. Fortunatamente nel 90% dei casi, client e server saranno sulla stessa macchina; tuttavia la modifica da te suggerita e' da implementare in ogni caso, altrimenti il progetto sara' sempre claudicante.


se poi vogliamo fare un grosso passo avanti, dovresti mandare solo i dati che cambiano e non sempre gli 85kb di tutti i dati, oppure se proprio vogliamo fare qualcosa di moderno ed efficace puoi usare websockets per mantenere attiva la connessione tra client e server e farsi mandare dal server gli aggiornamenti senza doverli "chiedere" di continuo.
Anche questo e' una cosa sicuramente da fare. Comincero' dallo spedire solo i dati che variano; per le web socket sara' decisamente piu' complicato.

A proposito: sai se sono implementate in IIS? Se si, a partire da quale versione?

Grazie a tutti! Immagino che la strada sia ancora in salita, ma intanto il progetto ha cominciato ad avere un comportamento piu' deterministico, e non e' cosa da poco

OoZic
09-03-2015, 00:34
Sembra dalla versione 8:
http://schmod.ruhoh.com/windows/socket-io-and-iis/

Oltre a questo ti direi di fare attenzione :)
http://en.wikipedia.org/wiki/Technical_debt

Tuvok-LuR-
09-03-2015, 08:47
il parse del JSON lato client devi farlo per forza? non puoi farti restituire gia' dal server un JSON? (e' quello che dovrebbe avvenire per fare le cose fatte bene)
il parse del json il client lo farà sempre e comunque (trasformare una stringa in un oggetto js), certo se usi dataType: "json" non lo devi fare a mano ma lo farà jQuery dietro le quinte con le stesse funzioni

sottovento
09-03-2015, 09:44
il parse del json il client lo farà sempre e comunque (trasformare una stringa in un oggetto js), certo se usi dataType: "json" non lo devi fare a mano ma lo farà jQuery dietro le quinte con le stesse funzioni
Ho verificato ed in effetti e' cosi'. Ma tutto sommato si puo' sopravvivere, ci impiega dai 3 ai 4 millisecondi.

Il problema e' il resto dell'applicazione: prende i dati e li plotta usando jqPlot. Si tratta di un semplice grafico cartesiano.

I tempi pero' continuano ad aumentare senza motivo ragionevole. In pratica si tratta solo di un paio di operazioni:


var series = [CentreLineDeviationOverLength, LHSPlateDataOverStriplength, RHSPlateDataOverStriplength, RHSEdgePoseDataOverStriplength, LHSEdgePoseDataOverStriplength];
plotEMD1CenterLineDeviation.replot({data:series});

Non capisco perche' queste due linee di codice debbano essere eseguite a tempo incrementale... posso capire che sia necessario impiegare un garbage collector, ma i tempi dovrebbero crescere fino ad un massimo e poi piu', no? In realta' i tempi sembrano crescere sempre piu'.

Cmq queste linee sono un mio test. La versione originale era anche peggio:

plotEMD1CenterLineDeviation.destroy();
plotEMD1CenterLineDeviation = $.jqplot('plotEMD1CenterLineDeviation', [CentreLineDeviationOverLength, LHSPlateDataOverStriplength, RHSPlateDataOverStriplength, RHSEdgePoseDataOverStriplength, LHSEdgePoseDataOverStriplength],
returnPlotOptions("Strip length [m]", "CentreLineDeviation [IU]", "Centre line deviation", "LHS Plate", "RHS Plate", "RHS Edge Position", "LHS Edge Position", "", "", "", "EMDStyle"));

con queste linee i tempi di esecuzione erano anche piu' grandi e l'applicazione rallentava molto di piu' ad ogni refresh.

Sto aspettando ad implementare i suggerimenti di OoZic perche' per me e' importante capire questo comportamento, prima di tutto. Apparentemente e' come se i punti nuovi non rimpiazzassero quelli vecchi ma fossero appesi alla sequenza, che diventando sempre piu' lunga rallenta l'esecuzione...

sottovento
09-03-2015, 12:18
Ho qualche informazione in piu', magari a qualcuno puo' venire in mente un'idea per domare il progetto: ho copiato in un piccolo file esterno non solo il parsing (l'avevo gia' fatto) ma anche il plot dei dati mediante jqplot.

Il risultato mi ha lasciato l'amaro in bocca: funziona, senza aumentare il tempo necessario per l'esecuzione. Praticamente e' lo stesso codice e mi sarei aspettato lo stesso risultato.

Evidentemente ho sbagliato ancora qualcosa, visto che non credo ai fantasmi. Nel progetto, ricevo i dati, li decodifico e li plotto. L'operazione di disegno incrementa i tempi di esecuzione ogni volta (piu' o meno).

Il progetto di test fa le stesse cose, essendo solo copy+paste dal progetto javascript, eppure i tempi non si incrementano. I dati non vengono piu' ricevuti dal server (ho commentato quella parte) ma si tratta di una stringa fissata nel codice, in entrambi i casi.

E' chiaro che non ve la sto raccontanto giusta, ci deve essere qualcos'altro. Quindi, prima di risolvere il problema, penso sia piu' importante capire cos'altro possa aver dimenticato...

ndakota
09-03-2015, 12:51
Ma quello che succede lato-server in quella chiamata ajax? Immagino nel progettino di esempio tu non l'abbia. Non è che c'è un orm, che magari ha a che fare con un campo lob e questo in qualche modo fa peggiorare le prestazioni? C'è la grandissima possibilità che io non abbia capito niente :stordita:

sottovento
09-03-2015, 13:06
Ma quello che succede lato-server in quella chiamata ajax? Immagino nel progettino di esempio tu non l'abbia. Non è che c'è un orm, che magari ha a che fare con un campo lob e questo in qualche modo fa peggiorare le prestazioni? C'è la grandissima possibilità che io non abbia capito niente :stordita:
No dai, non dire cosi'! Qualsiasi cosa mi e' utile, quindi non dire che non hai capito niente.

Nel progettino di test ho solo un file html + un javascript (che include jquery e jqplot). Prelevo i dati da una stringa messa a fuoco nel codice, li decodifico e li plotto.

Tutto funziona come dovrebbe. Il problema e' che, a furia di semplificare per cercare il problema, il codice di esempio e' praticamente un copy + paste del codice originale, il quale pero' vede i tempi di esecuzione dilatarsi, ciclo dopo ciclo.
Ovviamente la cosa mi turba parecchio. Non posso essere evidentemente nelle stesse condizioni, deve esserci qualcosa che non ho preso in considerazione.

Parlandoti, mi e' venuta in mente una nuova prova che posso fare: provo ad escludere altre parti del progetto e continuo ad escluderle fino a quando il progetto originale non si comporta come il progettino di test. Dovra' capitare prima o poi, no?

Grazie per l'aiuto

sottovento
12-03-2015, 09:41
Un doveroso aggiornamento per tutti quelli che mi hanno gentilmente aiutato: il comportamento strano era dovuto ad una libreria, trovata su internet, che plottava i dati nel browser.
Non so di preciso perche' avesse questo comportamento: dopo aver passato tanto tempo a cercare di capire, ho preferito rinunciare (il problema stava diventando troppo costoso) e rimpiazzare la libreria di charting con un'altra che funziona meglio, sempre gratuita e trovata su internet: flot (http://www.flotcharts.org/). Il problema e' ora sparito

Grazie a tutti!