ביצועי פייתון: לולאות NumPy מותאמות, קריאות חוזרות ועלויות המרה
בדיקה מספרית מבוקרת המשתמשת במעבד בלבד עוקבת אחר ערכים זהים דרך לולאות CPython, חשבון NumPy, עטיפת קריאה חוזרת של פייתון ולולאת Numba מקומפלת.
בדיקה מספרית מבוקרת המשתמשת במעבד בלבד עוקבת אחר ערכים זהים דרך לולאות CPython, חשבון NumPy, עטיפת קריאה חוזרת של פייתון ולולאת Numba מקומפלת. NumPy עם קלט מוכן הייתה בעלת יתרון גדול, בעוד המרה מרשימות פייתון ואליהן צמצמה את היתרון הזה לכמעט תיקו. הפרק מסביר את גבול הביצוע וכיצד למדוד את התוצאה שאפליקציה צריכה.
קראו את המהדורה הכתובה (אנגלית) ↗
מה מכסה הסרטון הזה
- CPython מקמפל קוד מקור לקוד בתים. עבודת מפרש/אובייקט חוזרת ולולאה מספרית מותאמת הן סידורי ביצוע שונים.
- חשבון NumPy מספרי יכול להעביר קבוצות מוגדרות פעולות מקומפלות; לולאת החשבון עדיין קיימת, ותצוגות יכולות להיות בעלות פריסות לא רציפות.
- מדוד את ההמרה ואת ייצוג הפלט הנדרש. בהרצה זו, המרת רשימות פייתון למערכים ובחזרה מחקה את היתרון הגדול של מערך מוכן.
- עומסי עבודה קטנים יכולים להעדיף פייתון רגיל מכיוון שעלויות קריאה קבועות למערך חשובות. ההבנה של שמונה ערכים ניצחה בהשוואה זו שנמדדה.
- np.vectorize היא עטיפת נוחות שקוראת לפייתון עבור אלמנטים עוקבים; ממשק אצווה לבדו אינו קובע ביצוע מספרי מותאם.
- לולאות מקומפלות בתחביר פייתון יכולות להיות מהירות גם כן. כלול עלויות קומפילציה/הגדרה כשהן חשובות לקורא.
- ביצוע מותאם, SIMD של המעבד, ריבוי תהליכים ויישומי קצה של GPU הם רעיונות נפרדים. תכונות מעבד זמינות אינן מזהות את ההוראות ששימשו בהרצה מסוימת.
תמליל מתורגם
תורגם מהקריינות המקורית באנגלית. זמינות אודיו וכתוביות נשלטת על ידי YouTube.
מדוע אותו מעבד יכול לסיים מוקדם יותר?
0:00 אתה חושב שפייתון צריך שפה חדשה כדי להיות מהיר. אותו מעבד הרץ את האצווה שלנו מהר יותר לאחר שהלולאה זזה. לאן נעלמה העבודה? מתי נעלם הזינוק במהירות? איזו קיצור דרך מחזירה את פייתון? זהו The Daily Diff, מתחת למכסה המנוע. שים לב לקיצור הדרך הזה. CPython הוא מימוש הפייתון הסטנדרטי.
0:17 הוא מקמפל את קוד המקור שלך לקוד בתים, ואז מריץ את ההוראות הללו בתוך מפרש. NumPy היא ספרייה נומרית שהמערכים שלה מכילים ערכים עם סוג נתונים מוצהר. החשבון המובנה שלה יכול להעביר אצווה שלמה לפעולות מקומפלות. קח מיליון ערכי נקודה צפה, העלה כל אחד בריבוע, ואז הוסף את הערך המקורי. כל גרסה מקבלת ערכים זהים ומפיקה תשובות תואמות. הערכים קטנים מספיק כדי למנוע הבדלי עיגול מהמירוץ.
0:40 במחשב זה, ההבנה ארכה כשלושים ושלוש מילישניות. NumPy לקחה פחות מאחת. שניהם רצו על המעבד. זו עדות שימושית לעומס עבודה זה, והספד נורא עבור שפת תכנות. התחל עם הלולאה הרגילה.
איזו עבודה לולאת פייתון חוזרת עליה?
0:53 פייתון מקבל את הערך הבא, מבצע את החשבון, ומוסיף את התשובה. ואז הוא עושה זאת שוב. הכפל זול. סידור הכפל הבא, וטיפול באובייקטים של פייתון מסביבו, מוסיף עבודה בכל פעם. זהו הפירוק בפועל של הפונקציה שנבדקה. איטרציה, כפל וחיבור מופיעים כהוראות קוד בתים. גרסאות ומפרשים של פייתון עושים בחירות שונות.
1:14 תזמונים אלה שייכים לגרסה שעל המסך, שהיא פייתון שלוש נקודה עשר. רשימת פייתון מאחסנת הפניות לאובייקטים, כך שסוגים שונים של דברים יכולים לחלוק רשימה. משימת החשבון שלנו משתמשת במספרים לכל אורכה. היא משלמת על סידור כללי בזמן שהיא מבקשת שירות חוזר. תאר לעצמך שאתה מזמין את אותה קפה מיליון פעמים וממלא טופס חדש לכל כוס. מכונת הקפה בסדר.
1:36 הניירת פיתחה קריירה משלה.
לאן NumPy מעביר את הלולאה?
1:38 NumPy משנה את החוזה. מערך זה מכיל ערכי נקודה צפה בגודל קבוע עם סוג מוצהר אחד. המערך שלנו רציף, כך שערכיו יושבים יחד בזיכרון. תצוגות יכולות להיות בעלות פריסות אחרות. פעולת החשבון יודעת איזה סוג נתונים היא מקבלת. הקריאה מעבירה את האצווה לפעולה מותאמת. לולאה עדיין מבקרת את הערכים בתוך קוד מקומפל. היא יכולה להשתמש מחדש בידע על הסוג לאורך כל העבודה.
2:00 פייתון נשאר האדם המוסר את העבודה. איתמר טרנר טרורינג מסביר את ההבדל ב-Python Speed. ממשקי אצווה, יישומים מקומיים והוראות וקטוריות של מעבד הם דברים נפרדים. שם הפונקציה מספק מעט מאוד ראיות.
האם מהיר פירושו GPU או ליבות מעבד רבות?
2:13 תוצאה זו השתמשה ב-NumPy על המעבד. קצה עורפי של GPU כמו CuPy בוחר בית אחר למערכים ופעולות. קניית GPU משאירה את לולאת הפייתון הרגילה הזו פועלת היכן שכבר פעלה. הוראות וקטוריות של המעבד יכולות לעבד מספר ערכים יחד. ל-NumPy יש מנגנון לבחירת ליבות נתמכות. זמן הריצה שלנו מדווח על תכונות זמינות, אך אינו מזהה את הוראות הביטוי הזה. מתן כל ההישג הנמדד להוראות וקטוריות יגזים בראיות.
2:40 עגלת הקניות של ה-GPU יכולה להישאר סגורה. יש משהו מספק עמוקות בתיקון ביצועים שמבטל גם רכישה. לרוע המזל, בדיקת הביצועים נמשכת.
מתי הישג המהירות המרשים נעלם?
2:49 עם שמונה ערכים, ההבנה ניצחה. קריאה למנגנון המערך עלתה יותר מהמשימה הקטנה. מערכים מוכנים גדולים יותר התקדמו. השתמשנו בניסויים חוזרים ובתהליך מחומם. בניות חדשות יותר של פייתון יכולות לשנות את נקודת המעבר. ואז לשנות את מה שהיישום צריך. התחל עם רשימת פייתון וסיים עם רשימת פייתון חדשה. NumPy חייב להמיר את הקלט למערך ולהחזיר את התשובה לאובייקטים של פייתון.
3:11 שלבים אלה שייכים לחשבון אם היישום שלך זקוק להם. עם מיליון ערכים, נתיב שלם זה לקח כשמונה ועשרים מילישניות. ההבנה לקחה כשמונה ועשרים גם כן. היתרון הדרמטי שלנו הפך לכמעט תיקו. החלק המהיר היה אמיתי. היתרון היה תלוי באפשרות שהנתונים יישארו בצורתם המספרית.
אילו גבולות שייכים לבדיקת הביצועים?
3:28 עקוב אחר הנתונים. אם הפעולה הבאה מקבלת מערך, השאר את המערך. אם אתה ממיר שוב ושוב בין רשימות למערכים, מדוד את המעברים הללו. עצירה לפני מסירה יקרה נותנת מספרים מדויקים לשאלה הלא נכונה. הכפלת מערך זה יוצרת תוצאה זמנית. הוספת הערכים המקוריים יוצרת את הפלט. ביצוע מותאם עדיין זקוק לזיכרון ומזיז נתונים.
3:47 חלק מהפעולות מקבלות חוצץ פלט, ומפחיתות הקצאות כאשר הקוד שלך יכול להשתמש בו מחדש בבטחה. שורה יפה אחת יכולה להזמין מספר חוצצים גדולים. Python Speed מצביע גם על בעיה זו. המקצה אדיש באופן מדהים לאסתטיקה שלך.
האם לולאת פייתון מקומפלת יכולה להיות מהירה גם?
4:00 אתה יכול גם לשמור על תחביר לולאה ולשנות את הביצוע שלה. Numba יכולה לקמפל פונקציות פייתון מספריות נתמכות לקוד מותאם. הנה הלולאה מהבדיקה שלנו עם המקשט הקומפיילר שלה. היא עדיין אומרת for. נתיב המפרש היקר השתנה. לאחר קומפילציה, לולאה זו לקחה פחות מחצי מילישנייה עם מערכים מוכנים. הקומפילציה עצמה לקחה כשנייה בהרצה זו. עלות ההגדרה הזו הוצאה מהתזמונים החוזרים,
4:22 כך שעבודה חד פעמית קטנה ראויה להשוואה שונה. המדריך של Numba עצמה שמח להציג לולאות מפורשות. התחלת הגירת שפה לפני מציאת הנתיב החם היא דרך מורכבת למנוע פתיחת פרופיילר. קודם כל מצא מה צורך את הזמן. המתנות רשת, ניתוח אובייקטים וחשבון בכמות גדולה דורשים תיקונים שונים. פעולות מערך מותאמות עוזרות כאשר החישוב והנתונים מתאימים.
4:43 מדוד את התוצאה שהקורא שלך באמת זקוק לה. ועכשיו לקיצור הדרך הזה. יש לך פונקציית פייתון ואתה רוצה שהיא תקבל מערך. עטיפה בשם vectorize נשמעת כמו כפתור הביצועים החסר. התיעוד שלה מכיל משפט חשוב שמחכה.
מה np.vectorize באמת מבצע?
4:56 NumPy אומר שעטיפת ה-vectorize שלה מסופקת בעיקר לנוחות. המימוש שלה הוא למעשה לולאת for. העטיפה מקבלת אצוות, אך ממשיכה לקרוא לפונקציית הפייתון שלך עבור אלמנטים עוקבים. הממשק התקצר. גבול הביצוע נשאר עסוק. צפה בערכים זהים חוזרים דרך הקריאה החוזרת של פייתון. עם מערכים מוכנים, גרסה זו לקחה כשמונים ושבע מילישניות. היא הייתה איטית יותר מההבנה שלנו.
5:18 עטיפת broadcasting שימושית יכולה לשמר את העבודה המדויקת לכל אלמנט שניסית להסיר. בדוק את המימוש שמאחורי ממשק אצווה, שמור נתונים תואמים בצורתם השימושית, וכלול המרה בהשוואה. לולאות מותאמות, קריאות חוזרות ומקומפלות יכולות להיראות דומות מכסא הקורא. השם vectorize הבטיח הבטחה שלעולם לא נחתמה על ידי שעון העצר שלך. שמור עליו כאשר נוחות היא מה שאתה צריך. לביצועים, בדוק את הלולאה שרצה.
מה עליך לשנות בקוד שלך?
5:41 פסק הדין שלי הוא SHIP IT לאצווה של נתיב חם מספרי שנמדד. שמור על לולאות רגילות כאשר הן כבר עובדות היטב. בדוק היכן מתבצעת העבודה ומה הקורא הבא צריך. הירשמו, לחצו על הפעמון, וספרו לי בתגובות אם הייתם חותמים על זה אחרת. וזהו ה-diff להיום. אני ניקו מ-Axrisi. מזג באחריות.
מקורות
- Universal functions (ufunc) basics — NumPy 2.2NumPy
- The N-dimensional array — NumPy 2.2NumPy
- numpy.vectorize — convenience and performance notesNumPy
- CPU/SIMD optimizations — NumPy 2.2NumPy
- dis — Disassembler for Python bytecodePython Software Foundation
- CPython 3.10.12 list representationCPython contributors
- The limits of Python vectorization as a performance techniqueItamar Turner-Trauring / Python⇒Speed
- Performance TipsNumba
- Basics of CuPyCuPy



