JavaScript Loading Performance

Συζητήσεις για γλώσσες προγραμματισμού και θέματα σχετικά με προγραμματισμό.
Post Reply
User avatar
Skeftomilos
bit level
bit level
Posts: 43
Joined: Fri Mar 04, 2005 8:08 am
Location: Ν.Κόσμος

JavaScript Loading Performance

Post by Skeftomilos » 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. Η πρώτη και πιο κομψή προσπάθεια. Τα προβλήματα απόδοσης που συνάντησα ήταν η αφορμή για αυτές τις δοκιμές. Ο κώδικας είναι ο εξής:

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")
  // Συν πολλές ακόμα εγγραφές.
}
... και ιδού τα αποτελέσματα. Το πρώτο νούμερο είναι ο χρόνος φόρτωσης και το δεύτερο ο χρόνος αναζήτησης. Οι χρόνοι είναι σε msec:

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
Όλοι οι browsers έχουν κακή απόδοση, και μάλιστα ο IE εμφανίζει γεωμετρική αύξηση του χρόνου με την αύξηση των εγγραφών. Η αρχικές μου υποψίες δε στράφηκαν στην ίδια τη μέθοδο των αντικειμένων αλλά στις υπόλοιπες προγραμματιστικές δομές. Αρχικά έβγαλα το with() κατά τον ορισμό τον αρκτικόλεξων:

Code: Select all

acronym_list.add("PNG", "Portable Network Graphics")
acronym_list.add("SQL", "Structured Query Language")
// Συν πολλές ακόμα εγγραφές.
Καμία διαφορά στην απόδοση. Στη συνέχεια κατήργησα τη μέθοδο add του array acronym_list, και έβαλα το σχετικό κώδικα μέσα στη "database":

Code: Select all

with(acronym_list) {
  push(new Acronym("PNG", "Portable Network Graphics"))
  push(new Acronym("SQL", "Structured Query Language"))
  // Συν πολλές ακόμα εγγραφές.
}
Αλλά κι αυτό δεν έφερε την παραμικρή βελτίωση. Μετά δοκίμασα να δώσω στο array μία μέθοδο load:

Code: Select all

acronym_list.load = function() {
  this.add("PNG", "Portable Network Graphics")
  this.add("SQL", "Structured Query Language")
  // Συν πολλές ακόμα εγγραφές.
}
Αυτή η αλλαγή δεν άρεσε καθόλου στον Firefox, που εμφάνισε δραματική μείωση της απόδοσης:

Code: Select all

Firefox 1.0 (5.000 εγγραφές)  : 1.300, 40
Firefox 1.0 (10.000 εγγραφές) : 4.500, 130
Το συμπέρασμα που έβγαλα ήταν ότι έπρεπε να βρεθεί άλλος τρόπος να αποθηκευτούν τα δεδομένα. Το parsing και η δημιουργία τόσων αντικειμένων ήταν αθεράπευτα χρονοβόρο.

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")
  // Συν πολλές ακόμα εγγραφές.
}
Το αρκτικόλεξο χωρίζεται από την περιγραφή του μέ ένα χαρακτήρα ";". Αυτό κάνει λίγο πιο περίπλοκη την αναζήτηση. Η μέθοδος compile του αντικειμένου RegExp υποτίθεται ότι κάνει τις regular expressions πιο γρήγορες. Τα αποτελέσματα:

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
Ναι! Πολύ ικανοποιητικοί χρόνοι, τέλος του προβλήματος. Τελικά η πιο άσχημη λύση ήταν η πιο γρήγορη. Το μόνο πρόβλημα είναι ότι σε αυτή τη μορφή τα δεδομένα δεν είναι βολικά για αλλαγές με το χέρι. Άραγε τι θα συνέβαινε αν έσπαγα το μονοκόμματο string σε κομμάτια?

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 εγγραφές)      : κόλλησε!
Ο IE φαίνεται ότι έχει πολύ μεγάλο πρόβλημα στη συνένωση των τμημάτων. Παρακολούθησα την κατανάλωση RAM να φτάνει τις πολλές εκατοντάδες MB πριν διακόψω το πρόγραμμα με τον Task manager. Επομένως η απλή λύση του ενός string παραμένει η καλύτερη.
Last edited by Skeftomilos on Fri May 13, 2005 7:56 am, edited 1 time in total.
The pure and simple truth is rarely pure and never simple. Ο μη νους δε σκέπτεται μη σκέψεις για το τίποτα.
User avatar
HdkiLLeR
Venus Project Founder
Venus Project Founder
Posts: 4356
Joined: Tue Jan 27, 2004 4:41 pm
Academic status: Alumnus/a
Gender:
Location: New York, NY
Contact:

Post by HdkiLLeR » Thu May 12, 2005 8:46 pm

Πολύ ωραία και ενδιαφέρουσα η ανάλυση σου, αλλά βασικά λίγο χλωμό να γράψει κάποιος heavy load κώδικα σε js ώστε να τρέχει σε browsers. Άντε να χειριστούμε καλύτερα μερικά events και κανένα effect.
-----BEGIN GEEK CODE BLOCK-----
Version: 3.12
GCS d-->--- s+:+ a- C++(+++) BILS++++$ P--- L++++>+++++ E--- W+++ N+ o+ K w--
O M+ V-- PS++>+++ PE- Y++ PGP++ t+ 5+ X+ R* tv b++ DI- D+ G+++ e+++>++++ h r++ y++
------END GEEK CODE BLOCK------

"UNIX is basically a simple operating system, but you have to be a genius to understand the simplicity." -- Dennis Ritchie
User avatar
Skeftomilos
bit level
bit level
Posts: 43
Joined: Fri Mar 04, 2005 8:08 am
Location: Ν.Κόσμος

Post by Skeftomilos » Fri May 13, 2005 7:58 am

Σύμφωνοι, η JS έχει σοβαρούς εγγενείς περιορισμούς, αλλά και κάποια πλεονεκτήματα που δε μπορούν να αγνοηθούν. Κατ' αρχήν το deployment είναι πολύ απλή υπόθεση καθώς δε χρειάζεται setup. Αρκεί ο χρήστης να κατεβάσει το .htm αρχείο και να το αποθηκεύσει στο δίσκο του. Μετά είναι το θέμα της ασφάλειας. Είναι πολύ ευκολότερο να πειστεί ένας άγνωστος να τρέξει ένα .htm αρχείο από ένα .exe. Η JS είναι απολύτως ακατάλληλη για δημιουργία spyware. Επιπλέον το πρόγραμμα αποτελείται από πηγαίο κώδικα και όχι binary. Αν στο χρήστη δεν αρέσει η γραμματοσειρά, μπορεί μόνος του να αλλάξει το font-family χωρίς να μας ενοχλήσει.

Θα προσέθετα ότι ο συνδυασμός HTML και CSS είναι ιδιαίτερα βολικός τρόπος δημιουργίας του user-interface, σε σχέση με τους visual designers οποιουδήποτε εργαλείου ανάπτυξης. Μπορώ μέσα σε λίγα λεπτά να φτιάξω στο Notepad ένα fluid layout με τα στοιχεία ελέγχου να αλλάζουν μέγεθος ανάλογα με το μέγεθος του παραθύρου. Θα χρειαστώ όμως ένα απόγευμα για να κάνω το ίδιο σε VB.NET, και το αποτέλεσμα θα είναι ελάχιστα ευέλικτο αν μετά θελήσω να κάνω αλλαγές.
The pure and simple truth is rarely pure and never simple. Ο μη νους δε σκέπτεται μη σκέψεις για το τίποτα.
User avatar
HdkiLLeR
Venus Project Founder
Venus Project Founder
Posts: 4356
Joined: Tue Jan 27, 2004 4:41 pm
Academic status: Alumnus/a
Gender:
Location: New York, NY
Contact:

Post by HdkiLLeR » Fri May 13, 2005 2:17 pm

Skeftomilos wrote:...Αρκεί ο χρήστης να κατεβάσει το .htm αρχείο και να το αποθηκεύσει στο δίσκο του. Μετά είναι το θέμα της ασφάλειας. Είναι πολύ ευκολότερο να πειστεί ένας άγνωστος να τρέξει ένα .htm...
.htm; Τι είναι αυτό; .html θέλεις να πείς; ;) ;)
-----BEGIN GEEK CODE BLOCK-----
Version: 3.12
GCS d-->--- s+:+ a- C++(+++) BILS++++$ P--- L++++>+++++ E--- W+++ N+ o+ K w--
O M+ V-- PS++>+++ PE- Y++ PGP++ t+ 5+ X+ R* tv b++ DI- D+ G+++ e+++>++++ h r++ y++
------END GEEK CODE BLOCK------

"UNIX is basically a simple operating system, but you have to be a genius to understand the simplicity." -- Dennis Ritchie
User avatar
Skeftomilos
bit level
bit level
Posts: 43
Joined: Fri Mar 04, 2005 8:08 am
Location: Ν.Κόσμος

Post by Skeftomilos » Sat May 14, 2005 2:26 am

.htm .html .xhtml, αυτές και ίσως και άλλες καταλήξεις δηλώνουν σελίδα HTML. Προτιμώ το πρώτο λόγω DOS καταβολών! :)
The pure and simple truth is rarely pure and never simple. Ο μη νους δε σκέπτεται μη σκέψεις για το τίποτα.
User avatar
HdkiLLeR
Venus Project Founder
Venus Project Founder
Posts: 4356
Joined: Tue Jan 27, 2004 4:41 pm
Academic status: Alumnus/a
Gender:
Location: New York, NY
Contact:

Post by HdkiLLeR » Sat May 14, 2005 1:07 pm

Της M$ lameριές είναι αυτό...η ερώτηση ήταν περισσότερο ρητορική ;) :) Αnw έκανες μια πολύ ωραία δουλειά εδω πέρα οπότε τα flames δεν πρέπει να κυριαρχούν. Παρεπιπτώντος για πές λίγα περισσότερα για το PC που γίναν τα tests. OS(εάν Win με SP χωρίς);,μνήμη,cpu κλπ κλπ.
-----BEGIN GEEK CODE BLOCK-----
Version: 3.12
GCS d-->--- s+:+ a- C++(+++) BILS++++$ P--- L++++>+++++ E--- W+++ N+ o+ K w--
O M+ V-- PS++>+++ PE- Y++ PGP++ t+ 5+ X+ R* tv b++ DI- D+ G+++ e+++>++++ h r++ y++
------END GEEK CODE BLOCK------

"UNIX is basically a simple operating system, but you have to be a genius to understand the simplicity." -- Dennis Ritchie
User avatar
AnINffected
Gbyte level
Gbyte level
Posts: 1935
Joined: Fri Jul 30, 2004 7:12 am
Location: There and Back Again

Post by AnINffected » Sat May 14, 2005 3:30 pm

Skeftomilos wrote:Επιπλέον το πρόγραμμα αποτελείται από πηγαίο κώδικα και όχι binary. Αν στο χρήστη δεν αρέσει η γραμματοσειρά, μπορεί μόνος του να αλλάξει το font-family χωρίς να μας ενοχλήσει.
Ναι, όπως μπορεί επίσης να αλλάξει κάτι αθελά του και μετα να σε ενοχλεί για τα καλά... για troubleshooting! :-D
Φιλικά πάντα.

Πολύ ωραίες έρευνες κάνεις.
Αλήθεια πώς τις κάνεις όλες αυτές τις μετρήσεις;
Είναι freeware τα προγραμματα που χρησιμοποιείς;

Αν μπορείς & δε βαριέσαι στείλε μου σε παρακαλώ κανα pm.

edit: Μόλις έφτασα τις 500 δημ.! :smt026
Άντε, να τις χιλιάσω! :smt113
The Analytical Engine has no pretensions to originate anything. It can do whatever we know how to order it to perform (...)
Ada Lovelace


Θέλω και εγώ να παίξω D&D λέμε!!! :-( :-(
User avatar
Skeftomilos
bit level
bit level
Posts: 43
Joined: Fri Mar 04, 2005 8:08 am
Location: Ν.Κόσμος

Post by Skeftomilos » Sat May 14, 2005 6:59 pm

Win XP SP1 2.4Ghz Intel Celeron 512RAM. Τίποτα σπουδαίο, ένα τυπικό μηχανάκι.
AnINffected wrote:...μπορεί επίσης να αλλάξει κάτι αθελά του και μετα να σε ενοχλεί για τα καλά... για troubleshooting!
Αν είναι τόσο απερίσκεπτος να πειράζει τον κώδικα χωρίς να κάνει πρώτα backup το αρχείο ... καλά να πάθει! Επιπλέον μπορούμε να χωρίσουμε το πρόγραμμα σε τρία αρχεία (περιεχόμενο/εμφάνιση/συμπεριφορά, html/css/js) ώστε να διευκολύνουμε το χρήστη να κάνει αλλαγές χωρίς κίνδυνο να πειράξει τον κώδικα. Αυτός ο διαχωρισμός είναι προτεινόμενος έτσι και αλλιώς και για άλλους λόγους.
AnINffected wrote:Αλήθεια πώς τις κάνεις όλες αυτές τις μετρήσεις; Είναι freeware τα προγραμματα που χρησιμοποιείς;
Χμ, δύσκολη ερώτηση. Κατατάσσεις το Notepad στα freeware ή στα simpleware? Μάλλον δεν είναι freeware αφού για να το αποκτήσεις πρέπει να αγοράσεις τα Windows. Τις μετρήσεις τις έκανα με το αντικείμενο Date της JavaScript. Δεν έχει μεγάλη ακρίβεια (~10msec) αλλά δεν είχα ιδιαίτερες απαιτήσεις από το συγκεκριμένο τεστ. Στο κάτω-κάτω οι χρόνοι που αναφέρω παραπάνω είναι μέσοι όροι από αρκετά διάσπαρτες τιμές. Ο IE ήταν ο πιο σταθερός (ελάχιστες αποκλίσεις) ενώ ο Opera ο πιο ασταθής. Π.χ. τη μια στιγμή ήθελε 90msec, την επόμενη 300msec. Να σημειωθεί ότι ο Opera 8.0 διαφημίζεται ως ο ταχύτερος στον κόσμο στην εκτέλεση κώδικα JavaScript. Τεκμηριώνει μάλιστα τον ισχυρισμό και με μετρήσεις που δε μπορώ να αμφισβητήσω. Σίγουρα όμως το optimization δεν κάλυπτε το χρόνο φόρτωσης κώδικα γιατί τα πήγε μάλλον χειρότερα από τους ανταγωνιστές του. Ο γηραλαίος IE6 τα πήγε αρκετά καλύτερα απ' ό,τι περίμενα.

Το πιο δύσκολο μέρος του τεστ ήταν η ανάγκη δημιουργίας διαφορετικής "database" για κάθε δοκιμή. Ευτυχώς οι πρόσφατα αποκτημένες γνώσεις μου σε Windows scripting βοήθησαν να γίνονται πολύ γρήγορα. Τον περισσότερο χρόνο το χάλασα κάνοντας συνεχώς Refresh στους browsers για να βγάλω τους μέσους όρους. Ένα από τα script που χρησιμοποίσα είναι το παρακάτω(object_list_add_with_5000_CREATE.js):

Code: Select all

var FILE_NAME = WScript.ScriptName.replace("_CREATE", "")
var LOOPS = Number(WScript.ScriptName.match(/_(\d*?)_CREATE/)[1])

var fso = WScript.CreateObject("Scripting.FileSystemObject")
var stream = fso.OpenTextFile(FILE_NAME, 2, true, 0) // ForWriting, ASCII.

stream.WriteLine("with(acronym_list) {")
for(var i = 0; i < LOOPS; i++) {
  var temp = i
  var name = ""
  var value = ""
  for(var j = 0; j < 5; j++) {
    var c = String(temp % 10)
    temp = Math.floor(temp / 10)
    name = c + name
    if (value != "") value = " " + value
    value = c + c + c + c + c + c + c + c + c + c + value
  }
  stream.WriteLine('  add("' + name + '", "' + value + '")')
}
stream.WriteLine("}")

stream.Close()
WScript.Echo("Ok!")
Παράγει το αρχείο object_list_add_with_5000.js:

Code: Select all

with(acronym_list) {
  add("00000", "0000000000 0000000000 0000000000 0000000000 0000000000")
  add("00001", "0000000000 0000000000 0000000000 0000000000 1111111111")
  ...
  add("04999", "0000000000 4444444444 9999999999 9999999999 9999999999")
}
Παρακαλώ μην κάνετε ειρωνικά σχόλια για την ποιότητα του κώδικα. Η εκτέλεση δεν διαρκούσε ούτε πέντε δευτερόλεπτα. Δεν υπήρχε λόγος να χάνω χρόνο κάνοντας optimize ένα scriptάκι που παράγει σκουπίδια!
The pure and simple truth is rarely pure and never simple. Ο μη νους δε σκέπτεται μη σκέψεις για το τίποτα.
User avatar
HdkiLLeR
Venus Project Founder
Venus Project Founder
Posts: 4356
Joined: Tue Jan 27, 2004 4:41 pm
Academic status: Alumnus/a
Gender:
Location: New York, NY
Contact:

Post by HdkiLLeR » Sat May 14, 2005 7:32 pm

Κανένα ειρωνικό σχόλιο βρε!! Είμαι περίεργος να το τρέξω και σε κάποιο διαφορετικό build(Linux) στις ίδιες ver ώστε να δούμε τι παίζει.
-----BEGIN GEEK CODE BLOCK-----
Version: 3.12
GCS d-->--- s+:+ a- C++(+++) BILS++++$ P--- L++++>+++++ E--- W+++ N+ o+ K w--
O M+ V-- PS++>+++ PE- Y++ PGP++ t+ 5+ X+ R* tv b++ DI- D+ G+++ e+++>++++ h r++ y++
------END GEEK CODE BLOCK------

"UNIX is basically a simple operating system, but you have to be a genius to understand the simplicity." -- Dennis Ritchie
User avatar
Skeftomilos
bit level
bit level
Posts: 43
Joined: Fri Mar 04, 2005 8:08 am
Location: Ν.Κόσμος

Post by Skeftomilos » Wed May 25, 2005 7:32 am

Το πρόγραμμα Acronyms.htm που υπήρξε η αφορμή για τις παραπάνω μετρήσεις, βρίσκεται online ως επισύναψη του παρακάτω άρθρου:

- Firefox, Smart Keywords και JavaScript
The pure and simple truth is rarely pure and never simple. Ο μη νους δε σκέπτεται μη σκέψεις για το τίποτα.
Post Reply

Return to “Προγραμματισμός”