Προφίλ κώδικα Python με χρήση timeit και cProfile - KDnuggets

Προφίλ κώδικα Python με χρήση timeit και cProfile – KDnuggets

Κόμβος πηγής: 2874649

Δημιουργία προφίλ κώδικα Python με χρήση timeit και cProfile
Εικόνα από συγγραφέα
 

Ως προγραμματιστής λογισμικού, πιθανότατα θα έχετε ακούσει το απόσπασμα «Η πρόωρη βελτιστοποίηση είναι η ρίζα όλου του κακού»— περισσότερες από μία φορές — στην καριέρα σας. Ενώ η βελτιστοποίηση μπορεί να μην είναι εξαιρετικά χρήσιμη (ή απολύτως απαραίτητη) για μικρά έργα, η δημιουργία προφίλ είναι συχνά χρήσιμη. 

Αφού ολοκληρώσετε την κωδικοποίηση μιας ενότητας, είναι καλή πρακτική να δημιουργείτε προφίλ του κώδικά σας για να μετρήσετε πόσο χρόνο χρειάζεται για να εκτελεστεί καθεμία από τις ενότητες. Αυτό μπορεί να βοηθήσει στον εντοπισμό μυρωδιών κώδικα και να καθοδηγήσει βελτιστοποιήσεις για τη βελτίωση της ποιότητας του κώδικα. Οπότε πάντα το προφίλ του κώδικά σας πριν από τη βελτιστοποίηση!

Για να κάνετε τα πρώτα βήματα, αυτός ο οδηγός θα σας βοηθήσει να ξεκινήσετε με τη δημιουργία προφίλ στην Python—χρησιμοποιώντας το ενσωματωμένο timeit και cΠροφίλ ενότητες. Θα μάθετε να χρησιμοποιείτε τόσο τη διεπαφή γραμμής εντολών όσο και τα ισοδύναμα καλούμενα μέσα σε σενάρια Python.

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

Ας πάρουμε ένα απλό παράδειγμα αντιστροφής μιας λίστας Python. Θα μετρήσουμε τους χρόνους εκτέλεσης για τη λήψη ενός αντίστροφου αντιγράφου της λίστας χρησιμοποιώντας:

  • ο reversed() λειτουργία, και
  • τεμαχισμός λίστας. 
>>> nums=[6,9,2,3,7]
>>> list(reversed(nums))
[7, 3, 2, 9, 6]
>>> nums[::-1]
[7, 3, 2, 9, 6]

 

Χρόνος εκτέλεσης στη Γραμμή εντολών

Μπορείτε να τρέξετε timeit στη γραμμή εντολών χρησιμοποιώντας τη σύνταξη:

$ python -m timeit -s 'setup-code' -n 'number' -r 'repeat' 'stmt'

 

Απαιτείται να παράσχετε τη δήλωση stmt του οποίου ο χρόνος εκτέλεσης πρέπει να μετρηθεί. 

Μπορείτε να καθορίσετε το setup κωδικοποιήστε όταν χρειάζεται—χρησιμοποιώντας τη σύντομη επιλογή -s ή τη μεγάλη επιλογή –setup. Ο κωδικός εγκατάστασης θα εκτελεστεί μόνο μία φορά.

Η number χρόνους εκτέλεσης της πρότασης: η σύντομη επιλογή -n ή η μεγάλη επιλογή -ο αριθμός είναι προαιρετική. Και πόσες φορές θα επαναλάβετε αυτόν τον κύκλο: η σύντομη επιλογή -r ή η μεγάλη επιλογή - η επανάληψη είναι επίσης προαιρετική.

Ας δούμε τα παραπάνω στην πράξη για το παράδειγμά μας:

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

$ python -m timeit -s 'nums=[6,9,2,3,7]' 'list(reversed(nums))'
500000 loops, best of 5: 695 nsec per loop

 

Όταν δεν καθορίζετε τιμές για repeat, χρησιμοποιείται η προεπιλεγμένη τιμή 5. Και όταν δεν διευκρινίζεις number, ο κώδικας εκτελείται όσες φορές χρειάζεται, ώστε να φτάσει ο συνολικός χρόνος τουλάχιστον 0.2 δευτερόλεπτα.

Αυτό το παράδειγμα ορίζει ρητά τον αριθμό των φορών που θα εκτελεστεί η πρόταση:

$ python -m timeit -s 'nums=[6,9,2,3,7]' -n 100Bu000 'list(reversed(nums))'
100000 loops, best of 5: 540 nsec per loop

 

Η προεπιλεγμένη τιμή του repeat είναι 5, αλλά μπορούμε να το ορίσουμε σε οποιαδήποτε κατάλληλη τιμή:

$ python3 -m timeit -s 'nums=[6,9,2,3,7]' -r 3 'list(reversed(nums))'
500000 loops, best of 3: 663 nsec per loop

 

Ας χρονομετρήσουμε επίσης την προσέγγιση τεμαχισμού λίστας:

$ python3 -m timeit -s 'nums=[6,9,2,3,7]' 'nums[::-1]'
1000000 loops, best of 5: 142 nsec per loop

 

Η προσέγγιση τεμαχισμού λίστας φαίνεται να είναι πιο γρήγορη (όλα τα παραδείγματα βρίσκονται στην Python 3.10 στο Ubuntu 22.04).

Εκτέλεση χρόνου σε σενάριο Python

Εδώ είναι το ισοδύναμο του χρόνου εκτέλεσης μέσα στο σενάριο Python:

import timeit setup = 'nums=[9,2,3,7,6]'
number = 100000
stmt1 = 'list(reversed(nums))'
stmt2 = 'nums[::-1]' t1 = timeit.timeit(setup=setup,stmt=stmt1,number=number)
t2 = timeit.timeit(setup=setup,stmt=stmt2,number=number) print(f"Using reversed() fn.: {t1}")
print(f"Using list slicing: {t2}")

 

Η timeit() Το callable επιστρέφει το χρόνο εκτέλεσης του stmt for number των καιρών. Σημειώστε ότι μπορούμε να αναφέρουμε ρητά τον αριθμό των φορών που πρέπει να τρέξουμε ή να κάνουμε number πάρτε την προεπιλεγμένη τιμή 1000000.

Output >>
Using reversed() fn.: 0.08982690000000002
Using list slicing: 0.015550800000000004

 

Αυτό εκτελεί τη δήλωση—χωρίς να επαναλάβει τη λειτουργία του χρονοδιακόπτη—για το καθορισμένο number φορές και επιστρέφει το χρόνο εκτέλεσης. Είναι επίσης αρκετά συνηθισμένο στη χρήση time.repeat() και αφιερώστε τον ελάχιστο χρόνο όπως φαίνεται:

import timeit setup = 'nums=[9,2,3,7,6]'
number = 100000
stmt1 = 'list(reversed(nums))'
stmt2 = 'nums[::-1]' t1 = min(timeit.repeat(setup=setup,stmt=stmt1,number=number))
t2 = min(timeit.repeat(setup=setup,stmt=stmt2,number=number)) print(f"Using reversed() fn.: {t1}")
print(f"Using list slicing: {t2}")

 

Αυτό θα επαναλάβει τη διαδικασία εκτέλεσης του κώδικα number των καιρών repeat πολλές φορές και επιστρέφει τον ελάχιστο χρόνο εκτέλεσης. Εδώ έχουμε 5 επαναλήψεις από 100000 φορές η καθεμία.

Output >>
Using reversed() fn.: 0.055375300000000016
Using list slicing: 0.015101400000000043

Είδαμε πώς το timeit μπορεί να χρησιμοποιηθεί για τη μέτρηση των χρόνων εκτέλεσης των σύντομων αποσπασμάτων κώδικα. Ωστόσο, στην πράξη, είναι πιο χρήσιμο το προφίλ ενός ολόκληρου σεναρίου Python. 

Αυτό θα μας δώσει τους χρόνους εκτέλεσης όλων των συναρτήσεων και των κλήσεων μεθόδων—συμπεριλαμβανομένων των ενσωματωμένων συναρτήσεων και μεθόδων. Έτσι μπορούμε να έχουμε μια καλύτερη ιδέα για τις πιο ακριβές κλήσεις συναρτήσεων και να εντοπίσουμε ευκαιρίες για βελτιστοποίηση. Για παράδειγμα: μπορεί να υπάρχει μια κλήση API που είναι πολύ αργή. Ή μια συνάρτηση μπορεί να έχει έναν βρόχο που μπορεί να αντικατασταθεί από μια πιο Pythonic έκφραση κατανόησης. 

Ας μάθουμε πώς να δημιουργείτε προφίλ σε σενάρια Python χρησιμοποιώντας τη λειτουργική μονάδα cProfile (επίσης μέρος της τυπικής βιβλιοθήκης Python). 

Εξετάστε το ακόλουθο σενάριο Python:

# main.py
import time def func(num): for i in range(num): print(i) def another_func(num): time.sleep(num) print(f"Slept for {num} seconds") def useful_func(nums, target): if target in nums: return nums.index(target) if __name__ == "__main__": func(1000) another_func(20) useful_func([2, 8, 12, 4], 12)

 

Εδώ έχουμε τρεις λειτουργίες:

  • func() που περιηγείται σε μια σειρά αριθμών και τους εκτυπώνει.
  • another func() που περιέχει μια κλήση προς το sleep() λειτουργία.
  • useful_func() που επιστρέφει το ευρετήριο ενός αριθμού στόχου στη λίστα (εάν ο στόχος υπάρχει στη λίστα).

Οι παραπάνω συναρτήσεις θα καλούνται κάθε φορά που εκτελείτε το σενάριο main.py.

Εκτέλεση cProfile στη γραμμή εντολών

Εκτελέστε το cProfile στη γραμμή εντολών χρησιμοποιώντας:

python3 -m file-name.py

 

Εδώ ονομάσαμε το αρχείο main.py:

python3 -m main.py

 

Εκτελώντας αυτό θα πρέπει να έχετε την ακόλουθη έξοδο:

 Output >> 0 ... 999 Slept for 20 seconds

 

Και το παρακάτω προφίλ:

 

Δημιουργία προφίλ κώδικα Python με χρήση timeit και cProfile
 

Εδώ, ncalls αναφέρεται στον αριθμό των κλήσεων στη λειτουργία και percall αναφέρεται στον χρόνο ανά κλήση λειτουργίας. Αν η τιμή του ncalls είναι μεγαλύτερο από ένα, λοιπόν percall είναι ο μέσος χρόνος για όλες τις κλήσεις.

Ο χρόνος εκτέλεσης του σεναρίου κυριαρχείται από another_func που χρησιμοποιεί το ενσωματωμένο sleep κλήση λειτουργίας (αδράνει για 20 δευτερόλεπτα). Το βλέπουμε αυτό print Οι κλήσεις λειτουργιών είναι επίσης αρκετά ακριβές. 

Χρήση cProfile στο σενάριο Python

Ενώ η εκτέλεση του cProfile στη γραμμή εντολών λειτουργεί καλά, μπορείτε επίσης να προσθέσετε τη λειτουργία δημιουργίας προφίλ στο σενάριο Python. Μπορείτε να χρησιμοποιήσετε το cProfile σε συνδυασμό με το μονάδα pstats για δημιουργία προφίλ και πρόσβαση σε στατιστικά στοιχεία.

Ως βέλτιστη πρακτική για να χειριστείτε καλύτερα τη ρύθμιση πόρων και το teardown, χρησιμοποιήστε τη δήλωση with και δημιουργήστε ένα αντικείμενο προφίλ που χρησιμοποιείται ως διαχειριστής περιβάλλοντος:

# main.py
import pstats
import time
import cProfile def func(num): for i in range(num): print(i) def another_func(num): time.sleep(num) print(f"Slept for {num} seconds") def useful_func(nums, target): if target in nums: return nums.index(target) if __name__ == "__main__": with cProfile.Profile() as profile: func(1000) another_func(20) useful_func([2, 8, 12, 4], 12) profile_result = pstats.Stats(profile) profile_result.print_stats()

 

Ας ρίξουμε μια πιο προσεκτική ματιά στο προφίλ εξόδου που δημιουργήθηκε:

 

Δημιουργία προφίλ κώδικα Python με χρήση timeit και cProfile
 

Όταν δημιουργείτε προφίλ σε ένα μεγάλο σενάριο, θα είναι χρήσιμο ταξινομήστε τα αποτελέσματα κατά χρόνο εκτέλεσης. Για να το κάνετε αυτό, μπορείτε να καλέσετε sort_stats στο αντικείμενο προφίλ και ταξινομήστε με βάση το χρόνο εκτέλεσης: 

...
if __name__ == "__main__": with cProfile.Profile() as profile: func(1000) another_func(20) useful_func([2, 8, 12, 4], 12) profile_result = pstats.Stats(profile) profile_result.sort_stats(pstats.SortKey.TIME) profile_result.print_stats()

 

Όταν εκτελείτε τώρα το σενάριο, θα πρέπει να μπορείτε να δείτε τα αποτελέσματα ταξινομημένα κατά χρόνο:

 

Δημιουργία προφίλ κώδικα Python με χρήση timeit και cProfile

Ελπίζω αυτός ο οδηγός να σας βοηθήσει να ξεκινήσετε με τη δημιουργία προφίλ στην Python. Να θυμάστε πάντα, ότι οι βελτιστοποιήσεις δεν πρέπει ποτέ να βαρύνουν την αναγνωσιμότητα. Αν ενδιαφέρεστε να μάθετε για άλλους προφίλ, συμπεριλαμβανομένων των πακέτων Python τρίτων, ρίξτε μια ματιά σε αυτό άρθρο για τους προγραμματιστές προφίλ Python.
 
 
Bala Priya C είναι προγραμματιστής και τεχνικός συγγραφέας από την Ινδία. Της αρέσει να εργάζεται στη διασταύρωση των μαθηματικών, του προγραμματισμού, της επιστήμης δεδομένων και της δημιουργίας περιεχομένου. Οι τομείς ενδιαφέροντος και εξειδίκευσής της περιλαμβάνουν τα DevOps, την επιστήμη δεδομένων και την επεξεργασία φυσικής γλώσσας. Της αρέσει να διαβάζει, να γράφει, να κωδικοποιεί και τον καφέ! Επί του παρόντος, εργάζεται για να μάθει και να μοιράζεται τις γνώσεις της με την κοινότητα προγραμματιστών, γράφοντας σεμινάρια, οδηγούς με οδηγίες, απόψεις και πολλά άλλα.
 

Σφραγίδα ώρας:

Περισσότερα από KDnuggets