Πώς να χρησιμοποιήσετε το Memoize για να αποθηκεύσετε προσωρινά τα αποτελέσματα της λειτουργίας JavaScript και να επιταχύνετε τον κωδικό σας

Οι λειτουργίες αποτελούν αναπόσπαστο μέρος του προγραμματισμού. Βοηθούν στην προσθήκη αρθρωτότητας και επαναχρησιμοποίησης στον κώδικα μας.

Είναι πολύ κοινό να διαιρούμε το πρόγραμμά μας σε κομμάτια χρησιμοποιώντας λειτουργίες τις οποίες μπορούμε να καλέσουμε αργότερα για να εκτελέσουμε κάποια χρήσιμη ενέργεια.

Μερικές φορές, μια συνάρτηση μπορεί να γίνει ακριβή για να καλέσετε πολλές φορές (ας πούμε, μια συνάρτηση για τον υπολογισμό του παραγοντικού ενός αριθμού). Αλλά υπάρχει ένας τρόπος που μπορούμε να βελτιστοποιήσουμε τέτοιες λειτουργίες και να τις κάνουμε να εκτελούνται πολύ πιο γρήγορα: προσωρινή αποθήκευση .

Για παράδειγμα, ας πούμε ότι πρέπει functionνα επιστρέψουμε το παραγοντικό ενός αριθμού:

function factorial(n) { // Calculations: n * (n-1) * (n-2) * ... (2) * (1) return factorial }

Τέλεια, τώρα ας βρούμε factorial(50). Ο υπολογιστής θα εκτελέσει υπολογισμούς και θα μας επιστρέψει την τελική απάντηση, γλυκιά!

Όταν τελειώσει, ας βρούμε factorial(51). Ο υπολογιστής εκτελεί και πάλι έναν αριθμό υπολογισμών και μας δίνει το αποτέλεσμα, αλλά ίσως έχετε παρατηρήσει ότι επαναλαμβάνουμε ήδη ορισμένα βήματα που θα μπορούσαν να είχαν αποφευχθεί. Ένας βελτιστοποιημένος τρόπος θα ήταν:

factorial(51) = factorial(50) * 51

Όμως, functionοι υπολογισμοί μας πραγματοποιούν από το μηδέν κάθε φορά που ονομάζεται:

factorial(51) = 51 * 50 * 49 * ... * 2 * 1

Δεν θα ήταν ωραίο αν κατά κάποιο τρόπο η factorialλειτουργία μας μπορούσε να θυμάται τις τιμές από τους προηγούμενους υπολογισμούς της και να τις χρησιμοποιήσει για να επιταχύνει την εκτέλεση;

Στην απομνημόνευση έρχεται , ένας τρόπος για functionνα θυμόμαστε (cache) τα αποτελέσματα. Τώρα που έχετε μια βασική κατανόηση του τι προσπαθούμε να επιτύχουμε, ακολουθεί ένας επίσημος ορισμός:

Η απομνημόνευση είναι μια τεχνική βελτιστοποίησης που χρησιμοποιείται κυρίως για να επιταχύνει τα προγράμματα υπολογιστών αποθηκεύοντας τα αποτελέσματα δαπανηρών κλήσεων λειτουργίας και επιστρέφοντας το προσωρινά αποθηκευμένο αποτέλεσμα όταν εμφανιστούν ξανά οι ίδιες εισόδους

Η απομνημόνευση με απλούς όρους σημαίνει απομνημόνευση ή αποθήκευση στη μνήμη. Μια απομνημονευμένη συνάρτηση είναι συνήθως γρηγορότερη επειδή εάν η συνάρτηση καλείται στη συνέχεια με τις προηγούμενες τιμές, αντί να εκτελέσουμε τη συνάρτηση, θα πάρουμε το αποτέλεσμα από την προσωρινή μνήμη.

Δείτε πώς μπορεί να μοιάζει μια απλή λειτουργία απομνημόνευσης (και εδώ είναι ένα CodePen σε περίπτωση που θέλετε να αλληλεπιδράσετε με αυτό) :

// a simple function to add something const add = (n) => (n + 10); add(9); // a simple memoized function to add something const memoizedAdd = () => { let cache = {}; return (n) => { if (n in cache) { console.log('Fetching from cache'); return cache[n]; } else { console.log('Calculating result'); let result = n + 10; cache[n] = result; return result; } } } // returned function from memoizedAdd const newAdd = memoizedAdd(); console.log(newAdd(9)); // calculated console.log(newAdd(9)); // cached

Απομνημόνευση

Μερικά προϊόντα από τον παραπάνω κώδικα είναι:

  • memoizedAddεπιστρέφει ένα functionπου καλείται αργότερα. Αυτό είναι δυνατό επειδή στο JavaScript, οι συναρτήσεις είναι αντικείμενα πρώτης κατηγορίας που μας επιτρέπουν να τα χρησιμοποιούμε ως συναρτήσεις υψηλότερης τάξης και να επιστρέφουμε άλλη λειτουργία.
  • cacheμπορεί να θυμηθεί τις τιμές του, καθώς η συνάρτηση που επιστρέφεται έχει κλείσιμο.
  • Είναι σημαντικό η απομνημονευμένη λειτουργία να είναι καθαρή. Μια καθαρή συνάρτηση θα επιστρέψει την ίδια έξοδο για μια συγκεκριμένη είσοδο όχι mater πόσες φορές καλείται, γεγονός που κάνει την cacheεργασία όπως αναμένεται

Γράφοντας τη δική σας memoizeλειτουργία

Ο προηγούμενος κώδικας λειτουργεί καλά, αλλά τι γίνεται αν θέλουμε να μετατρέψουμε οποιαδήποτε λειτουργία σε λειτουργία απομνημόνευσης;

Δείτε πώς μπορείτε να γράψετε τη δική σας λειτουργία απομνημόνευσης (codepen):

// a simple pure function to get a value adding 10 const add = (n) => (n + 10); console.log('Simple call', add(3)); // a simple memoize function that takes in a function // and returns a memoized function const memoize = (fn) => { let cache = {}; return (...args) => { let n = args[0]; // just taking one argument here if (n in cache) { console.log('Fetching from cache'); return cache[n]; } else { console.log('Calculating result'); let result = fn(n); cache[n] = result; return result; } } } // creating a memoized function for the 'add' pure function const memoizedAdd = memoize(add); console.log(memoizedAdd(3)); // calculated console.log(memoizedAdd(3)); // cached console.log(memoizedAdd(4)); // calculated console.log(memoizedAdd(4)); // cached

Τώρα είναι υπέροχο! Αυτή η απλή memoizeλειτουργία θα τυλίξει οποιοδήποτε απλό functionσε ισοδύναμο που έχει απομνημονευθεί. Ο κώδικας λειτουργεί καλά για απλές λειτουργίες και μπορεί εύκολα να τροποποιηθεί για να χειριστεί οποιονδήποτε αριθμό argumentsσύμφωνα με τις ανάγκες σας. Μια άλλη εναλλακτική λύση είναι η χρήση ορισμένων de-facto βιβλιοθηκών όπως:

  • Λοδάς _.memoize(func, [resolver])
  • @memoizeΔιακοσμητές ES7 από το decko

Απομνημόνευση αναδρομικών λειτουργιών

Εάν προσπαθήσετε να περάσετε μια αναδρομική συνάρτηση στην memoizeπαραπάνω λειτουργία ή _.memoizeαπό το Lodash, τα αποτελέσματα δεν θα είναι τα αναμενόμενα, καθώς η αναδρομική συνάρτηση στις επόμενες κλήσεις της θα καταλήξει να καλεί τον εαυτό της αντί της απομνημονευμένης συνάρτησης, χωρίς να κάνει χρήση του cache.

Απλώς βεβαιωθείτε ότι η αναδρομική σας λειτουργία καλεί τη λειτουργία απομνημόνευσης. Δείτε πώς μπορείτε να τροποποιήσετε ένα παραγοντικό παράδειγμα ενός βιβλίου (codepen):

// same memoize function from before const memoize = (fn) => { let cache = {}; return (...args) => { let n = args[0]; if (n in cache) { console.log('Fetching from cache', n); return cache[n]; } else { console.log('Calculating result', n); let result = fn(n); cache[n] = result; return result; } } } const factorial = memoize( (x) => { if (x === 0) { return 1; } else { return x * factorial(x - 1); } } ); console.log(factorial(5)); // calculated console.log(factorial(6)); // calculated for 6 and cached for 5

Μερικά σημεία που πρέπει να σημειώσετε από αυτόν τον κωδικό:

  • Η factorialσυνάρτηση καλεί αναδρομικά μια απομνημονευμένη έκδοση της.
  • Η απομνημονευμένη συνάρτηση αποθηκεύει προσωρινά τις τιμές των προηγούμενων παραγόντων που βελτιώνουν σημαντικά τους υπολογισμούς, καθώς μπορούν να επαναχρησιμοποιηθούν factorial(6) = 6 * factorial(5)

Είναι η απομνημόνευση ίδια με την προσωρινή αποθήκευση;

Ναι, είδος. Η απομνημόνευση είναι στην πραγματικότητα ένας συγκεκριμένος τύπος προσωρινής αποθήκευσης. Ενώ η προσωρινή αποθήκευση μπορεί να αναφέρεται γενικά σε οποιαδήποτε τεχνική αποθήκευσης (όπως HTTP caching) για μελλοντική χρήση, η απομνημόνευση περιλαμβάνει συγκεκριμένα την προσωρινή αποθήκευση των τιμών επιστροφής του a function.

Πότε να απομνημονεύσετε τις λειτουργίες σας

Αν και φαίνεται ότι η απομνημόνευση μπορεί να χρησιμοποιηθεί με όλες τις λειτουργίες, στην πραγματικότητα έχει περιορισμένες περιπτώσεις χρήσης:

  • Για να απομνημονεύσετε μια συνάρτηση, πρέπει να είναι καθαρή, ώστε οι τιμές επιστροφής να είναι ίδιες για τις ίδιες εισόδους κάθε φορά
  • Η απομνημόνευση είναι μια αντιστάθμιση μεταξύ προστιθέμενου χώρου και προστιθέμενης ταχύτητας και συνεπώς μόνο σημαντική για λειτουργίες που έχουν περιορισμένο εύρος εισόδου, έτσι ώστε οι αποθηκευμένες τιμές να μπορούν να χρησιμοποιούνται συχνότερα
  • Μπορεί να φαίνεται ότι πρέπει να απομνημονεύσετε τις κλήσεις API, ωστόσο δεν είναι απαραίτητο, επειδή το πρόγραμμα περιήγησης τις αποθηκεύει αυτόματα για εσάς. Δείτε την προσωρινή αποθήκευση HTTP για περισσότερες λεπτομέρειες
  • Η καλύτερη περίπτωση χρήσης που βρήκα για απομνημονευμένες συναρτήσεις είναι για βαριές υπολογιστικές λειτουργίες που μπορούν να βελτιώσουν σημαντικά την απόδοση (τα παραγοντικά και οι fibonacci δεν είναι πραγματικά καλά παραδείγματα πραγματικού κόσμου)
  • Εάν είστε στο React / Redux, μπορείτε να ελέγξετε ξανά την επιλογή που χρησιμοποιεί έναν απομνημονευτή επιλογέα για να βεβαιωθείτε ότι οι υπολογισμοί πραγματοποιούνται μόνο όταν μια αλλαγή συμβαίνει σε ένα σχετικό μέρος του δέντρου κατάστασης.

Περαιτέρω ανάγνωση

Οι παρακάτω σύνδεσμοι μπορούν να είναι χρήσιμοι εάν θέλετε να μάθετε περισσότερα σχετικά με ορισμένα από τα θέματα αυτού του άρθρου με περισσότερες λεπτομέρειες:

  • Λειτουργίες υψηλότερης τάξης σε JavaScript
  • Κλείσιμο σε JavaScript
  • Καθαρές λειτουργίες
  • Τα _.memoizeέγγραφα και ο πηγαίος κώδικας του Lodash
  • Περισσότερα παραδείγματα απομνημονεύσεων εδώ και εδώ
  • αντιδρά / επανεπιλέξτε

Ελπίζω ότι αυτό το άρθρο ήταν χρήσιμο για εσάς και έχετε κατανοήσει καλύτερα την απομνημόνευση σε JavaScript :)

Μπορείτε να με ακολουθήσετε στο twitter για πιο πρόσφατες ενημερώσεις. Άρχισα επίσης να δημοσιεύω πιο πρόσφατες δημοσιεύσεις στο προσωπικό μου blog.