רציתי לכתוב פוסט על Numba והחלטתי להשתדל לצמצם אותו ככל הניתן עם דוגמה איך numba יכולה לשפר את הביצועים של סקריפט פייטון על קוד עולם אמיתי (ובכן לא ממש, התרגיל הזה לא מאוד שימושי אבל שיהיה).
ראשית מה זה Numba.
מהאתר של הפרוייקט הכותרת הבומבסטית מכריזה על:
״Numba is an open source JIT compiler that translates a subset of Python and NumPy code into fast machine code.״
אני לא ארחיב על המבנה הפנימי של הספריה ואיך היא מבצעת את הקסמים שלה, כשאם נודה על האמת אני כמובן גם לא מומחה בזה, אבל כל מה שאנחנו צריכים לדעת כדי להתחיל להשתמש ב Numba הוא שבאמצות שימוש ב decorators פשוטים על פונקציות פייטון מתאימות*, הספריה תדע לבצע לנו אופטימיזציה של הקוד בזמן הריצה על אותה הפונקציה.
* מתאימות לא במקרה נרשם עם כוכבית- לא כל פונקציה תעבוד היטב עם Numba. פונקציות שכדאי להריץ עם numba הן פונקציות שמבצעות חישובים רבים, כשסימן נוסף לכך שאולי יש סיבה להשתמש ב numba היא ריבוי לולאות משמעותיות כלומר עם איטרציות גדולות (מה שיכול גם להעיד במקרים מסוימים על אימפלמנטציה לא יעילה אבל זה כבר דיון אחר), ולא פחות חשוב הקוד צריך במקרים רבים לעבור התאמה מסוימת על מנת לרוץ בצורה מיטבית:
ל Numba יש שני דקורטורים עיקריים. אחד numba.njit והשני numba.jit
הדקורטר njit יאפשר רק לפונקציות שנכתבו בצורה מותאמת לספריה לרוץ, אחרות יכשלו. דוגמה פשוטה הוא שימוש ב list רגיל של פייטון (עם Numba נשתמש בחלופה מספריית numpy שהיא numpy.array).
לעומת זאת, jit תרוץ תמיד (עם warrning). חשוב להבין: שיפור הביצועים המהותי יגיע בפונקציות שעברו התאמה. פונקציות שרצות עם מצב תאימות לפייתון, יכולות להיות אפילו איטיות יותר מאשר במצב הרצה ללא הספריה, לכן אני אישית ממליץ להשתמש ב njit, להתאמץ לפתור ולסדר את הבעיות תאימות בקוד, כפי שנראה בדוגמה שאתן כאן.
התרגיל
אז לצורך ההמחשה של היתרון בשימוש ב numba בחרתי במשהו שהוא תרגיל מוכר במבחני עבודה.
נניח ויש לנו נתונים יומיים על מניה לאורך זמן ארוך. פותר התרגיל צריך לממש פונקציה שתדפיס את צמד הימים האידיאלי של הקניה והמכירה של המניה, כלומר את היום שבו אם היינו קונים את המניה והיום שבו היינו מוכרים היינו ממקסמים את הרווח האפשרי מהמניה.
אנחנו לפתרון נעבוד עם מספרים שלמים, ולא float. זה כמובן לא מדויק אבל נזרום עם זה לפשטות (הרצתי גם פתרון עם float אבל אני משאיר את זה לקוראים כתרגיל :)).
כל קטעי הקוד, כקובץ אחד המוכן להרצה ניתנים להורדה כאן.
כתבתי פונקציה בסיסית שפותרת את הבעיה, ביעילות סבירה של (O(n, היא רק נועדה להדגים את הפתרון ולא כתובה בצורה הכי יפה וללא תחכום יתר, היא נועדה להיות הכי ברורה לקריאה ומעקב. אתם מוזמנים להריץ אותה עם debugger כדי להבין את אופן עבודתה במידת הצורך.
עכשיו ננסה להמיר אותה לעבוד עם numba
כשנבצע את זה, נגלה מספר בעיות שנאלץ לפתור. להלן הקוד לאחר השינוים שביצעתי בו, הסתכלו על שתי הפונקציות (המקורית וזאת שניתנת כאן) וזהו את ההבדלים הקטנים בקוד.
השינוים הם בעיקר:
- במקום list הפרמטר של הפונקציה הוא מסוג numpy.array
- העברנו את הלוגיקה של האיבר הראשון לתוך ה for כי לבצע slice פשוט לא מותאם לספריה.
הקוד מעט פחות נקי בעיניי, וממש מעט יותר ארוך. אך השינוים שנדרשו הם קטנים יחסית.
בעת דיבוג (ניפוי שגיאות בשביל טרחני השפה) של הקוד אני ממליץ לבטל את ה decorator של numba, אחרת הדיבאגר לא ממש יכול לעזור לנו (לא נוכל לרוץ בדיבאגר בין השורות שבפונקציה).
הוספתי קוד בדיקה עם נתונים רנדומליים שרץ שלוש פעמים על כל אחת מהשיטות.
התוצאות:
אפשר להבחין שבהרצה הראשונה השיפור קטן יותר אך עדיין משמעותי. בהרצה הזו הקוד מתקמפל ולכן שיפור הביצועים קטן יותר איך עדיין משמעותי מאוד. השיפור בביצועים נע בין כ- 44 אחוז (בהרצה הראשונה) לקרוב ל 70 אחוז. לא רע בכלל.
לסיכום:
עם שינוים קטנים בקוד, הצלחנו להאיץ את מהירות הריצה בצורה משמעותית. אם היתה לנו פונקציה עם יעילות ריצה נמוכה יותר היינו מקבלים אפילו שיפור גדול יותר בביצועים. לא רע סה״כ. זה בהחלט לא פתרון שתמיד יהיה נוח להשתמש בו, אבל זו אופציה שכדאי להכיר ולהשתמש בה בעת הצורך. השימוש בה מיותר לפונקציות קטנות שמבצעות מעט חישובים, והן אף צפויות להיות איטיות יותר. ככל שהאלגוריתם שלנו מורכב יותר ועובד על סט גדול יותר של נתונים ככה עולה הסבירות לשקול את השימוש בספריה.
בנוסף, ניתן להשתמש עם numba לעבודה עם ריבוי מעבדים. כידוע, בפייתון, תהליך לא יכול לרוץ במקביליות על פני מספר מעבדים, כשהסיבה לכך היא ה- GIL הידוע לשמצה (אין צורך באמת לשנוא אותו, יש סיבות טובות לכך שהוא שם, ועל זה אולי בפעם אחרת).בגלל שהקוד בפונקציה מקומפל, ואינו תלוי באינטרפטר של פייטון בזמן הריצה שלו, הספריה מאפשרת לנו לנצל ריבוי מעבדים על ידי ביטול התלות ב GIL, ככה שאלגוריתמים שניתן לפצל את עבודתם בין מעבדים יכולים לנצל את ריבוי המעבדים בקלות. יתרון עצום!
אין תגובות:
הוסף רשומת תגובה