הבלוג של ינון פרק

טיפים קצרים וחדשות למתכנתים

מנגנוני ניהול זיכרון ב Python

28/12/2019

שפות תכנות רבות דורשות מהמתכנתים לנהל את הזיכרון כלומר להפעיל פקודה כדי "לקבל" זיכרון מהמערכת ואז פקודה נוספת כדי "להחזיר" את הזיכרון למערכת. בשפות כאלה מתכנתים ששכחו להחזיר זיכרון למערכת יצרו קוד שככל שפעל יותר זמן רק דרש יותר ויותר זיכרון מהמערכת, עד שהזיכרון של המכונה נגמר. במצבים כאלה היינו אומרים שיש בתוכנית זליגת זיכרון או Memory Leak באנגלית.

המעבר לשפות שמנהלות לעצמן את הזיכרון תיקן חלקית את הבעיה אבל גם שפות שמנהלות לעצמן את הזיכרון עשויות לטעות ולייצר זליגות זיכרון. שני המודלים המרכזיים בהם שפות מנהלות לעצמן את הזיכרון נקראים Reference Count ו Garbage Collection.

מנגנון Reference Count אומר שהשפה שומרת ליד כל אוביקט מספר שמציין כמה משתנים מתיחסים לאוביקט זה. כשהמספר יורד לאפס באופן אוטומטי הזיכרון של האוביקט חוזר למערכת ההפעלה.

מנגנון Garbage Collection אומר שהשפה מפעילה כל כמה דקות תהליך שמחפש אוביקטים שאי אפשר להגיע אליהם ומחזיר אותם למערכת ההפעלה.

שפת פייתון משתמשת בשילוב של שני המנגנונים כדי לנהל את הזיכרון במקום המתכנת. מנגנון Reference Count פועל בצורה מיידית ומנקה את הזיכרון כשמספר השמות של אוביקט מסוים יורד לאפס, ומנגנון Garbage Collection משמש לגיבוי כדי לזהות מעגלים.

מהו מעגל? שמח ששאלתם. מעגל בהקשר של משתנים וזיכרון אומר שיש לנו מספר משתנים שכל אחד מצביע על אחד אחר. דוגמא קלה היא הקוד הבא ב Python:

a = []
a.append(a)

יצרנו רשימה של איבר אחד שהאיבר הזה הוא מצביע לאותה רשימה ממש. זה נחמד כי עכשיו אפשר לגשת לתא הראשון ברשימה כמה פעמים שרק נרצה:

>>> len(a)
1
>>> len(a[0])
1
>>> len(a[0][0])
1
>>> len(a[0][0][0])
1
>>> len(a[0][0][0][0])
1
>>> len(a[0][0][0][0][0])
1

אבל החיסרון הוא שמנגנון Reference Count כבר לא יכול לעבוד. נלך לראות מה פייתון חושב על הקוד שלנו.

המודול sys מספק פונקציה בשם getrefcount שמחזירה את מספר השמות של אוביקט נתון. הנה כמה דוגמאות:

>>> a = []
>>> sys.getrefcount(a)
2

>>> b = {}
>>> sys.getrefcount(b)
2

>>> a = 10
>>> sys.getrefcount(a)
12

שני הראשונים די מסבירים את עצמם: השם a הוא השם הראשון של הרשימה, ואז בתוך הפונקציה getrefcount יש לרשימה זו שם נוסף (זה שם הפרמטר שהופיע בסוגריים בהגדרת הפונקציה).

הקטע האחרון קצת מבלבל - מאיפה הגיעו 12 שמות למספר 10? הסיבה שבכל מקום שאנחנו כותבים 10 יצרנו עוד שם לדבר הזה בזיכרון שאנחנו קוראים לו המספר 10, וגם פייתון עצמו משתמש בקבוע עשר מדי פעם. ככל שנהיה יותר יצירתיים מספר השמות יורד:

>>> a = 19821
>>> sys.getrefcount(a)
2

עכשיו אפשר לחזור למעגל שלנו:

>>> a = []
>>> a.append(a)
>>> sys.getrefcount(a)
3

לאוביקט יש הפעם 3 שמות: השניים הראשונים מוכרים לנו מהדוגמאות הקודמות והשם השלישי הוא ההצבעה הפנימית. מנגנון Reference Count לעולם לא יוכל למחוק משתנה זה מהזיכרון כי מספר השמות שלו לא ירד אף פעם ל-0. ההצבעה הפנימית תימחק רק כשנאפס אותה ידנית.

לכן פייתון משתמש במנגנון משלים שנקרא Garbage Collection. במנגנון זה פייתון יחפש בצורה פעילה משתנים שאי אפשר להגיע אליהם וימחק אותם מהזיכרון.

נתחיל בחיסול האוביקט שלנו וננסה לראות אם פייתון מחק אותו לבד מהזיכרון:

import sys, gc
a = []
a.append(a)
ida = id(a)
a = 10

# is there still an object with id == i ?
>>> ida in [id(x) for x in gc.get_objects()]
True

הפקודה האחרונה משתמשת במנגנון ה Garbage Collector כדי לזהות האם עדיין יש בזיכרון אוביקט שה id שלו הוא מה שהיה ה id של הרשימה שלנו שהצביעה לעצמה. התשובה היא כמובן True.

זאת בדיוק הסיבה שפייתון מחזיקה גם מנגנון של Garbage Collection לגיבוי. מנגנון זה מכיר את כל האוביקטים במערכת וימחק אוביקטים כאשר אי אפשר להגיע אליהם, גם אם הם כוללים הצבעה מעגלית. מנגנון ה gc מתעורר אוטומטית אחת לכמה דקות או כשאנחנו מבקשים להפעיל אותו עם הפקודה gc.collect. ננסה את זה:

>>> gc.collect()
1

>>> ida in [id(x) for x in gc.get_objects()]
False

ועכשיו הרשימה עם המעגל נמחקה מהזיכרון.

בזכות השילוב בין שני המנגנונים הרבה יותר קשה לייצר זליגות זיכרון בתוכניות פייתון. כדאי לשים לב עם זאת שמנגנון ה Garbage Collector מתעורר בזמנים לא צפויים ועלול לפגוע בביצועים של התוכנית אם אנחנו רוצים לכתוב תוכנית שתגיב בזמן אמת לאירועים חיצוניים. לכן קיימת בפייתון הפקודה gc.disable שמבטלת את מנגנון ה Garbage Collection ומשאירה אותנו רק עם Reference Count. אם אתם בטוחים שאין לכם הצבעות מעגליות באפליקציה וממש צריכים ביצועי Real Time אולי זה משהו ששווה לבדוק אותו.

לא רוצה, לא יכול ולא יודע איך

27/12/2019

בכל פעם שאני מנסה לעשות משהו ולא מצליח האינסטינקט הראשון שלי הוא להאשים את "לא רוצה": אני שם שעון לחמש בבוקר כדי לצאת לריצה וכשהוא מצלצל אני שוב נשאר בתוך הפוך במקום ללבוש את הנעלי ספורט. שעתיים אחר כך הדבר הכי קל להגיד לעצמי זה "כנראה שאני לא באמת רוצה להיכנס לכושר".

בן דוד רחוק של "לא רוצה" נקרא "לא יכול" או "אני לא הטיפוס": אני יושב לנסות ללמוד איך Machine Learning עובד ומסתבך עם המתמטיקה. שלושה ימים אחרי הדבר הכי קל להגיד לעצמי יהיה משהו כמו "כנראה שזה לא לאנשים כמוני", או "אין מה לעשות אין לי ראש לדברים כאלה".

ובדיוק בגלל שאני לא רוצה ואני לא יכול הם חברים כל כך טובים שלנו כדאי לשאול עד כמה הם באמת עוזרים לנו להתמודד עם קשיים. הרי אם אני לא באמת רוצה לעשות ספורט אז בשביל מה להתאמץ פעם הבאה? אולי כשאני ארצה באמת זה יצליח. ואם אני לא יכול ללמוד תכנות אז בשביל מה להמשיך לנסות? עדיף למצוא משהו שאני טוב בו.

סיפור קצת יותר יעיל במצבים שדברים לא מצליחים נקרא "לא יודע איך". הוא בן דוד ממש רחוק של לא רוצה ולא יכול, כל כך רחוק שהם נפגשים רק בראש השנה וגם אז בקושי מחליפים שתי מילים. כי כשלא יודע איך נכנס לתמונה הדלת לרעיונות חדשים נפתחת:

כנראה שאני עדיין לא יודע איך לקום מהמיטה בחמש בבוקר לעשות ספורט. לא סוף העולם מחר אנסה לשים את נעלי הספורט יותר קרוב למיטה ואכין לי פרק של פודקסט שאני רוצה לשמוע. אולי זה יעבוד.

או - כנראה שאני עדיין לא יודע איך ללמוד Machine Learning כמו שצריך. אולי שווה לנסות קורס אחר או לשבת ללמוד עם חבר. או אולי שווה לעשות ריענון על תכנים חשובים במתמטיקה לפני שלוקחים את הקורס אונליין שנרשמתי אליו. אנסה מחר.

הרבה פעמים כשלא רוצה ולא יכול מבלבלים לי את המוח, אני מעדיף להזמין את "לא יודע איך" לארוחה. ממילא אחרי שהוא יגיע שני הנודניקים האלה ימצאו סיבה טובה להיעלם, ואני אוכל להמשיך לעבוד במקום להיתקע על תירוצים.

שגיאות זה לא אוכל

26/12/2019

בליעת שגיאות היא אחת התופעות הנפוצות והכואבות בכתיבת קוד. לבלוע שגיאה זה אומר שהקוד מזהה שמשהו לא בסדר אבל ממשיך לעבוד כרגיל בלי להציף את הבעיה למשתמש. דוגמא לפונקציה שבולעת שגיאה מצאתי השבוע בקוד רובי של חבר:

  def self.track_event(user_token)
    return if user_token.nil?

    session = UserSession.active.find_by(token: user_token)
    session&.update_attribute('last_seen', Time.now)
  end

הפונקציה מצפה לקבל פרמטר שאינו nil ואת זה היא מוודאת באמצעות if בהתחלה. אבל, אם בטעות היא קיבלה nil השגיאה תיבלע ואף אחד לא ידע שהיה נתיב בעייתי בקוד.

בהמשך הפונקציה מנסה למצוא אוביקט UserSession לפי אותו טוקן. וגם כאן אם היא הצליחה למצוא היא תעדכן את ה session, אבל אם לא הצליחה למצוא היא תבלע את השגיאה ולא תדווח על הבעיה לאף אחד. משתמשים פשוט לא יראו את המידע המעודכן שלהם.

אגב האופרטור &. אומר שאם האוביקט אינו nil אז צריך להפעיל את הפונקציה, אבל אם הוא כן nil אז לא עושים כלום. האופרטור נקרא Optional chaining וגירסא שלו תגיע בקרוב גם ל JavaScript.

בחזרה לרובי - אני מבין מאיפה בא הרצון לבלוע שגיאות. אני מבין את הפחד מלוגים ארוכים מדי ומזה שאף אחד לא מוצא זמן לחפש שגיאות או לטפל בהן וממילא יש לנו מספיק באגים גם בלי לקבל עוד דיווחי שגיאות מהמערכת. באמת שאני מבין. אבל האמת שבליעת שגיאות לא חוסכת לכם זמן אלא מבזבזת לכם יותר ממנו, כי כשמשתמש מדווח על התנהגות מוזרה, הדבר שאתם הכי רוצים בעולם זה לפתוח את הקובץ לוג ולראות הודעה שכתבתם לעצמכם מהקוד ומסבירה בדיוק מה קרה.

קוד סטארטר לפיתוח Dashboard ב React

25/12/2019

השילוב של React ו Redux עושה עבודה מצוינת בפיתוח יישומים אינטרקטיביים ובמיוחד כאלה שצריכים להציג או לערוך מידע משותף. במקרה של Dashboard המידע יכול להיות עומס על השרת, מספר משימות שבוצעו, שורות קוד שנכתבו היום או אפילו איזה תאי שירותים פנויים בכל רגע נתון. בעקבות שאלה של חבר כתבתי קוד סטארטר ליישומים מסוג זה ואני משתף אותו כאן גם אתכם.

המשך קריאה

ללמוד איך משתמשים או איך זה בנוי

24/12/2019

קניתי השבוע מברשת שיניים חשמלית לראשונה בחיי. היא מדליקה ממש ומתחברת לטלפון בבלוטות' וכל יום נותנת לי ציונים על זה שאני מצחצח שיניים. הדבר הראשון שעשיתי כשהוצאתי אותה מהקופסא היה לפתוח את חוברת המדריך למשתמש כדי לראות איך משתמשים בזה. את החיבור לטלפון, האפליקציה והבלוטות' אגב הבנתי מהר מאוד גם בלי המדריך, אבל איך לצחצח שיניים נכון זה כבר סיפור יותר מסובך.

אין לי מושג כמובן איך המברשת הזאת בנויה מבפנים וגם ממש לא מעניין אותי לפרק אותה כדי לגלות. אני שמח שיש לי אותה ושאני יודע להשתמש בה כדי לקבל שיניים נקיות.

גם בתוכנה אנחנו יכולים להחליט לקחת גישה דומה, וצריך להגיד - יש מתכנתים מאוד יעילים שמצליחים להשתמש בטכנולוגיות בלי שיהיה להם אכפת איך זה בנוי או עובד מבפנים. אבל רוב המתכנתים הטובים שאני מכיר לא עצרו שם.

זה דבר אחד להיות מסוגל להשתמש ב API שקיבלת ודבר אחר לגמרי להיות הבן אדם שבונה את ה API. זה דבר אחד להיות מסוגל להשתמש בספריה שקיבלת לפי התיעוד שלה, ודבר אחר לגמרי להיכנס לקוד הספריה ולהציע תיקונים ושיפורים. אחרי שלמדנו איך משתמשים במשהו - הצעד הבא הוא תמיד ללמוד איך זה בנוי ואיך בונים אחד בעצמנו.

שדרוגים

23/12/2019

יש רק שתי גישות לגבי שידרוגים: אפשר לשדרג את הפריימוורק וה Dependencies פעם בחודשיים-שלושה ולהקדיש לזה יום עבודה ברבעון, או שאפשר לשדרג פעם בשנתיים-שלוש ולהקדיש לזה שבוע עם כל הריקושטים.

בכל מוצר תוכנה שאני יכול לחשוב עליו שידרוג בין גירסאות קרובות הוא הרבה יותר קל מאשר שידרוג בין גירסאות רחוקות. המעבר מריילס 5.1 ל 5.2 הולך להיות הרבה יותר פשוט מאשר קפיצה מ-4 ל-6. אותו דבר עם אנגולר, ריאקט, Java או כל טכנולוגיה אחרת.

ובכל מקרה כדאי להיות הוגנים עם עצמכם לגבי הבחירה. אם לא שידרגתם פריימוורק בשנתיים האחרונות, השידרוג הולך לקחת יותר מכמה שעות עבודה.

חדש באתר: קורס ריאקט

22/12/2019

ריאקט הוא הפריימוורק האהוב עליי לפיתוח קוד צד-לקוח עבור Web Applications ואני שמח ומתרגש להשיק גירסא מעודכנת של קורס ריאקט כאן בטוקוד. אספר קודם בקצרה מה תמצאו בקורס ואז נדבר על איך הגעתי לבנות את אותו קורס בפעם השלישית.

אבל לפני הכל ולממהרים - זה הלינק לקורס: https://www.tocode.co.il/bundles/react

המשך קריאה

ביי ביי Redux Thunk

21/12/2019

בעבודה עם Redux אולי המידלוור הכי פופולרי היה תקופה ארוכה Redux Thunk. וכמו שהוא היה פופולרי כך הוא גם הזיק לאקו-סיסטם. בימים הקרובים אני הולך להשיק כאן קורס ריאקט חדש לגמרי ואחרי התלבטות החלטתי לוותר על Redux Thunk בסידרת השיעורים על Redux. הנה הסיבות המרכזיות:

המשך קריאה

לקנות הזדמנות

20/12/2019

הרבה אנשים מרגישים שכשהם קונים משהו הם משלמים כסף ומקבלים תוצאה, והתשלום הוא על מה שכבר קרה: אם אני קונה תפוח אני מרגיש ששילמתי על התוצאה של העבודה של כל האנשים שגידלו, הזיזו ומכרו לי את התפוח. אינטרקציות מהסוג הזה מחזקות אצלנו את תחושת ה"מגיע לי", כי שילמתי הרבה כסף על התפוח הזה אז מגיע לי שיהיה תפוח טעים.

יותר מועיל להסתכל על קניה כמו הזדמנות, ולהסתכל על העתיד: אם אני קונה תפוח יש לי הזדמנות להכין עוגת תפוחים נפלאה. זה בידיים שלי, זה יכול להיכשל, ואינטרקציות מהסוג הזה מחזקות אצלנו את תחושת העצמאות והיכולת.

אני רואה את זה גם בקורסים - יש אנשים שיבואו לקורס ובטוחים שאחרי שנה הם ימצאו את משרת החלומות בהייטק. הם ישבו בשיעור, יקשיבו, יכינו את כל תרגילי הבית ולא יבינו מה הם עשו לא בסדר כשהקורס מסתיים והם עדיין לא מצליחים להתקבל לעבודה.

ויש כאלה שמסתכלים על הקורס כמו הזדמנות - אחרי כל שיעור הם יחפשו לראות איך הדברים שלמדו רלוונטים אליהם לחיים וינסו ליישם כל דבר שלמדו כבר מתחילת הקורס. אלה האנשים שמסיימים קורס פיתוח אפליקציות עם 3-4 אפליקציות שהם כתבו שבכלל לא היו קשורות לסילבוס של הקורס, פשוט כי הקורס נתן להם השראה. וכן, אלה גם האנשים שימצאו בקלות את העבודה כשהקורס יסתיים.

האקתונים והרגלים

19/12/2019

האקתונים עובדים. לא בטוח שההאקתונים המסחריים האלה שמרגישים יותר כמו אירוע חברתי מאשר כמו תהליך פיתוח, אבל האקתון במובן של מתכנת שמתחבא במערה ומתכנת עד שהפרויקט גמור. ברנדון אייך כתב את JavaScript בחודש וחצי כזה, וקן ת'ומפסון כתב ככה את Unix.

גם הרגלים עובדים. שבי חצי שעה ביום על דואולינגו ואחרי שנתיים את מתחילה לדבר ספרדית. קומי לריצת בוקר שלוש פעמים בשבוע ואחרי שנה את מוצאת את עצמך בכושר. הרגל מייצר אנרגיה שרק מתחזקת ככל שהזמן עובר.

וכמובן שלכל שיטה יש את המגבלות שלה - אני לא רואה איך אפשר ללמוד שפה בהאקתון, לא משנה כמה אינטנסיבי הוא יהיה, ואני לא רואה איך אפשר לבנות מוצר אם אתה יושב לתכנת חצי שעה ביום, לא משנה כמה מוכשר אתה בתור מתכנת.

אם לא הצלחת להגיע למטרה שרצית, אולי שווה לנסות שיטה אחרת.