JavaScript Loading Performance
Posted: Thu May 12, 2005 11:15 am
Η απόδοση μίας εφαρμογής JavaScript σπάνια είναι ζήτημα προς εξέταση. Ο περιορισμός της γλώσσας μέσα σε μία web σελίδα και επομένως τα λιγοστά δεδομένα που είναι διαθέσιμα, κάνουν τα προγράμματα μικρά σε μέγεθος και χωρίς πολλούς βρόγχους. Στις περισσότερες περιπτώσεις η εκτέλεση λίγου κώδικα συμβαίνει μέσα από συμβάντα που προκαλεί ο χρήστης (onclick, onmouseover, κ.λπ). Έτσι η λέξη optimization συνδέεται με τη JavaScript μόνο σε σχέση με αυτό καθαυτό το μέγεθος του κώδικα σε bytes, δηλαδή με το χρόνο που απαιτείται για να κατέβει το script από τον server στον client.
Εδώ όμως θα με απασχολήσει κάτι άλλο. Αυτό που ενδιαφέρθηκα να μάθω είναι ο χρόνος που απαιτείται για το parsing ενός μεγάλου όγκου κώδικα από τον browser. Η mini-εφαρμογή που θέλησα να φτιάξω έχει την ιδιαιτερότητα ότι δεν προορίζεται για web σελίδα. Θα τρέχει ως κανονική Windows εφαρμογή στον browser του χρήστη, χωρίς την ανάγκη να είναι συνδεδεμένος στο Internet. Η εφαρμογή είναι μία απλή database με αρκτικόλεξα που δίνει στο χρήστη τη δυνατότητα να γράψει π.χ. τη λέξη PNG και να πάρει την απάντηση "Portable Network Graphics". Η εφαρμογή πρέπει να τρέχει σε όλους τους browsers, επομένως τα ActiveX είναι εκτός συζήτησης. Εάν λοιπόν δεν υπάρχει δυνατότητα για μία συμβατική SQL database, η αποθήκευση των αρκτικόλεξων πρέπει να γίνει εξ'ολοκλήρου με JavaScript. Όμως η γλώσσα αυτή δεν έχει φτιαχτεί για πολύ μεγάλο φόρτο, και επομένως έχει σημασία να βρεθεί η πιο αποδοτική από τις διαθέσιμες επιλογές. Στόχος μου ήταν να έχω μία database με 10.000 περίπου εγγραφές, και η σελίδα να φορτώνεται και να εμφανίζει τα αποτελέσματα σε λιγότερο από 200msec σε ένα μηχάνημα Celeron 2.4. Αυτός ο χρόνος αφορά το parsing του κώδικα, και όχι το κατέβασμά του από το Internet ή το χρόνο εκκίνησης του browser, ο οποίος πρέπει να είναι ήδη ανοιχτός. Παρακάτω περιγράφω τους τρεις τρόπους που δοκίμασα, και την απόδοση του καθενός από αυτούς σε τρeις browsers (Firefox, Opera, Internet Explorer).
1) Αντικείμενα JavaScript. Η πρώτη και πιο κομψή προσπάθεια. Τα προβλήματα απόδοσης που συνάντησα ήταν η αφορμή για αυτές τις δοκιμές. Ο κώδικας είναι ο εξής:
... και ιδού τα αποτελέσματα. Το πρώτο νούμερο είναι ο χρόνος φόρτωσης και το δεύτερο ο χρόνος αναζήτησης. Οι χρόνοι είναι σε msec:
Όλοι οι browsers έχουν κακή απόδοση, και μάλιστα ο IE εμφανίζει γεωμετρική αύξηση του χρόνου με την αύξηση των εγγραφών. Η αρχικές μου υποψίες δε στράφηκαν στην ίδια τη μέθοδο των αντικειμένων αλλά στις υπόλοιπες προγραμματιστικές δομές. Αρχικά έβγαλα το with() κατά τον ορισμό τον αρκτικόλεξων:
Καμία διαφορά στην απόδοση. Στη συνέχεια κατήργησα τη μέθοδο add του array acronym_list, και έβαλα το σχετικό κώδικα μέσα στη "database":
Αλλά κι αυτό δεν έφερε την παραμικρή βελτίωση. Μετά δοκίμασα να δώσω στο array μία μέθοδο load:
Αυτή η αλλαγή δεν άρεσε καθόλου στον Firefox, που εμφάνισε δραματική μείωση της απόδοσης:
Το συμπέρασμα που έβγαλα ήταν ότι έπρεπε να βρεθεί άλλος τρόπος να αποθηκευτούν τα δεδομένα. Το parsing και η δημιουργία τόσων αντικειμένων ήταν αθεράπευτα χρονοβόρο.
2) Array συμβολοσειρών. Κάθε αρκτικόλεξο αποθηκεύεται σε ένα string:
Το αρκτικόλεξο χωρίζεται από την περιγραφή του μέ ένα χαρακτήρα ";". Αυτό κάνει λίγο πιο περίπλοκη την αναζήτηση. Η μέθοδος compile του αντικειμένου RegExp υποτίθεται ότι κάνει τις regular expressions πιο γρήγορες. Τα αποτελέσματα:
Ο χρόνος φόρτωσης μειώθηκε αισθητά, αλλά το ίδιο αισθητά αυξήθηκε ο χρόνος αναζήτησης. Με λίγα λόγια δεν ήταν η λύση που αναζητούσα.
3) Όλα τα δεδομένα σε ένα μοναδικό string:
Ο κώδικας είναι ιδιαίτερα σύντομος. Άραγε αυτό τον κάνει και πιο γρήγορο?
Ναι! Πολύ ικανοποιητικοί χρόνοι, τέλος του προβλήματος. Τελικά η πιο άσχημη λύση ήταν η πιο γρήγορη. Το μόνο πρόβλημα είναι ότι σε αυτή τη μορφή τα δεδομένα δεν είναι βολικά για αλλαγές με το χέρι. Άραγε τι θα συνέβαινε αν έσπαγα το μονοκόμματο string σε κομμάτια?
Ο IE φαίνεται ότι έχει πολύ μεγάλο πρόβλημα στη συνένωση των τμημάτων. Παρακολούθησα την κατανάλωση RAM να φτάνει τις πολλές εκατοντάδες MB πριν διακόψω το πρόγραμμα με τον Task manager. Επομένως η απλή λύση του ενός string παραμένει η καλύτερη.
Εδώ όμως θα με απασχολήσει κάτι άλλο. Αυτό που ενδιαφέρθηκα να μάθω είναι ο χρόνος που απαιτείται για το parsing ενός μεγάλου όγκου κώδικα από τον browser. Η mini-εφαρμογή που θέλησα να φτιάξω έχει την ιδιαιτερότητα ότι δεν προορίζεται για web σελίδα. Θα τρέχει ως κανονική Windows εφαρμογή στον browser του χρήστη, χωρίς την ανάγκη να είναι συνδεδεμένος στο Internet. Η εφαρμογή είναι μία απλή database με αρκτικόλεξα που δίνει στο χρήστη τη δυνατότητα να γράψει π.χ. τη λέξη PNG και να πάρει την απάντηση "Portable Network Graphics". Η εφαρμογή πρέπει να τρέχει σε όλους τους browsers, επομένως τα ActiveX είναι εκτός συζήτησης. Εάν λοιπόν δεν υπάρχει δυνατότητα για μία συμβατική SQL database, η αποθήκευση των αρκτικόλεξων πρέπει να γίνει εξ'ολοκλήρου με JavaScript. Όμως η γλώσσα αυτή δεν έχει φτιαχτεί για πολύ μεγάλο φόρτο, και επομένως έχει σημασία να βρεθεί η πιο αποδοτική από τις διαθέσιμες επιλογές. Στόχος μου ήταν να έχω μία database με 10.000 περίπου εγγραφές, και η σελίδα να φορτώνεται και να εμφανίζει τα αποτελέσματα σε λιγότερο από 200msec σε ένα μηχάνημα Celeron 2.4. Αυτός ο χρόνος αφορά το parsing του κώδικα, και όχι το κατέβασμά του από το Internet ή το χρόνο εκκίνησης του browser, ο οποίος πρέπει να είναι ήδη ανοιχτός. Παρακάτω περιγράφω τους τρεις τρόπους που δοκίμασα, και την απόδοση του καθενός από αυτούς σε τρeις browsers (Firefox, Opera, Internet Explorer).
1) Αντικείμενα JavaScript. Η πρώτη και πιο κομψή προσπάθεια. Τα προβλήματα απόδοσης που συνάντησα ήταν η αφορμή για αυτές τις δοκιμές. Ο κώδικας είναι ο εξής:
Code: Select all
function Acronym(name, value) {
this.name = name
this.value = value
}
var acronym_list = []
acronym_list.add = function(name, value) {
this.push(new Acronym(name, value))
}
acronym_list.search = function(name) {
var re_text = new RegExp("^" + name + "$", "i")
for (var i = 0; i < this.length; i++) {
if (re_text.test(this[i].name)) return this[i]
}
}
with(acronym_list) {
add("PNG", "Portable Network Graphics")
add("SQL", "Structured Query Language")
// Συν πολλές ακόμα εγγραφές.
}
Code: Select all
Firefox 1.0 (5.000 εγγραφές) : 280, 40
Firefox 1.0 (10.000 εγγραφές) : 480, 130
Opera 8.0 (5.000 εγγραφές) : 300, 35
Opera 8.0 (10.000 εγγραφές) : 700, 70
IE 6.0 (5.000 εγγραφές) : 530, 35
IE 6.0 (10.000 εγγραφές) : 1.480, 70
Code: Select all
acronym_list.add("PNG", "Portable Network Graphics")
acronym_list.add("SQL", "Structured Query Language")
// Συν πολλές ακόμα εγγραφές.
Code: Select all
with(acronym_list) {
push(new Acronym("PNG", "Portable Network Graphics"))
push(new Acronym("SQL", "Structured Query Language"))
// Συν πολλές ακόμα εγγραφές.
}
Code: Select all
acronym_list.load = function() {
this.add("PNG", "Portable Network Graphics")
this.add("SQL", "Structured Query Language")
// Συν πολλές ακόμα εγγραφές.
}
Code: Select all
Firefox 1.0 (5.000 εγγραφές) : 1.300, 40
Firefox 1.0 (10.000 εγγραφές) : 4.500, 130
2) Array συμβολοσειρών. Κάθε αρκτικόλεξο αποθηκεύεται σε ένα string:
Code: Select all
var acronym_list = []
acronym_list.add = function(str) {
this.push(str)
}
acronym_list.search = function(name) {
var re_name = new RegExp().compile("^" + name + ";", "i")
var re_value = new RegExp(";(.*)$")
for (var i = 0; i < this.length; i++) {
if (re_name.test(this[i])) return this[i].match(re_value)[1]
}
}
with(acronym_list) {
add("PNG;Portable Network Graphics")
add("SQL;Structured Query Language")
// Συν πολλές ακόμα εγγραφές.
}
Code: Select all
Firefox 1.0 (5.000 εγγραφές) : 150, 150
Firefox 1.0 (10.000 εγγραφές) : 260, 400
Opera 8.0 (5.000 εγγραφές) : 300, 70
Opera 8.0 (10.000 εγγραφές) : 600, 140
IE 6.0 (5.000 εγγραφές) : 170, 30
IE 6.0 (10.000 εγγραφές) : 320, 60
3) Όλα τα δεδομένα σε ένα μοναδικό string:
Code: Select all
search = function(name) {
var re_entry = new RegExp("#" + name + ";(.*?)#", "i")
var match = acronym_list.match(re_entry)
if (match) return match[1]
}
var acronym_list = "#PNG;Portable Network Graphics#SQL;Structured Query Language#"
// Συν πολλές ακόμα εγγραφές.
Code: Select all
Firefox 1.0 (5.000 εγγραφές) : 90, 30
Firefox 1.0 (10.000 εγγραφές) : 150, 40
Opera 8.0 (5.000 εγγραφές) : 280, 70
Opera 8.0 (10.000 εγγραφές) : 290, 140
IE 6.0 (5.000 εγγραφές) : 90, 5
IE 6.0 (10.000 εγγραφές) : 120, 10
Code: Select all
var acronym_list = "#" +
"PNG;Portable Network Graphics#" +
"SQL;Structured Query Language#"
// Συν πολλές ακόμα εγγραφές.
Code: Select all
Firefox 1.0 (5.000 εγγραφές) : 140, 20
Firefox 1.0 (10.000 εγγραφές) : 210, 40
Opera 8.0 (5.000 εγγραφές) : 280, 70
Opera 8.0 (10.000 εγγραφές) : 290, 140
IE 6.0 (5.000 εγγραφές) : κόλλησε!
IE 6.0 (10.000 εγγραφές) : κόλλησε!