על wait queues, wake-ups, sleeping וחלומות אחרים

בלינוקס למעשה פרוסס יכול לישון כאשר הוא מחכה לI/O או למשאב אחר של המערכת (קובץ, semaphore וכו') כי מה הטעם לקפוץ אל פרוסס (schedule() לאותו הפרוסס) שהוא למעשה לא מבצע שום עבודה?
על כן, הפרוסס יכול להכנס לnot-runnable state (למעשה הstate member בtask_struct שמאפיין כל task של המערכת הוא TASK_INTERRUPTIBLE או TASK_UNINTERRUPTIBLE) וכמובן לצאת מהrun queue (שמתאר את הפרוססים שיכולים לרוץ או רצים כעת, כלומר, state = TASK_RUNNING, ולמעשה מי שקרא את הפוסט הקודם יודע שמדובר בrb tree ולא בqueue
אבל אני שומר פה על שמות פורמאליים) ולהכנס אל מושג שאולי אתם מכירים ואולי לא בשם wait queues, שבו למעשה מחכים פרוססים להתעורר ולרוץ שוב ברגע שהם יכולים.

במאמר זה אני אציג כיצד בפועל נכנסים אל wait queue ואיך מתעוררים ממנו (או שנכנסים לאיזור הלימבו ולא יוצאים משם לעד…או שלא) wait queues מייצגים לנו את כל הפרוססים שלא צריכים לרוץ כרגע, כאמור,
מחכים לאירוע כלשהו. בנוסף, יש הבדלים בין הentries השונים (למעשה הפרוססים השונים) בwait queues.
קרא עוד »

על קצה המזלג של הCFS של לינוקס

תהליך הschedueling בלינוקס קורה מעט שונה מאשר במערכות unix אחרות. ב-2.6.23 נכתב scheduler בשם CFS (completely fair scheduler) שבא להחליף את האלגוריתם הקיים מאחר ולא תפקד בצורה טובה מול תוכנות שדורשות אינטרקציה מלאה עם המשתמש (כמו שבא לידי ביטוי למשל במחשבים הפרטיים). אני רק רוצה לציין שהפוסט מציג את CFS ברמה הקונספטואלית הבסיסית ולא מתעמק עד לרמת הביט.
קרא עוד »

istreambuf_iterator/ostreambuf_iterator – מה מי מו?

על מנת לעבור לכל צורך שהוא כמו העתקה, קריאה או ביצוע כל פעולה אחרת על i/ostream, רבים משתמשים ב-istream_iterator/ostream_iterator גם כשלפעמים יש אלטרנטיבה הרבה יותר יעילה.
אחת האלטרנטיבות היא istreambuf_iterator/ostreambuf_iterator ובפוסט זה אעבור על מתי עדיף להשתמש באלו.
קרא עוד »

עד כמה reference counting משרת אותנו עד הסוף?

אני מניח שכבר שמעתם על std::auto_ptr ולמה במקרים רבים כבר עדיף להשתמש ב-std::tr1::shared_ptr.
אבל האם השימוש ב-std::tr1::shared_ptr מציל אותנו מכל מצב? האם התשובה לכל memory leak היא shared_ptr?
התשובה היא לא. ובכן, הפוסט הזה לא הולך לגעת כיצד כל אחד משני ה-smart pointers עובדים אלא על מקרה קצה שבו shared_ptr לא מתפקד איך שהיינו מצפים (אלא שבמחשבה שניה, נראה כי הוא עושה את עבודתו מצויין).
קרא עוד »

על exception-safety ואיך לכתוב קוד טוב יותר

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

namespace pollution – או שלא? (סודותיו האפלים של הקומפיילר)

נסתכל על הקוד הבא:

namespace MyScope
{
    struct MyScopedStruct
    {
        explicit MyScopedStruct(int p1) : m_Dummy(p1) { }
        operator int() const
        {
            return this->m_Dummy;
        }

        private:
            int m_Dummy;
    };

    ostream& operator< <(ostream& os, const MyScopedStruct& obj)
    {
        cout << "Received " <<  int(obj) << " in MyScope";
        return os;
    }
}

ostream& operator<<(ostream& os, const MyScope::MyScopedStruct& obj)
{
    cout << "Received " << int(obj) << " in global scope";
    return os;
}

int main()
{
    MyScope::MyScopedStruct myObj(100);
    cout << myObj;

    return 0;
}

לעיני רבים, הקוד אמור להתקמפל בהצלחה ללא שום בעיות. אך הפלא ופלא, ישנה שגיאת קומפילציה.
הקומפיילר יתריע כי כאן יש בעיית ambiguity הרי והוא לא יודע למי לפנות, operator< < בscope הגלובאלי, או שמא ל-operator<< בMyScope.
אז איך בכל זאת יתכן שגם אחרי שלא קראנו לusing בשום וריאציה שהיא (דחיפה של הnamespace או של רכיב ספציפי לתוך הscope הנוכחי) הקומפיילר בכל זאת מצליח לזהות?
קרא עוד »

בעיית יוספוס

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

בעיית יוספוס
בעיית יוספוס עבור n=7, האיש השביעי ניצל.
קרא עוד »

Cache, Multithreading וקצת על הפרוטוקול MESI

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

struct Widget
{
	int x, y;
} w;
const int k = 100000;
//thread 1
void t1()
{
	for(int i=0; i < k; ++i)
		++w.x;
}
// thread 2
void t2()
{
	for(int i=0; i < k; ++i)
		++w.y;
}

נאמר שיצרנו שני טרדים t1,t2 על סביבת multi-core. כל אחד מהטרדים מעדכן משתנה אחר. הכתובת של w.x שונה מהכתובת של w.y, לכן אין צורך בסנכרון.
האם אתה יכול למצוא בעיות במימוש המולטיטרדי הנ"ל?
קרא עוד »

האם כאשר רמת האבסטרקצייה גדלה הביצועיים יורדים?

טיעון נפוץ הוא שככל שאנחנו עולים ברמת האבסטרקציה של התוכנית, כך הביצועיים של התוכנית נעשים נמוכים יותר. בפוסט הזה תופתעו לגלות שלטיעון זה ישנם הסתייגויות.
ביצעתי השוואת ביצועיים בין הפונקציה qsort שבספריה cstdlib של c לבין הפונקציה sort של ++C מ-STL:

qsort:

int compare (const void * a, const void * b)
{
  return ( *(int*)a - *(int*)b );
}

int main()
{
	const int k = 100000000;
	int* values = new int[k];
	for(int i=0; i< k; i++) values[i] = i;
// measure the time
		boost::progress_timer t;
		qsort(values, k, sizeof(int), compare);
}

std::sort:

struct MyCompare
{
	int operator()(int a, int b) { return a < b; }
};

int main()
{
	const int k = 100000000;
	int* values = new int[k];
	for(int i=0; i< k; i++) values[i] = i;
// measure the time
		boost::progress_timer t;
		std::sort(values, values+k, MyCompare());
}

קרא עוד »

כיצד ניפטר מאובייקטים זמניים?

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

class A
{
private:
	int member;
public:
	A(int x=0): member(x) { cout << "Constructor" << endl; }
	~A() { cout << "Destructor" << endl; }
	A(const A& a) { cout << "Copy constructor" << endl; }
	friend const A getObj(int);
};

const A getObj(int x)
{
	A a;
	a.member = x;
	return a;
}

קרא עוד »