نکات جدیدی که از فصل ۱۴ دایتل یادگرفتم – فایل ها

این فصل دربارهٔ پردازش فایل ها یا همون File Processing بود. بیشتر نکاتش رو از قبل می‌دونستم و صرفا برام مرور بود.

کلاس های مورد استفاده‌مون

برای انجام پردازش فایلی باید هدر های iostream و fstream رو اینکلود کنیم. توی fstream این سه تا typedef وجود داره:

  • typedef basic_ifstream<char> ifstream
  • typedef basic_ofstream<char> ofstream
  • typedef basic_fstream<char> fstream

همونطور که می‌بینید این سه تا typdef برای نوع دادهٔ char ویژه سازی شده(specialized).

توی این کلاس ها اپراتور bool هم overload شده. به این معنی که میتونیم معتبر(valid) بودن یک شئ رو به این شکل بررسی کنیم:

fstream myFile("something.txt", ios::out);
if (myFile) {
// do somthing
}

باز کردن فایل ها

برای باز کردن فایل ها چند حالت(mode) وجود داره که هرکدوم دارای ویژگی های خاصی هستن. نحوه‌ی نوشتنش و اینکه هرکدومشون چه کاری انجام می‌دن پایین تر نوشته شده:

ofstream handler("filename", ios::out)

حالت های مختلف موجود برای باز کردن فایل

نکتهٔ دیگه اینکه میتونیم این حالت هارو به صورت همزمان باهم دیگه استفاده بکنیم. چطوری؟ هرکدومشون رو با علامت «|» (bitwise or) از هم جدا می‌کنیم:

ofstream handler("filename", ios::in | ios::out | ios::binary)

پیمایش در فایل

میتونیم به سی++ بگیم که از کجای فایل میخوایم شروع کنیم به نوشتن/خوندن. وقتی یک فایل رو(هم برای خواندن و هم نوشتن) باز می‌کنیم، دو اشاره گر ایجاد میشه که به ابتدای فایل(بایت شماره صفر) اشاره می‌کنن. یکی از این اشاره گر ها برای خوندن از فایل و دیگری برای نوشتن در فایل هست. برای تغییر مکان اشاره گرِ خوندن، از تابع seekg و برای تغییر مکان اشاگر نوشتن از تابع seekp استفاده می‌کنیم.

// position to the nth byte of fileObject (assumes ios::beg)
fileObject.seekg(n);
// position n bytes in fileObject
fileObject.seekg(n, ios::cur);
// position n bytes back from end of fileObject
fileObject.seekg(n, ios::end);
// position at end of fileObject
fileObject.seekg(0, ios::end);

خوندن و نوشتن در فایل

من خوندن و نوشتن رو از قبل بلد بودم پس اینجا فقط چیز هایی رو می‌نویسم که به نظرم جالب تر بودن،‌ ممکنه یادم برن یا برام جدید بودن.

فرض کنیم یه فایلی داریم که متن های داخلش به این فرمت هستند:

100 "some text" 200

همونطور که میدونیم، input stream ها اسپیس رو به عنوان کلمه‌ی کلیدی برای جدا کردن آرگومان ها در نظر میگیرن. بنابراین اگر به شکل معمول زیر اقدام به خواندن خط بالا بکنیم، به مشکل برخواهیم خورد.

InputStream >> number1 >> text >> number2;

یکی از راه های موجود برای حل این مشکل، استفاده از یک stream manipulator به اسم quoted هست که در هدر <iomanip> قرار داره. حالا کدی که نوشتیم رو تغییر میدیم تا به درستی ورودی هارو از هم تشخیص بده.

نکته: این ابزار مختص رشته هایی هست که داخل دو double quotation قرار داره.

InputStream >> number1 >> quoted(text) >> number2;

خوندن و نوشتن به صورت Random-Access

نمیخوام از مزیت های این نوع دسترسی به فایل صحبت بکنم چون احساس می‌کنم مطلبی نبوده که به عنوان یک چیز جدید توی این کتاب یاد گرفته‌م. به همین حد اکتفا می‌کنم که در اکثر موارد این روش بسیار سریع تر از روش Sequential Access هست.

در این روش که فایل رو در حالت باینری باز می‌کنیم، باید داده هامون رو به char * تبدیل کنیم.

اگر فرض بگیریم که int number{10};، برای نوشتن در فایل:

outFile.write(reinterpret_cast<const char*>(&number), sizeof(number));

و برای خوندن از فایل:

outFile.read(reinterpret_cast<char*>(&number), sizeof(number));

پارامتر اول تابع های read و write، داده ای هستن که میخوایم بنویسیم یا بخونیم و باید به صورت بایت به بایت خونده بشن. که برای اینکار، از char*استفاده می‌کنیم. پارامتر دوم همونطور که احتمالا فهمیدید، سایز داده ای هست که میخوایم بخونیم یا بنویسیم.

درباره انواع cast ها در سی++ احتمالا یه پست جدا می‌نویسم!

در آخر

برای صدمین بار رها کردن مطالعه، باز اومدم. اینبار هم مثل ۹۹ بار قبلی میگم که عزم بیشتری دارم 🙂

این فصل خیلی چیز جدیدی برام نداشت. فصل بعدی دربارهٔ کتابخانه استاندارد سی++‌ و container هاست.

چطور این ۲ هفته رو گذروندم

هوف! بالاخره پنل ادمین وبلاگمو باز کردم. بعد از آخرین پستم که تقریبا ۱۷ روز پیش بود حالا دوباره اومدم که فرآیند یادگیری‌ و مطالعه‌م رو شروع کنم. توی این تقریبا ۲ هفته کار زیادی نکردم. اولین برنامه‌ی اندرویدم رو نوشتم، چندتا فیلم دیدم و GTA V رو نصب کردم.

۵ تومن

داستان این برنامه برمیگرده به اونجا که شروع کردم در مقابل هرنوع کمکی که به دوستام میکردم، بهشون بگم ۵ تومن شد :))))

این داستان انقدر پیش رفت که تبدیل به تیکه کلام شد، یه سری واقعا این ۵ تومن هارو پرداخت کردن و دست آخر دیدم حسابشون داره از دستم در میره. خب چه چیزی بهتر از کامپیوتر برای نگهداری حسابا؟ ۵ تومن پول رایج مملکت مشوق من شد تا اولین برنامه‌ی خودم با کیوت بنویسم. و هیجان انگیز تر از اون؟ یه برنامه‌ی اندروید هم براش نوشتم! کیوت خیلی جالبه اما اگر تسلط کافی به خودِ سی++ و api های اندروید(یا هر بستر دیگه ای. مثلا iOS) ندارید و نقشه‌تون ایجاد یه برنامه‌ی مالتی پلتفرم نیست، بهتره که از زبان های native اون پلتفرم استفاده کنید.

این پروژه‌ی تمرینی چیز های جالب زیادی بهم یاد داد. مثلا اینکه ما داریم تو ایران زندگی می‌کنیم و واقعا بدبختیم. که ۲۵ دلار پول برای developer شدن توی گوگل پلی به پول ما خدا تومن میشه. که حتی همون خدا تومن رو هم نمیشه مثل آدم پرداخت کرد. بگذریم…

Cinema Paradiso

عجیبه که این فیلم رو انقدر دیر دیدم. درواقع پیش خودم فکر میکنم که چندتا فیلم/کتاب/موسیقی به این شکل زیبا توی جهان هست که من ازشون بی خبرم؟ و حتی تا آخر عمرم هم ازشون بهره ای نمیبرم. انگار واکنش من به لذت ها اینه. نوعی ناخوشی گُنگ حتی در خوشی هام هست.

GTA V

بازی GTA V توسط اپیک گیمز رایگان شد! البته، قبل از هرچیز باید بگم که لانچر اپیک گیمز یه آشغال به تمام معناست! نمیتونی دانلود/نصب برنامه رو متوقف کنی و بعدا ادامه بدی، انقدر تحریما رو سخت گرفته که حتما برای دانلودش هم باید فیلتر شکنت روشن باشه. د آخه مومن، پاچه خواری تا کجا؟ مگه فقط تو توی آمریکایی؟ میمیری مثل بقیه کمپانیا محدود کنی؟ بهرحال، لانچرش واقعا آشغاله.

و اما خود بازی. همون موقعی که تازه GTA V اومد همیشه دوست داشتم که روی سیستم خودم بازی کنمش. سیستم من ضعیف بود و هیچوقت نشد. کرک هاش درست کار نمیکردن و مشکلات دیگه. حتی روی پلتفرم های دیگه مثل کنسول هم شاید کلا ۲ بار بازی کردم.

اما بالاخره گرفتمش. و عجب بازی ایه! آهنگای خودم رو به بازی اضافه کردم و درحالی که داریوش داره میخونه مردم رو زیر میکنم.

در پایان

کلا اینکه این هفته هم مثل اکثر هفته های دیگه‌ی زندگیم آنچنان مفید نبود. اما حداقل لذت بخش بود. مفید نبود اما وقت تلف کردن هم نبود، چون وقتی که لذت بردی یعنی وقتت تلف نشده.

نکات جدیدی که از فصل ۱۲ سی++ دایتل یاد گرفتم

تقریبا یه هفته طول کشید تا پاراگراف آخر این پست رو بنویسم که درباره‌ی چند ریختی یا polymorphism توی سی++ عه. دلیلش هم همون بهونه‌ی قدیمیم، حال نداشتن، بود. احتمالا وقتی این متن رو میخونید متوجه میشید که به شدت با بی‌حوصلگی نوشته شده، مثال زیاد نزدم و کدی هم ننوشتم. دلیلش هم که خب واضحه.

چند ریختی به ما اجازه میده که برنامه های گسترده ای بنویسیم و درواقع بتونیم برنامه نویسی general داشته باشیم بجای اینکه مجبور بشیم به صورت خاص برنامه بنویسیم(الآن براش مثالی توی ذهنم ندارم که کوتاه باشه).

کلمه‌ی virtual

معمولا وقتی از طریق یه پوینتر توابع رو فراخوانی می‌کنیم، توابع مربوط به [جنس اون] پوینتر صدا زده میشن نه توابع مربوط به کلاسی که بهش اشاره میشه. یعنی اگه تابعی با دو نسخه(یکی توی کلاس والد و یکی توی کلاس فرزند) وجود داشته باشه، اون نسخه ای صدا زده میشه که مربوط به type پوینتره. virtual به وجود اومده تا راه حلی برای این مشکل باشه، برای اینکه موقع صدا زدن یه تابع از طریق پوینتر/رفرنس، برنامه چک میکنه که اگر کلاس فرزند نسخه‌ی خودش از اون تابع رو داره، همون رو صدا بزنه در غیر این صورت نسخه‌ی مربوط به کلاس والد رو صدا می‌زنه.

درواقع توابع ویرچوال ابن امکان رو فراهم میکنن که بجای فراخوانی تابع مربوط به هندل، تابع مربوط به کلاس اشاره شده فراخوانی بشه.

بهتره که برای خوانایی بهتر توی هر سطح که توابع virtual والد رو بازنویسی می‌کنیم، قید کنیم که این یه تابع virtual عه.

Dynamic Binding

همونطور که بالاتر گفتیم اگر یکی از توابع به شکل virtual تعریف شده باشه، و این تابع از طریق یه پوینتر/رفرنس فراخوانی بشه، برنامه خودش تابع مناسب رو بر اساس جنسِ شئ ای که داره بهش اشاره میشه انتخاب میکنه. به این فرآیند میگن dynamic binding.

کلمه‌ی override

وقتی یه تابعی از کلاس والد رو توی کلاس فرزند بازنویسی می‌کنیم، بهتره که از کلمه‌ی override براش استفاده بکنیم، با استفاده کردن این کلمه، کامپایلر چک میکنه که آیا تابعی با امضا(signature) مشابه توی کلاس(های) والد وجود داره که بخواد بازنویسی بشه یا نه.

ضرورت virtual کردن destructor

مهمه که اگر میخوایم به صورت چند ریختی از کدمون استفاده بکنیم، حتما destructor رو به صورت virtual تعریف کنیم.

دلیلش اینه که اگر یک اشاره گر از نوع کلاس والد که داره به یک شئ از کلاس فرزند اشاره میکنه توسط کلمه‌ی delete پاک بشه، تابع نابودکننده مربوط به خودش صدا زده بشه و نه تابع نابودکننده والد.

کلمه‌ی default

چیز جدیدی که به سی++ ۱۱ اضافه شده کلمه‌ی default هست. وقتی میخوایم یه کانستراکتور و یا دیستراکتور پیش فرض داشته باشیم دیگه نیاز نیست که یه تابع با بدنه‌ی خالی بنویسیم. فقط کافیه توی اعلان، اون تابع رو default کنیم.

کلمه‌ی final

وقتی از کلمه‌ی final برای یه تابع استفاده میشه، دیگه کامپایلر اجازه نمیده که اون تابع توی کلاس های فرزند بازنویسی و override بشه.

وقتی از کلمه‌ی final برای تعریف یه کلاس استفاده بشه، کامپایلر اجازه نمیده که کلاس های دیگه از این کلاس ارث بری داشته باشن.

کلاس Abstract

تا اینجا ما میتونستیم از کلاس هامون اشیاء ای رو بسازیم. اما یه سری کلاس های دیگه هم وجود دارن که نمیشه ازشون هیچ شئ ای ساخت که به این کلاس ها میگن Abstract. به کلاس های معمولی میگن concrete.

کلاسی رو میشه ابسترکت نامید که حداقل یکی از توابع virtual اش به صورت pure باشه.

برای اینکه یک تابع رو pure کنیم، باید اعلان تابع رو برابر 0 قرار بدیم که اون 0 نشان pure specifier هست. با pure اعلام کردن یک تابع دیگه نباید براش پیاده سازی ای نوشت بنابراین همه‌ی کلاس های فرزند باید توابع pure رو پیاده سازی کنن وگرنه خودشونم ابسترکت میشن.

چندریختی به صورت عمیق تر!

یه رفتار پلی مورفیک از سه سطح از پوینتر ها تشکیل شده.

مرحله اول، vtable

وقتی کاپایلر کلاسی رو که دارای توابع virtual عه رو کامپایل میکنه، برای اون کلاس یدونه Virtual function Table (vtable) میسازه. کار این جدول چیه؟ آدرس توابع virtual شده‌ی اون کلاس رو داخل خودش ذخیره می‌کنه. وقتی در زمان اجرا میخوایم با استفاده از dynamic binding یه تابع virtual رو فراخوانی کنیم، برنامه توی vtable کلاس مربوطه میگرده تا تابع درست رو پیدا و اجرا بکنه.

مرحله دوم

وقتی یه آبجکت از یک کلاس دارای توابع ویرچوال ساخته میشه، کامپایلر به اون آبجکت یه پوینتر اختصاص میده(این پوینتر رو معمولا در اول آبجکت میذاره) که اون پوینتر به vtable مربوط به اون کلاس اشاره می‌کنه.

مرحله سوم

پوینتریه که به خودِ شئ اشاره می‌کنه. به عنوان مثال ما یک وکتور از همه اشیاء از کلاس های مشتق شده از کلاس A داریم. پوینتر هایی که توی این وکتور هستند(که به اشیاء اشاره می‌کنن) به عنوان سطح سوم پوینتر ها محسوب میشن.

بنابراین برای اجرای یه تابع ویرچوال که به صورت dynamic binding میخواد صدا زده بشه، حداقل ۳ بار pointer dereferencing اتفاق میوفته که این موجب افزایش زمان اجرایی میشه. همچنین ذخیره کردن پوینتر مرحله ۲ و خودِ vtable هم باعث استفاده بیشتر از مموری میشه. اگر پرفورمنس و سرعت توی برنامه ای که داریم می‌نویسیم یک اصل بسیار مهم و سفت و سخته،‌ بهتره که از پلی مورفیسم استفاده نکنیم.

RunTime Type Information

تا اینجا وقتی به صورت پلی‌مورفیسم کار می‌کردیم، نیاز نبود بدونیم هر آبجکت دقیقا از چه نوعیه. اما ممکنه گاهی این نیاز رو پیدا بکنیم. با استفاده از قابلیت RTTI یا همون RunTime Type Information و قابلیت dynamic cast میتونیم در زمان اجرا بفهمیم که شئ ما از چه نوعیه و رفتار متناسب با خودش رو باهاش انجام بدیم. با استفاده از dymanic_cast میتونیم یه اشاره گر از جنس کلاس والد رو که داره به یکی از کلاس های فرزند اشاره می‌کنه به یک اشاره گر از نوع خودِ کلاس فرزند تغییر بدیم. فرقش با static_cast اینه که تایپ چک انجام میده و اگر کلاسی که داره بهش اشاره میشه از نوع کلاسی نباشه که میخواد بهش cast بشه، تبدیل انجام نمیشه.

همچنین با استفاده از typeid میتونیم در زمان اجرا بفهمیم که یه شئ از چه نوعیه. با استفاده از متود name اش میتونیم اسم جنسِ یه شئ رو به صورت یه رشته بگیریم.

در پایان

فصل بعدی توی I/O بیشتر عمیق میشیم.

یادگیری امروزم از معماری کامپیوتر(۴)

بعد از چندروز دعوا با سگ وحشی افسردگی دوباره امروز تونستم یکم به مطالعه‌م برسم. درواقع علاوه بر یادگیری، اینکه بخوام سریعتر توی وبلاگم دربارش بنویسم هم بهم انگیزه داد 🙂

درواقع این پست حاصل یادگیری امروزم نیست و تقریبا یه هفته‌ست که روی این بخش گیر کردم. گاهی وقتا آدم سنگین تر از اونه که بخواد کاری انجام بده. اما بهرحال اینجاییم و داریم پیش میریم، هرچقدر هم که بخواد کًند باشه 🙂

پًست امروز درباره‌ی Pipeline Processing عه و احتمالا پست بعدی هم درباره همین مورد باشه.

پردازش خط لوله‌ای(pipeline processing) یه تکنیکه که طی اون زیرعمل ها(sub operation) و یا بخش های مختلف از یک سیکل دستور، با همدیگه همپوشانی(overlap) پیدا میکنن.

این روش برای انجام کارها با تکرار زیاد بسیار مناسبه و نیازمند این هست که CPU چند فانکشنال یونیت مجزا داشته باشه که به صورت موازی کار انجام بدن.

دو بحش هست که پایپلاین داخلشون استفاده میشه یکیش arithmetic pipeline (عملیات های مختلف ریاضی رو به سگمنت های جدا تبدیل میکنه) و اون یکی هم instruction pipeline (بخش های مختلف مثل fetch و decode و execution رو به سگمنت های مختلف تبدیل میکنه)

معمولا بعد از هر سگمنت یک pipeline register وجود داره که نتیجه‌ی حاصل شده از کار سگمنت قبل از خودش ذخیره می‌کنه و در اختیار سگمنت بعدی میذاره.(درواقع بین دو سگمنت A و B یک رجیستر وجود داره که به عنوان خروجی A و ورودی B میتونه محسوب بشه)

Arithmetic Pipeline

معمولا این پایپلاین برای انجام عملیات ها روی اعداد اعشاری و ضرب برای اعداد غیر اعشاری انجام میشه. توضیحاتش به نظرم بدیهی اومد بنابراین نمی‌نویسمش.

Instruction Pipeline

همون طور که عمل پایپلاینینگ میتونه روی data stream ها انجام بشه، میتونه روی instruction stream هم انجام بشه. همونطور که میدونیم اجرای یه دستور از چند بخش تشکیل شده:

  • fetch
  • decode
  • محاسبه effective address
  • دریافت operand ها(fetch operand)
  • اجرا(execute)
  • write back

ما برای بررسی راحت تر چندتا از این بخش هارو باهم یکی می‌کنیم و فرض می‌کنیم مراحل اجرای یک دستور صرفا شامل مراحل زیر هست:

  • fetch (FI)
  • decode (DA)
  • fetch operand (FO)
  • execute (EX)

مثال ما ۴ استیج(سگمنت) هست اما توی کامپیوتر های دیگه تعداد استیج ها متفاوته(مثلا توی پنتیوم ۴ اینتل حدود ۳۰ تا استیج داریم).

توی جدول زیر میتونیم ببینیم که چطور بخش های مختلف باهم دیگه overlap پیدا می‌کنن. محور عمودی دستورات هستند و محور افقی کلاک ها هستن.

جدول زمانی یک instruction pipeline

مشکلات instruction pipeline

  • resource conflict ها
  • Data dependency ها
  • Branch difficulty ها

Resource Conflict

این مشکل زمانی رخ میده که دوتا سگمنت به صورت همزمان بخوان به مموری دسترسی داشته باشن. اینطور که کتاب گفته این مشکل معمولا با جدا کردن حافظه دیتا و اینستراکشن ها رفع میشه. اما خب این واسه کامپیوتر های خیلی قدیمیه، باید بگردم ببینم توی کامپیوتر های جدید این موضوع چطوریه.

Data Dependency

مشکل data dependency و address dependency که باعث کاهش پرفورمنس هم میشن، وقتی به وجود میان که یک سگمنت به یک داده و یا آدرسی نیاز داشته باشه که هنوز در دسترسش نیست. مثال: مثلا دستور A یک داده ای رو به عنوان خروجی تولید میکنه که این داده قراره به عنوان operand توی دستور B استفاده بشه. این دوتا دستور متوالی هستند، حالا قسمت Fetch operand(که میخواد اوپرند های دستور B رو لود کنه) تا زمانی که بخش Execute (که داره دستور A رو اجرا می‌کنه) تموم نشه نمیتونه داده های مورد نیاز خودش رو بدست بیاره. پس باید صبر کنه تا اجرای دستور قبلی به اتمام برسه تا کارش رو انجام بده. همین اتفاق برای آدرس ها هم میوفته. یعنی یک سگمنت به آدرسی نیاز داره که هنوز دستور قبلی تولیدش نکرده.

برای حل این مشکل معمولا سه تا راه وجود داره:

Hardware interlock

با اضافه کردن interlock، این مدار هروقت تشخیص بده که خروجی دستور فعلی قراره به عنوان ورودی دستور بعدی استفاده بشه، با استفاده از کلاک های اضافه تاخیر ایجاد میکنه تا اجرای دستور فعلی تموم بشه.

Operand forwarding

این راه حل اینطوریه که با استفاده از یه سری سخت افزارای خاص میان بین سگمنت های مختلف مسیر های جداگونه میسازن. یعنی چی؟ اینطوریه که به عنوان مثال اگر قرار باشه خروجی دستور A از ALU توی یک رجیستر خاص قرار بگیره، این مدار چک میکنه که آیا این رجیستر به عنوان ورودی دستور بعدی میخواد استفاده بشه؟ اگر جواب بله بود، به جای اینکه دیتا رو از ALU به رجیستر منتقل کنه و دستور بعدی با مراجعه به رجیستر اطلاعات موردنیاز خودش رو دریافت کنه، دیتا رو مستقیما به ورودی های ALU میفرسته تا برای دستور بعدی استفاده بشن.

Delayed load

توی این روش، حل این مشکل رو به عهده‌ی کامپایلری میذاریم که داره زبان سطح بالا رو به زبان سطح پایین تبدیل میکنه. درواقع کامپایلر وقتی data conflict رو میبینه، سعی میکنه با تغییر دادن ترتیب دستور ها(مثلا با اضافه کردن nop) به قدر کافی برای اجرا شدن دستور قبلی زمان ایجاد بکنه.

Branch difficulty

این مشکل درواقع یک چالش برای مدیریت کردن branch های رخ داده توی برنامه‌ست. همونطور که میدونیم branch ها جریان عادی دستورات برنامه رو میشکنن. چالش اینه که چطور conditional branch و unconditional branch رو مدیریت کنیم که کمترین ضرر رو به پرفورمنس بزنیم.

Prefetch target instruction

این یکی از راه حل های موجود برای مدیریت کردن conditional branch هاست. نحوه‌ی کارش اینطوریه که هردوحالت (اجرا شدن branch و اجرا نشدنش) رو درنظر میگیره. یعنی چی؟ یعنی شروع میکنه دستوراتی که در صورت وقوع branch قراره اجرا بشن و به صورت همزمان دستوراتی که در صورت انجام نشدن branch قراره اجرا بشن رو fetch می‌کنه. اگر شرط مربوط به branch درست بود، میره و دستورات branch رو(که قبلا fetch کرده) اجرا میکنه. در غیر این صورت دستورات مربوط به جریان عادی برنامه رو اجرا می‌کنه.

Branch Target Buffer(BTB)

BTB یه حافظه کوچیکه که توی استیج fetch قرار می‌گیره.کارش اینه که آدرس چند branch اجرا شده‌ی اخیر + آدرس target اونها + چندتا از instruction های بعد از target رو داخل خودش ذخیره میکنه. وقتی دستور توی پایپلاین رمزگشایی دیکد میشه، توی BTB میگرده که آیا آدرس این دستور(که یه branch هست) توی BTB وجود داره یا نه. اگه وجود داشته باشه، اون دستور و متعلقاتش(مثل آدرس تارگت و چندتا دستور اولِ اون branch) مستقیما در دسترس هستند و دستورات از مسیر جدید به سرعت fetch میشن. اگه آدرس جستجو شده توی BTB وجود نداشته باشه، این دستور به علاوه‌ی متعلقاتش توی BTB ذخیره میشن و پایپلاین خالی میشه تا مسیر جدید دستور ها توی پایپلاین قرار بگیره.

Loop buffer

یکی از انواع BTB عه که شامل یه رجیستر فایل بسیار سریعه و توسط بخش fetch مدیریت میشه. وقتی مشخص میشه قراره یه حلقه اجرا بشه، کل محتویات اون حلقه داخل این بافر ذخیره میشه و بنابراین برای اجرای دستورات داخل حلقه نیازی به رجوع به حافظه نیست.

دستورات داخل حلقه تا پایان اجرای حلقه داخل این بافر میمونن.

Branch Prediction

یکی از بحث های خفن معماری کامپیوتره که خیلی درباره‌ش بحث میشه. توی این کتاب تا اینجا توضیح مختصری داده اما با جستجویی که خودم کردم مثل اینکه موضوع خیلی بحث برانگیز و جالبیه.

branch prediction درواقع فرآیندیه که طی اون پایپلاین سعی میکنه قبل از اجرا شدن یه conditional branch حدس بزنه که آیا شرط (condition) صدق خواهد کرد یا نه. در صورتی که پیشبینی کنه این شرط درست خواهد شد، شروع میکنه دستوراتِ داخل branch رو fetch میکنه. اگر پیشبینی درست باشه،‌ دیگه وقفه ای در کار پایپلاین رخ نمی‌ده.

در آخر

برای اینکه این پست طولانی نشه، ادامه‌ی مطالب این پست رو توی پست بعدی می‌نویسم که اون هم درباره‌ی پردازش موازیه.

Pink Floyd – Keep Talking

دوم، دام دام، دوم، دام دام، دوم، دام دام، دوم، دام دام، دوم دام دام

(دوم، دام دام، دوم، دام دام، دوم، دام دام، دوم، دام دام، دوم دام دام)
There’s a silence surrounding me
I can’t seem to think straight
I sit in the corner
And no one can bother me

(دوم، دام دام، دوم، دام دام، دوم، دام دام، دوم، دام دام، دوم دام دام)
I think I should speak now (why won’t you talk to me?)
I can’t seem to speak now (you never talk to me)
My words won’t come out right (what are you thinking?)
I feel like I’m drowning (what are you feeling?)
I’m feeling weak now (why won’t you talk to me?)
But I can’t show my weakness (you never talk to me)
I sometimes wonder (what are you thinking?)
Where do we go from here (what are you feeling?)

اوووووووووووم آآآ آآآ اووووم

دیلیو دیلیو دیلیو. دیییییییی دیلیلیو دیلو دیلو. دییو دیو دیووووووووو. دیووو دی دی دی دیوو .دی دی دی دیییییی. دی دیدی دیو دیووووووووووو. دی دی دی دیووو. دی دیو دی دی دی دیوو یووو. چک کککککک دویییی دیوویو یویویوووو یو یو یو یو یووووووووووووووووو دیویویو یووووووووو یو یو یو، یوووووو یوآیو.

I feel like I’m drowning
(You never talk to me) you know I can’t breathe now
(What are you thinking?) We’re going nowhere
(What are you feeling?) We’re going nowhere
(Why won’t you talk to me?)

دیو دیو دیووویویویوووووو
(You never talk to me)
داویو یو یو آیو یو یوی
(What are you thinking?)
وا وا وا وا وا وا واوااااااااو
(Where do we go from here?)
دیو دیو دیووووووووووو

ووووآآاوووووووو. دوم، دام دام، دوم، دام دام، دوم، دام دام، دوم، دام دام، دوم دام دام دوم، دام دام، دوم، دام دام، دوم، دام دام، دوم، دام دام، دوم دام دام دوم، دام دام، دوم، دام دام، دوم، دام دام، دوم، دام دام، دوم دام دام دوم، دام دام، دوم، دام دام، دوم، دام دام، دوم، دام دام، دوم دام دام (دیم دیم دیم دیم) دوم، دام دام، دوم، دام دام، دوم، دام دام، دوم، دام دام، دوم دام دام (دیم دیم دیم دیم)

دوم، دام دام، دوم، دام دام، دوم، دام دام، دوم، دام دام، دوم دام دام (دیم دیم دیم دیم)

دوم، دام دام، دوم، دام دام، دوم، دام دام، دوم، دام دام، دوم دام دام (دیم دیم دیم دیم)

دوم، دام دام، دوم، دام دام، دوم، دام دام، دوم، دام دام، دوم دام دام (دیم دیم دیم دیم)

نکات جدیدی که از فصل ۱۱ سی++ دایتل یاد گرفتم

این فصل از دایتل درباره‌ی ویژگی ارث‌بری توی سی++ بود. نکاتی که برام جدیده رو توی این پست نوشتم.

تفاوت is-a relationship و has-a relationship

وقتی یک کلاسی(A) از یک کلاس دیگه(B) ارث بری می‌کنه، میگیم که رابطه‌شون از نوع is-a relationship هست. یعنی کلاس A نوعی از کلاس B عه؛ مثالش؟ «خودرو» و «وسیله نقلیه». خودرو از وسیله‌ی نقلیه مشتق شده(نمیدونم این تعبیرِ مشتق شدن درسته یا نه) و تمام ویژگی های یک وسیله‌ی نقلیه رو داره. درواقع، «خودرو یک وسیله‌ی نقلیه‌ست».

اما وقتی یک کلاس در data-member های خود یک شئ از یک کلاس دیگه رو داره(composition) میگیم رابطه‌شون has-a relationship هست. توی مثال خودرو، میشه فرمون خودرو رو مثال زد که خودش یک شئ هست و عضوی از خودرو محسوب میشه(نه نوعی از خودرو!).

Constructor

در حالت عادی، توابع سازنده(constructor)، نابودکننده(destructor) و اوپراتور تخصیص (operator= ) از کلاس والد ارث برده نمیشن.

وقتی یک شئ از کلاس فرزند(B) ساخته میشه اگه کلاس والد(A) دارای default constructor باشه، بدون اینکه کانستراکتور والد رو صراحتا صدا بزنیم، خودش به صورت ضمنی اجرا میشه. اما اگه کلاس A دارای default constructor نباشه باید حتما خودمون کانستراکتور کلاس A رو توی کانستراکتور کلاس B صدا بزنیم.

دسترسی به عناصر private والد

کلاس فرزند نمیتونه به طور مستقیم به عناصر private کلاس والد دسترسی داشته باشه. برای اینکه بشه اینکار رو انجام داد، اون عناصر مورد نظر توی کلاس والد باید به عنوان protected تعریف شده باشن یا اینکه کلاس فرزند از طریق توابع public کلاس والد اقدام به دسترسی گرفتن به عناصر پرایوت بکنه.

با استفاده از protected access specifier به دلیل اینکه میتونید به صورت مستقیم به عناصر مورد دسترسی داشته باشید، برنامه‌تون کمی سریعتر میشه. در مقابل استفاده از توابع public باعث میشه برنامه‌تون کمی کند تر باشه.

اما این روش(protected) مشکلاتی هم داره:

اول اینکه ممکنه داده های invalid بهشون تخصیص داده بشه. یعنی از اونجایی که دسترسی به صورت مستقیم و بدون اعتبارسنجی به اون‌ها وجود داره، ممکنه یک داده‌ی اشتباهی بهشون تخصیص داده بشه.

دوم اینکه برنامه رو شکننده (اصطلاحا fragile) میکنه. یعنی کلاس فرزند جای اینکه بر پایه خدماتی(service) باشه که کلاس والد ارائه میده، بر پایه‌ی پیاده سازی (implementation) کلاس والد توسعه پیدا میکنه. مشکلش چیه؟ از نظر مهندسی نرم افزار بهتره که تغییرات رو localize بکنیم. یعنی برنامه طوری نباشه که اگر اسم یکی از عناصر رو توی کلاس والد تغییر دادیم، مجبور بشیم تمام جاهایی از کلاس فرزندمون که به اون عضو رفرنس دادیم رو تغییر بدیم و استفاده از protected پتانسیل بوجود اومدن چنین مشکلی رو ایجاد می‌کنه.

مگه نگفتی استفاده از توابع public برنامه رو کندتر می‌کنه؟

چرا. اما بهتره که ما اصول مهندسی نرم افزار رو رعایت کنیم و بهینه کردن کد(optimization) توی این مورد رو به دست کامپایلر بسپاریم. چون خیلی وقتا کامپایلر خودش بهینه سازی های خوبی انجام میده(مثلا ممکنه توابع set و get رو به حالت inline دربیاره). به قول معروف، Do not second-guess the compiler .

فرق بین ارث بری public و بقیه

خود کتاب یه جدول خیلی خوب رسم کرده که من عکسش رو میذارم. به نظرم خوندن همون کافیه 🙂

جدولی که توضیح میده هرکدوم از انواع ارث بری public, private, protected چه ویژگی هایی دارن.

ارث بردن constructor

بالاتر گفتم که در حالت عادی کانستراکتور از کلاس والد به ارث برده نمیشه. اما توی C++ 11 ویژگی ای اضافه شده که اجازه میده کانستراکتور والد توسط کلاس فرزند به ارث برده بشه.

برای اینکار باید یه همچین چیزی توی تعریف کلاستون بنویسید:

using BaseClass :: BaseClass ;

که BaseClass اسم کلاس والد هست. با اینکار، کامپایلر به ازای کانستراکتور های موجود توی کلاس والد، یه کانستراکتور توی کلاس فرزند میسازه که به طور خودکار تابع متناظرش توی کلاس والد رو صدا بزنه و data-member های کلاس فرزند رو default initialize بکنه.

چندتا نکته برای این کار وجود داره:

  • کانستراکتور های default, copy, move ارث بری نمیشن.
  • اگر توی کلاس فرزند کانستراکتوری باشه که ورودی هاش با ورودی های یک کانستراکتور توی کلاس والد یکسان باشه، اون کانستراکتور به ارث برده نمیشه.
  • اگه کانستراکتور دارای default argument باشه، به همون صورت به ارث نمیرسه بلکه به صورت چند کانستراکتور overload شده به ارث میرسه.
  • اگه یه کانستراکتور توی کلاس والد حذف بشه، توی کلاس فرزند هم حذف میشه.

در آخر

فصل بعدی درباره‌ی چند ریختی یا همون polymorphism عه که دانش ابتدایی ما برای برنامه نویسی شئ‌گرا رو کامل میکنه.

یادگیری امروزم از معماری کامپیوتر موریس مانو (۳)

توی این پست راجع به تعاریف اولیه پردازش موازی (Parallel Processing) می‌نویسم.

پردازش موازی یعنی انجام چند کار همزمان به منظور افزایش سرعت اجرای برنامه. در حالت عادی، دستورات به صورت مرتب پشت سر هم اجرا می‌شدند اما در پردازش موازی، چند task به شکل همزمان انجام می‌شوند.

پردازش موازی سطوح مختلفی از پیچیدگی داره که ساده ترینش همون parallel load در رجیستر هاست. بخش های پیچیده تر شامل تعداد زیادی از واحد های عملیاتی (functional unit) میشه که هرکدومشون ممکنه یه کار خاصی رو انجام بدن. اطلاعاتی که قراره پردازش بشه بین این واحد ها پخش میشه و هرکدومشون به صورت همزمان روی اطلاعات پردازش انجام میدن.

اینجا واحد اجرا به چندین تا واحد عملیاتی تقسیم شده.

پردازش موازی به چندین طریق طبقه بندی میشه. یکی از طبقه بندی ها، طبقه بندی Flynn هست که بر اساس تعداد دستورات و دیتا هایی که به صورت همزمان پردازش میشن هست. به مجموعه‌ی دستور ها که از حافظه خونده میشن instruction stream گفته میشه و به پردازشی که بر روی دیتا ها انجام میشه data stream گفته میشه. پردازش موازی ممکنه روی یکی و یا هر دوی این موارد انجام بشه. بنابراین بر اساس این گفته ها، ۴ گروه اصلی بوجود میاد:

  • Single Instruction stream, Single Data stream (SISD)
  • Single Instruction stream, Multiple Data stream (SIMD)
  • Multiple Instruction stream, Single Data stream (MISD)
  • Multiple Instruction stream, Multiple Data stream (MIMD)

SISD

توی این کامپیوتر ها یک واحد پردازش،‌ یک حافظه و یک واحد کنترل وجود داره. ممکنه این کامپیوتر ها اصلا به طور درونی قابلیت پردازش موازی رو نداشته باشن. اما بهرحال پردازش موازی از طریق multiple functional unit ها و یا پردازش لوله ای (pipeline processing) خواهد بود.

SIMD

توی این سیستم ها چندین پردازشگر وجود داره و یک دستور به تمام پردازنده ها ارسال میشه و هر پردازنده بر روی یک بخشی از اطلاعات پردازش رو انجام میده و همه‌ی واحد های پردازشی تحت نظر یک واحد کنترل قرار دارن.

MISD

این مدل درواقع بیشتر تئوری هست و طبق گفته‌ی کتاب تابحال در عمل بکار نیومده

MIMD

این نوع از کامپیوتر ها قابلیت اینو دارن که چندین برنامه رو به صورت همزمان اجرا کنند. درواقع بیشتر کامپیوتر های امروزی که میبینیم از این نوع هستن.

توی پست بعدی درباره‌ی pipleline processing صحبت می‌کنم و نکته‌ش اینه که این روش توی مدل فلین جا نمیگیره 🙂

نکاتِ جدیدی که از فصل ۱۰ سی++ دایتل یادگرفتم

خب فصل ۱۰ ام از کتاب برنامه نویسی سی++ دایتل رو تموم کردم. اینجا خلاصه ای از چیزای جدیدی که یادگرفتم رو می‌نویسم.

string-object literal

توی سی++ ۱۴ میشه با اضافه کردن یک s به انتهای لیترال رشته‌ای‌مون(بعد از دابل کوتیشن، نه قبلش) اون رشته رو تبدیل به یه شئ از کلاس string کرد. مثال:

"Hello, World"s

Operator Overloading؛ به عنوان عضو کلاس یا نه؟

با اینکه من قبلا این بخش هارو توی کتاب C How To Program خونده بودم اما چون یادداشت نکرده بودم و خیلی وقت هم شده بود که حوصله‌ی برنامه نویسی رو نداشتم، یادم رفته بود.

مسئله اینه که چه وقتی باید تابع اوپراتوری که overload میشه رو جزئی از member function ها بذاریم و چه وقتی جزئی از non-member function. ساده‌ست، تنها زمانی میتونیم تابع اوپراتور رو به عنوان عضوی از کلاس قرار بدیم که شئِ کلاس ما به عنوان پارامتر در سمت چپ قرار بگیره. این کد رو ببینید:

istream& operator>>(istream& input, MyClass& object);

وقتی مینویسیم cin >> myobject درواقع انگار تابع رو به شکل operator>>(input, myobject) فراخوانی کردیم.

حالا از اونجایی که شئ ما توی پارامتر سمت چپ قرار نمیگیره، نمیتونیم این اوپراتور رو داخل کلاس overload کنیم و اگر اینکارو انجام دادیم، باید اینطوری بنویسیمش: myobject << cin

از طرف دیگه، برای اینکه بتونیم اوپراتور هامون رو به صورت commutative(جابجایی پذیر) تعریف کنیم، به دلایلی که بالاتر توضیح داده شد باید حداقل یکبار اوپراتورمون رو به عنوان non-member function تعریف کنیم.

نکته درباره‌ی Dynamic Allocation در عضو های کلاس

وقتی یک حافظه ای رو به صورت پویا برای یکی از اعضای کلاس‌مون (data-member) در نظر می‌گیریم باید حواسمون باشه که
Default memberwise assignment و Default copy constructor رو به حال خودشون رها نکنیم چون مشکلاتی مثل double free رو بوجود میارن. پس یادمون باشه که کپی کانستراکتور خودمون + علامت مساوی(=) خودمون رو تعریف کنیم.

حذف یه تابع از کلاس

یه تابع از کلاس (function member) رو میشه حذف کرد. قبلا برای اینکار، اون تابع رو جزء بخش private کلاس قرار می‌دادند اما الان به عنوان مثال میشه اینطوری نوشت:

const Array* operator=(const Array*) = delete;

کاربردش چیه؟ معمولا برای غیرفعال کردن توابعی که توسط کامپایلر به صورت خودکار ساخته میشن (auto generate)، مثل سازنده‌ی پیشفرض(Default constructor)، کپی کانستراکتور، اوپراتور تخصیص (=) و … استفاده می‌شن.

تعریف شئ از کلاس با initializer list

برای اینکه بتونیم یه شئ از کلاسمون رو با initializer list بسازیم، باید یه کانستراکتور داشته باشیم که ورودی‌ش از نوع initializer_listباشه. مثال:

MyClass::MyClass(initializer_list list);

تبدیل انواع مختلف به کلاس و بالعکس

ما میتونیم اشیاء ای که از کلاسمون میسازیم رو با استفاده از Conversion Constructor ها و Conversion Operator ها به انواع دیگه ای تبدیل کنیم.

برای اینکه یه کانستراکتور تبدیل کننده داشته باشیم نیاز داریم که کانستراکتور ما بتونه با یک آرگومان صدا زده بشه.(این مسئله برای کپی کانستراکتور ها صادق نیست) مثال :

MyClass:MyClass(int a);

حالا با استفاده از این تابع میشه هم به صورت ضمنی و هم به صورت مستقیم یک int رو به شئ ای از کلاس خودمون تبدیل کنیم.

برای تعریف یک Conversion Operator میتونیم اینطوری عمل کنیم:

MyClass::operator string() const;

حالا اگر فرض کنیم a اسم شئ ما باشه، وقتی بنویسیم static_cast<string> (a) درواقع کامپایلر تابع a.operator string() رو صدا میزنه.

چرا توابع سازنده‌مون رو به شکل explicit تعریف کنیم؟

توابع سازنده ای که میتونن فقط با یک آرگومان صدا زده بشن ممکنه توسط کامپایلر اشتباها به عنوان Conversion Constructor تلقی بشن(چون این تابع ها هم با یک ورودی صدا زده میشن) و به صورت ضمنی عمل cast رو انجام بدن. درکل ما برای اینکه از تبدیل ضمنی چه توی کانستراکتور(اونایی که میتونن با یه آرگومان صدا زده بشن) و چه توی اوپراتور های تبدیلی( conversion operator) جلوگیری کنیم، اون توابع رو به صورت explicit تعریف می‌کنیم.

فصل بعدی درباره‌ی ارث‌بری هست 🙂

یادگیری امروزم از معماری کامپیوتر موریس مانو (۲)

خب دیروز نشد که پست معماری کامپیوتر رو بنویسم. امروز سعی می‌کنم دوتا پست راجع به معماری کامپیوتر موریس مانو بنویسم.

این پست درباره‌ی تفاوت های ساده‌ی معماری سیسک و ریسک هست.

در حالت خیلی ساده به کامپیوتر هایی که تعداد زیادی دستورات پیچیده دارن میگن Complex Instruction Set Computer یا CISC.

ایده‌ی RISC از اینجا اومد که اوایل دهه هشتاد یه سری طراحای کامپیوتر اومدن گفتن بیاین کامپیوتری داشته باشیم که تعداد دستورات کمتر و با ساختار ساده تری داشته باشه. پس اسم این سری کامپیوتر هارو گذاشتن Reduced Instruction Set Computer یا RISC.

معماری CISC

یکی از اهداف این معماری، ساده کردن کامپایل از زبان های سطح بالا به زبان ماشینه. بنابراین این معماری تعداد دستورات زیادی داره.

درواقع سعی شده statement هایی که به زبان های سطح بالا نوشته میشن یه معادل مستقیم توی دستوراتِ این معماری داشته باشند.

یکی دیگه از ویژگی های این معماری، variable-length بودن قالب دستوراتشه. یعنی دستورات طول یکسانی ندارند(طول دستورات یعنی معادل باینری یک instruction)؛ یک دستور ممکنه ۵ بایت جا بگیره و یک دستور دیگه ممکنه ۲ بایت جا بگیره. بنابراین به عنوان مثال در یک سیستم با کلمه های ۳۲ بیتی (32bit words) یک دستور ممکنه نصف یک کلمه جا بگیره و دستور دیگه ممکنه یک کلمه به علاوه‌ی یک بایت از کلمه‌ی بعدی رو اشغال کنه. در این معماری یک بار باید عمل decode برای مشخص کردن طول دستور انجام بشه و یکبار هم برای پردازشِ خودِ دستور(مثل مشخص کردن opcode و …).

خلاصه ویژگی های این معماری به این صورته:

  • تعداد دستورات زیاد
  • مد های آدرس دهی بسیار وسیع
  • variable-length بودن دستور ها
  • امکان manipulate کردن مستقیم در مموری فراهمه

معماری RISC

این معماری بیشتر روی ساده تر بودن دستورات تاکید داره

ویژگی های این معماری به این صورته:

  • تعداد دستورات نسبتا کمی داره
  • تعداد مد های آدرس‌دهی نسبتا کمی داره
  • دستورات توی یک سیکل اجرا میشن
  • طول دستورات یکسانه
  • تعداد رجیسترهای بیشتری داره
  • مثل اینکه پایپلاین ها موثر تر هستن(چراش رو نمیدونم)

ثابت بودن طول دستور ها این امکان رو فراهم میکنه که کدگشایی دستورات خیلی راحت تر و سریعتر صورت بگیره و از اونجایی که دسترسی به حافظه محدوده، پردازش ها در رجیستر ها انجام میشن(که خیلی سریعتر از حافظه‌ست) و صرفا از حافظه برای دریافت operand ها و یا قرار دادن نتایج استفاده میشه.

در آخر

حجم کدها توی ریسک بیشتر از سیسکه، کمتر از حافظه و بیشتر از رجیستر ها استفاده میشه و مصرف حافظه کمتر و سرعت بالاتری داره.

چرا امروز نتونستم به کارهام برسم

چرا امروز(و روز های مشابه) نتونستم به کارهام برسم؟ چیزی که فکر میکنم vs چیزی که احساس می‌کنم.

احساس

امروز که بیدار شدم احساس کردم حس هیچکاری رو ندارم. نمیدونم، فقط احساس میکنم دلم میخواد استراحت کنم. چون خسته شدم از بس همش کار کردم و یا استرس داشتم که باید برای ادامه‌ی زندگیم چیکار کنم. و بخاطر این خستگی و احساس کردن هام، احساس سرخوردگی و شکست میکنم. حس میکنم یه لوزر به تمام معنام. کارایی که براشون برنامه ریزی کردم رو نتونستم درست انجام بدم. الان ساعت نزدیک ۷ غروبه و من حتی ۱ ساعت هم طبق برنامه پیش نرفتم. امروز مثلا باید پست دوم راجع به چیزایی که از معماری کامپیوتر یاد گرفتم رو مینوشتم. خلاصه‌ی فصل نمیدونم چندم از سی++ رو می‌نوشتم. اما جاش چیکار کردم؟ هیچی. توی خیالات گنگ خودم گشتم و راجع به گذشته ها فکر کردم. روی تخت دراز کشیدم و وقتمو تلف کردم. انگار یک نیروی سیاهی من رو از انجام کارهام بازمیداشت. و حالا که احساس سرخوردگی میکنم این نیرو بهم میگه که باید غصه بخورم و ناراحت باشم. برم توی تختم و بذارم افکارم بدنمو تحت کنترل دربیارن، بگیرن زیر مشت و لگد. عجیبه که ذهن کسی، بدنش رو سیاه و کبود کنه. ولی من اینطوری ام. سیاه و کبود نمیشم اما حس کوفتگی بدن بعد از کتک خوردن رو میشناسم. ازبس که ننوشتم قوه‌ی نوشتنم ضعیف شده. میدونم که قبلا خیلی بهتر میشنتم و الان دیگه هیچوقت نمیتونم مثل اونموقع باشم. چشمامو میبندمو و اجازه میدم که کلمه ها خودشون به دستم برسن اما بخاطر اینکه سرعت تایپ کردنم از سرعت فکر کردنم کمتره باعث میشه که از فکرم عقب بیوفتم و در این بین یک سری کلمات ازبین میرن و دیگه نمیشه فهمید که دقیقا به چی فکر میکردم. احساس میکنم که تلاش هایی که تا الان کردم بیهوده بوده. چون واقعا نتیجه ای دربر نداشته. اما شاید هم نه. نتیحه ها زود بدست نمیان. پس غصه میخورم که چرا انقدر دیر شروع کردم تا نتیجه ها بخوان دیر بدست بیان.گاهی وقتا از فرط احساس کمبود وقت و سرخوردگی و ناتوانی از انجام کارهام دلم میخواد دنیارو توی دستم بگیرم و به در و دیوار بکوبونم.آه. به همین دلایل هیچوقت درباره وضعیتم غر نزدم که چرا مقلان فلان نمره‌م کم شد و یا فلان کار رو دست انجام ندادم. چون دلیلش رو میدوم. تنبلی خودم. تلاش نکردن خودم، کافی نبودنم و اینکه همه چیزایی که سرم میاد نتیجه ی کار خودمه.

این حسیه که همیشه گریبان منو میگیره. بالاخره یک روز هم آدم خسته میشه. امروز من خسته‌م. اونقدری که فقط دلم میخواد استعمال دخانیات بکنم. کاریه که ضعیفا برای نجات میکن. فرار.

مسئله اینه که من اونقدر خفن، باهوش و یا نابفه نبودم که بشه گفت از فرط هوش و ذکاوت زیاد به این روز افتاده. که بشه گفت آره یه چیز عادیه، معمولا آدمایی که خیلی باهوشن به این احساسات دچار میشن و امثالهم. نه، من هیچکاری انجام ندادم. ۲۰ سالم شد و هیچ غلطی نکردم. کارهایی هم که قبلا انجام میدادم رو ول کردم و کارهایی که الان دارم انجام میدم در پایین ترین کیفیت خودش قرار داره.

چقدر چسناله. حالم بهم خورد.

فکر

فکرم چی میگه؟ بهم میگه که اینا فقط بخاطر تنبلیه و تنبلی هم از سر بی برنامگی رخ میده.من واقعا کاری نکردم، پس چرا میگم «ازبس که کار کردم»؟! مغز مزخرف آدم دوست داره که استراحت کنه. اگر برنامه نداشته باشی سخت میشه که متقاعدش کنی تا کاری رو انجام بده. من صبحا دیر بلند میشم چون شبا دیر میخوابم. اولین مشکلم همینه. آدم که دیر(و از همه مهم تر، نامنظم) بیدار بشه، زندگیش بهم میریزه. با اینکه ساعت ۷ غروب شده اما بازهم میتونم بخشی از کارهای عقب افتاده‌م رو جبران کنم. بجای غصه خوردن سعی میکنم با این نیروی تاریک(که خودمم) بجنگم. برنامه داشته باشم، به چیزای خوب فکر کنم و یادم باشه که چیزهای خوب زمان می‌برن. منم از اون استثنا ها نیستم که میانبر بزنم. الان شروع میکنم که معماری کامپیوتر رو بخونم.

در پایان

چیزی که اذیتم میکنه این «همیشه در حال جنگ بودن» عه. اینکه همیشه باید با خودم درحال مشاجره باشم. بین افسردگی و خوشحالی. بین استراحت و کار. بین منزوی بودن و ارتباط اجتماعی. بین زنده بودن و نبودن. و از خودم میپرسم آیا کسی هست که با این شدت درحال جنگ باشه؟