اینم یه تجربه پراکنده دیگه!

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

مشکل با متغیر محلی بزرگ

ما یه کد سی داشتیم که یه متغیر local با حجم حدودا ۱۰ کیلو بایت تعریف شده بود و مقدار دهی اولیه شده بود(عکس بود). همیشه کامپایلر به این قسمت از کد که میرسید کلی طول میکشید تا بتونه کد رو کامپایل کنه و همیشه اعصاب ما رو خورد میکرد. تا اینکه یه روز تصمیم گرفتیم مشکل رو حل کنیم. راه حل‌هایی که پیش رومون بود اینها:

  1. متغیر رو کلا حذف کنیم. که خب نمیخواستیم صورت مساله رو حذف کنیم
  2. متغیر رو global میکردیم که خب همه میتونستن تغییرش بدن و برامون بد بود
  3. متغیر رو static کنیم. این تفاوتی برای ما نداشت فقط میخواستیم امتحان کنیم.

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

بررسی چرایی راه‌حل

سوالی که برای ما پیش اومد این بود که چرا یه تغییر اینجوری اینهمه هم توی سرعت هم حجم باینری موثره؟ برای جواب دادن به این ماجرا یکم رفتم تحقیق کردم:

  • همه باینری‌ها از بخش‌های مختلفی تشکلی شدن که اگه assembly یادتون باشه شامل اینهاست
  • متوجه شدم که متغیرهای global توی data segment تعریف میشن و متغیرهای local توی stack segment تعریف میشن.
  • متوجه شدم که متغیرهای static هم توی data segment تعریف میشن.

پس هرچی بود زیر سر data segment بود از اینجا به بعدش بازی شد حدس و گمان در مورد چرایی دوتا اتفاق:
– چرا سرعت کامپیال بیشتر؟ احتمالا به این دلیل که کامپایلر با data segment راحت‌تر کار میکنه تا stack segment. اما این حدس حجم باینری کمتر رو توجیه نمیکنه چون در این دوحالت حجم متغیر ثابت مونده
– چرا حجم باینری کمتر؟ برای اینکار یکم باید یادمون بیاد توی assembly که چطور یه متغیر local تعریف میشه منظورم هست. یه متغیر local عملا اینه که اول یه مقداری توی stack قرار میگیره و در یه مرحله دیگه اون مقدار از stack خارج میشه. که این هم نیاز به کد سمت code segment داره هم نیاز به حافظه سمت stack segment داره.

خب با این حالت ما به حدس دقیق تری از اتفاق پشت پرده رسیدیم. اونم اینه که وقتی متغیر توی data segment قرار میگیره یه بخش توسط کامپایلر تخصیص داده میشه و مقادیر هم توی اون خونه‌ها نوشته میشه. ولی وقتی قرار باشه اون متغیر توی stack segment قرار بگیره هم کد تولید میشه هم حافظه تخصیص داده میشه که منجر به طولانی تر شدن کل روند میشه.

امیدوارم به دردتون خورده باشه
همین!

اینم یه تجربه پراکنده دیگه!

چند شب پیش یکی از دوستان با این سوال به سراغ من اومد:

مشکل اینه که وقتی با malloc یا new یک حافظه تخصیص میدی و با free یا delete حافظه رو برمیگیردونی, اون حافظه شاید بلاک هاش خالی بشه اما توی تسک لیست خالی به عنوان منابع استفاده شده میاره و تا پایان پروسه پاک نمیشه. از دوستانی که آشنایی دارن پیگیری کن ببین جریان چیه. مهمه .

خدا خیرت بده. یه جایی از حافظه نمایی رشد میکه و وقتی خالیش میکنیم (با اینکه از لیست heap سیستم عامل خالی میشه) اما توی لیست پروسه‌ها(مثل نتیجه ps) هنوز به عنوان حافظه استفاده شده هست و حافظه آزاد نمیشه !!!‌ عجیبه . کلی توی اینترنت اینو سوال کردن !!

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

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

پیش‌نیازها

پیش نیازهایی که به نظرم باید بدونیم ایناست:

  • نکته صفر اینکه با توجه به تحقیقات من زیر کلمه کلیدی new از همون malloc استفاده میشه. پس بررسی malloc به تنهایی میتونه جوابی برای ما فراهم کنه.
  • اول اینکه باید بدونیم که وقتی malloc صدا زده میشه چه اتفاقی میفته. کاری که malloc میکنه اینه که heap پروسه رو بهت اختصاص میده. حالا heap توسط سیستم عامل مدیریت میشه و در صورت نیاز به پروسه‌ی شما حافظه بیشتری تخصیص میده.
  • دوم اینکه فضای heap توسط کرنل مدیریت میشه و به اصطلاح نگاشته میشه به یه چیزی به نام Virtual Memory
  • سوم اینکهVirtual Memory نگاشته میشن به چیزی به نام Memory Page که نزدیک به سخت افزار تره
  • چهارم اینکه Memory Page نگاشته میشن بلوک‌های واقعی حافظه. از این مطمئن نیستم اما این نزدکترین لایه به سخت افزاره که من باهاش آشنا هستم.

بررسی مساله

حالا اگه بخوایم مساله رو بررسی کنیم باید به چند‌تا سوال جواب بدیم:

  • اول اینکه malloc چطور عمل میکنه؟ در پیاده‌سازیش ساز و کارش چیه؟
  • دوم اینکه روند افزایش طول Virtual Memory و اضافه شدن Memory Pageها چطوره؟
  • سوم اینکه بعد از پس دادن Memory Pageها سیستم عامل چطور اونها رو از یک پروسه پس میگیره؟

خب جواب سوالا به ترتیب اینا به نظر میرسن:

  • پیاده‌سازی‌های متفاوتی از malloc وجود داره
    • پیاده‌سازی معمولا اون اینطوریه که پس از گرفتن حافظه وقتی free شد معمولا اونها رو به سیستم عامل پس نمیده و نگه میداره که شاید بعدا بخواد دوباره تخصیص بده.
    • دوم اینکه درخواست یک Memory Page جدید توسط دستوراتی مثل mmap و sbrk درخواست یک بلاک بزرگ حافظه میکنه و اون رو بنا به درخواست نرم‌افزار تخصیص میده.
    • بعدش هم اینکه برای اینکه یک Memory Page رو برگردونه به سیستم عامل بایستی تمام حافظه‌هایی که روی اون تخصیص داده شدن برگشت داده بشه. که این هم همیشه اتفاق نمی‌افته.
    • معمولا هم پیاده‌سازی‌ها اینجورین که حافظه‌ای که توسط sbrk افزایش پیدا کرده رو دست نمیزنن(دلیل دقیقش رو نمیدونم) و حافظه‌ای که توسط mmap اضافه شده رو به راحتی برمیگردونن.
  • تقریبا جواب سوال دوم رو هم تا الان دادم. مقدار Virtual Memory توسط glibc کنترل میشه و بوسیله malloc تخصیص داده میشه.
  • جواب سوال سوم هم اینه که کرنل لینوکس همیشه حافظه برگشت داده شده رو برنمیگردونه چون که هزینه داره فقط اون رو علامت میزنه که در صورت نیاز اون رو حذف کنه یا به swap ببره.

راه حل مساله

حالا بریم سراغ مساله اصلی(چرا حافظه برگشت داده شده برگشت نخورده) و توجیهی که براش پیدا کردیم:
* مهمترین دلیل این اتفاق به نظرم malloc هست که حافظه رو نگه داشته و برنگردونده
* دومین دلیلش هم میتونه این باشه که کرنل هنوز Memory Page رو برنداشته و فقط علامت زده

حالا راه حل چیه:
* اول اینکه به کرنل و glibc اعتماد کنیم
* دوم اینکه بیایم و این پارامتر مروبط به malloc رو تنظیم کنیم۴
* سوم اینکه بیایم از یه پیاده‌سازی دیگه برای تخصیص حافظه و آزاد سازی اون استفاده کنیم
* چهارم اینکه بیایم خودمون مستقم از mmap و munmap استفاده کنیم

امیدوارم اینا راه گشای دیگران هم باشه

برای مطالعه بیشتر:

۱

۲

۳

۴

همین!

اینم یه تجربه پراکنده دیگه!

به نظر میرسه که من در توییتر گفتم که میخوام در مورد کلا async و روندهای پیاده سازی اون توی وبلاگ بنویسم.

از اونجایی هم که من هنوز هم علم رو دوست دارم رفتم یکم عمیق تر نگاه کردم ببینم کل این ماجرا از کجا آب میخوره رسیدم به چیزی به نام process calculus یا همون حسابان پروسه‌ها! بالاخره علما آدم‌های پایه‌ای هستن که میان و خیلی عمیق و گاها عجیب به مسائل نگاه میکنن. کل ماجرا از این قراره که در این علم تلاش شده که بصورت formal سیستم های همروند یا concurent توصیف بشن و روابط بین پروسه‌ها مختلف در این سیستم‌ها نحوه تعاملشون رو توصیف و تفصیل بشه.

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

حسابان پروسه‌ها یا process calculi

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

سه ویژگی اصلی هست که توی تمام این روش‌های مدل سازی اینه که:

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

همه این مدلهاهم توصیفی از کانال ارتباطی ارائه می‌کنند که ابزاری برای ارتباط به حساب میاند. هم چنین عملگرهایی برای بوجود آوردن پروسه‌های جدید از پروسه‌های قدیمی مورد نیاز هست که معمولا این عملگرها عبارتند از:
– ترکیب موازی: این عملگر باعث می‌شه که دو پروسه بتونن بصورت موازی و غیر وابسته اجرا بشن. همچنین این پروسه‌ها به کمک یک کانال مشترک با هم تعامل دارند. کانالها بصورت همزمان یا غیرهمزمان هستند.
– ارتباطات: تعاملات معمولا یک جریان جهت‌دار اطلاعات هست. معمولا هم بین ورودی‌ها و خروجی‌ها تفاوت آشکاری قائل شده اند. معمولا هم جریان داده‌ها از خروجی‌ها به سمت وردی‌هاست از طریق یک کانال است.
– ترکیب ترتیبی: بعضی وقت‌ها تعاملات میان پروسه‌ها بایستی از نظر زمانی مرتب باشند. معمولا عملگر ترتیب به ورودی‌ها یا خروجی‌ها یا هردو اعمال می‌شود.
– قوانین تبدیل: معمولا قوانینی برای تبدیل یک عبارت که در آن عملگرهای بالا استفاده شده اند به یک عبارت معادل در یک حسابان پروسه تعریف می‌شود.
– مخفی سازی: حقیقتا خودم خیلی متوجه نشدم. اما به نظر میرسه در مورد این باشه که پروسه‌های نمیدونن که با چند نفر تعامل دارن اما نقاط تعامل میدونن که چند نفر با هم در تعاملن.
– بازگشت و تکرار: برای تعریف روندهای نامتناهی با مجموعه ای از قوانین متناهی به بازگشت و تکرار نیاز هست.
– پروسه null: یک پروسه که هیچ کاری نمی‌کنه و با هیچ‌کس تعاملی نداره ولی در روند استدلال مورد نیازه.

شاخه‌های اصلی

شاخه‌های اصلی این علم سه دسته هستند که عبارتند از موارد زیر. این موارد رو تنها برای اونهایی که دوست دارن بیشتر بدونن آوردم
– حسابان سیستم‌های در ارتباط یا Calculus of Communicating Systems(CCS)
– ارتباط سیستم‌های ترتیبی یا Communicating Sequential Processes (CSP)
– جبر پروسه‌های در ارتباط یا Algebra of Communicating Processes (ACP)

منابع و مطلاعات بیشتر

من بیشتر مطالب رو مبتنی بر اونچه که توی ویکیپیدیا خوندم آوردم ولی پیشنهاد میکنم اگه این موضوع علاقه‌تون رو جلب کرد موارد زیر رو هم نگاهی بندازید:
یه صفحه که چندتا منبع رو معرفی میکنه
یه سری اسلاید که روش CCS رو یاد میده
یه مقدمه خوب و علمی که کلا ماجرا رو در ۳۵ صفحه مرور میکنه
یه صفحه دیگه که منابع مخلتفی رو معرفی میکنه.

اینم یه تجربه پراکنده دیگه!

خب من به مسائل مرتبط با کارایی و اجرای غیر همزمان کدها یا همون async رو دوست دارم. یکم هم تجربه کد نویسی رو باهاشون دارم. توی این پست میخوام بصورت ساده یکی از روشهای این پیاده‌سازی رو براتون توضیح بدم. خیلی دنبال این نیستم که بگم دقیقا این روش کجاها به درد میخوره و چرا به درد میخوره بلکه هدف یه معرفی اولیه است. انشالا در یه پست مفصل بیشتر توضیح میدم. کل موضوع حول دوتا واژه promise و future میگرده که در c++0x11 معرفی شده.

داستان از این قراره که در مدل sync شما یه تابع رو صدا میزنی و منتظر میمونی که جواب به شما برگرده. اما در این مدل بعد از صدا زدن یک تابع بجای منتظر موندن برای جواب همون موقع یک مقدار از نوع future برمیگرده که تا زمانی که تابع صدا زده شده به اتمام نرسیده مقداری نداره. بعد از تموم شدن تابع مقدار future همون مقداری هست که نتیجه محاسباته. حالا اگه از منظر تابعی که صدا زده میشه نگاه کنیم اون تابع خروجیش از نوع promise هست که بعد از اتمام اون رو پر میکنه و برمیگردونه. همین!

حالا این نمونه کد رو هم ببنید که یه مثال ساده از future و promise هست.

مثالها از اینجا گرفته شده. اولین مثال از این قرار که ما یه تابع که خروجیش void هست رو با استفاده future مورد استفاده قرار می‌دیم.

اتفاقی که اینجا میفته اینه که با استفاده از ‍std::async ما سعی میکنیم که اون تابع رو در یک thread دیگه اجرا کنیم و نتیجه رو بصورت یک future از اون بگیریم. با اجرای result.get() مطمئن می‌شیم که اجرای اون thread به اتمام رسیده.

مثال دوم یه محاسبه بصورت غیر همزمان اجرا میشه و نتیجش به کمک future برگردونده میشه

امیدوارم که این بدردتون خورده باشه.
همین!

خب اینم یه تجربه پراکنده دیگه!

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

یکی از ویژگی‌هایی که go وجود داره اینه که خود زبان بصورت توکاری شده از یه مفومی به نام goroutine ها پشتیبانی می‌کنه. goroutine ها یه ابزاری برای ایجاد همروندی در برنامه است و تقریبا نحوه استفاده از اونها شبیه جاهایی است که ما از Thread ها استفاده می‌کنیم. یعنی هر جایی که بخوایم عملیاتی رو که به هر دلیل بلوکه میشه رو انجام بدیم و نمیخوایم اصل برنامه گیر کنه ازش استفاده می‌کنیم. واسه من این سوال بوجود اومد که خب اگه اینقدر این دوتا شبیه هستن چرا اسمشون شده دوتا و این جا بود که تحقیقات شروع شد:

اول از همه بگذارید یکم بگم Thread یا goroutine چیه:

  • خب Thread و goroutine ها هر کدام Stack متفاوتی دارند. یعنی متغیرهای محل مختص به خودشون رو دارن!
  • همه Thread ها و goroutineها بخش Data Segment یکسانی دسترسی دارند. یعنی متغیرهای عمومی برنامه رو می‌بینند.
  • همه Threadها و goroutineها بخش Code Segment یکسانی دسترسی دارند. یعنی توابع کل برنامه رو میبینن
  • همه Threadها و goroutineها بخش Heap Segment یکسانی دسترسی دارند. یعنی به شیٔ هایی که در برنامه new شدن دسترسی دارن

حالا نوبت تفاوتهاست:

  • اولین تفاوت در سایز stack بود.
    • یعنی اینکه به ازای هر Thread جدید سیستم عامل نزدیک به ۲ مگ و دیده شده تا ۸ مگ حافظه به stack اون Thread اختصاص میده و این درحالیه که هر goroutine در یسری نسخه‌ها ۴ کیلو بایت و در یسری نسخه جدید تر ۸ کیلو بایت حافظه اختصاص میده
    • سایز stack در Threadها در طول اجرا تغییر نمیکنه یعنی این سایز همین مقدار میمونه ولی سایز stack در goroutineها بر اساس نیاز زیاد میشه.
    • نتیجه تفاوت حجم هم اینها هستن
      • همین تفاوت حجم باعث میشه که وقتی یک گیگ مموری داریم میتونیم بین ۱۲۸ تا ۵۱۲ Thread بسازیم ولی میتونیم حدودا ۱۲۸۰۰۰ تا ۲۵۶۰۰۰ goroutine بسازیم.
      • همین تفاوت حجم باعث میشه ساختن و پاک کردن goroutine ها بسیار کم هزینه تر از Thread ها باشه
  • دوم تفاوت هم نحوه زمانبندی برای اجراست:
    • threadها توسط سیستم عامل زمانبندی شده و اجرا میشن که با توجه به حجم stack معمولا کار پر هزینه‌ای است
    • goroutine ها در محیطی به نام go runtime هست که کل کدها توی اون اجرا میشن. یعنی اینکه go runtime مسئول لود کرد حافظه مورد نیاز برای goroutineها رو بر عهده داره. همچنین زمانبندی اجرا هم توسط همین محیط انجام میشه. این محیط خودش از یک یا چند Thread تشکیل شده که goroutine ها رو اجرا میکنن و عین اینه که شما یه سری کار رو بصورت اتوماتیک به یک Thread pool داده باشی. به همین خاطر کل این روند بسیار از نظر برنامه نویس ساده شده. همچنین وجود این go runtime این امکان رو بوجود آورده که از الگوریتم‌های زمانبندی بهینه تری برای این زبان استفاده بشه که خودش افزایش کارایی رو نتیجه میده. البته این روش معایبی هم داره که اگر عمری بود در موردش مینویسم.
  • سومین تفاوت هزینه بازگذاری و خروج از مموری:
    • برای اجرای Thread ها تقریبا تمام رجیسترهای سی پی یو (شامل  ۱۶ رجیستر عمومی، PC، SP، رجیسترهای سگمنت و …) تغییر میکند. که در مواردی که تغییر بین Threadها زیاد است هزینه قابل توجهی است.
    • در goroutine ها تنها سه رجیستر(SP, PC, DX) تغییر میکند.

فعلا همین!

اینم یه تجربه پراکنده دیگه!

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

خب اول از همه ویندوز، iOS و آندروید هر سه در سطح سیستم عامل این عملیات رو پشتیبانی میکنن. بدین صورت که اگه شما بخوای اطلاعاتی رو به موبایل push کنی بایستی تقریبا مراحل زیر رو طی کنی:

  1. نرم افزاری رو که میخوای براش اطلاعات بفرستی به فراهم کننده این سرویس معرفی کنی و ازش یه شبه اکانت بگیری
  2. توی نرم افزار موبایل کارها مرتبط با پیدا سازی این سرویس رو انجام بدی
  3. از سمت سرور به سرورهای فراهم کننده وصل بشی و بدونی چه کلاینت هایی داری و به اون کلاینت‌ها اطلاعات رو push کنی.

خب دونستن این اطلاعات خیلی به من برای رسیدن به هدفم کمک نمی‌کرد. پس رفتم سراغ یه جای دیگه. ما راه‌حل‌هایی داریم به اسم Backend as a Service که اونها اداعا میکنن که سرور push notification دارن. بعد از بررسی و خوندن مستندات و یکم دست و پنجه نرم کردن با این راه‌حل‌ها فهمیدم که کارکردن و سرو کله زدن سرورهای فراهم کننده خیلی راحت و تو دل برو نیست و برای پیاده سازی باید کلی نحوه ارسال این اطلاعات مدیریت بشه. سرور push notification توی این راه‌حل‌ها سعی میکردن این مدیریت رو انجام بدن. این اطلاعات هم خوب بود اما خیلی به درد نمی‌خورد.

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

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

فعلا همین!

پ.ن. این پست یه قسمت دیگه هم خواهد داشت که از منظر پیاده‌سازی به این موضوع نگاه میکنه.

 

 

اینم یه تجربه پراکنده دیگه!

همه اونهایی که با برنامه‌نویسی شبکه آشنا هستند می‌دونند که ما در لایه انتقال از دو تکنولوژی TCP و UDP استفاده می‌کنیم. مهمترین تفاوتی که این دوتا پروتکل دارند که تقریبا همه باهاش آشنا هستند اینه که TCP مبتنی بر اتصال هست و رسیدن پکت‌ها و ترتیب پکت‌ها رو تضمین میکنه اما UDP بدون اتصال هست و رسیدن و ترتیب پکت‌ها رو تضمین نمیکنه.

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

tcppacket

udppacket

 

همینطور که می‌بینید  توی TCP قبل از بخش دیتا تقریبا ۱۹۲ بیت هدر وجود داره و این هدر توی UDP تنها ۶۴ بیت! که توی پهنای بایت در حد ۱۰ کیلو بیت بر ثانیه تفاوت واضحی از خودش نشون میده.

همین!

 

این یه تجربه پراکنده دیگه!

سیستم فایل‌ها ویژگی‌ها و امکاناتی دارند که اگه به درستی استفاده بشن می‌تونن کارایی کل سیستم رو بالا ببرن. مثلا من خودم امتحان کردم و روی فلشم از سیستم فایل btrfs استفاده کردم و دیدم که سرعت نوشتن و خوندن از روش به طرز چشمگیری اضافه شده. یکی از ویژگی‌های سیستم فایل‌های مدرن مثل zfs و همین btrfs امکان copy on write هست. امروز سعی میکنم یکم در مورد این ویژگی توضیح بدم.

اولا احتمالا همه میدونن که هر سیستم فایل یک جدول داره که توی اون نگه داشته میشه که توی چه بلوکی چه اطلاعاتی ذخیره میشه. این اطلاعات در هر سیستم فایل متفاوته. اما در سیستم فایل‌های دارای ویژگی CoW مثل تمام فایل سیستم‌های یک فایل جدید روی یه سری بلاک نوشته میشه. اما وقت ویرایش فایل بجای نوشتن بلاک تغییر یافته سر جای بلاک قبلی این بلاک در یک جای جدید نوشته میشه. یعنی یه جوری هم نسخه قبلی وجود داره هم نسخه جدید. اگه دوستانی باشن که از امکان TimeMachine توی مک استفاده کرده باشن دقیقا از همین روش برای نگهداری تاریخچه فایل‌ها استفاده میکنه. در صورتی هم که بلاک‌های سیستم کاملا پر بشن قدیمی‌ترین بلاک‌ها بازنویسی می‌شن. حالا این ویژگی چه امکاناتی رو فراهم میکنه:

  • امکان گرفتن snapshot: یعنی شما به سیستم فایل میگی این قطعه از تاریخچه رو نگهدار و به هیچ وجه از بین نبرش حتی اگه جا کم داشته باشی
  • استفاده در مموری مجازی سیستم عامل: وقتی یه پروسس کپی خودش رومیسازه نیاز به کپی کردن تمام حافظه نیست. تنها بخش‌هایی که تغییر میکنن توی یه بخش جدید حافظه ایجاد میشه.
  • در سیستم فایل‌های مرتبط با ماشین‌های مجازی از این تکنولوژی استفاده میشه. یعنی شما هارد ماشین مجازی رو میسازید و بهش فضا اختصاص نمی‌دید و هنگام نیاز اون فضا اختصاص داده میشه
  • در این سیستم ‌های انتقال backup ها بصورت incremental امکان پذیره و شما فقط تغییرات رو نسبت به قبل انتقال میدید.

لیست فایل سیستم‌هایی که من میشناسم و. از این ویژگی پشتیبانی می‌کنن اینا هستن

امیدوارم به درد کسی بخوره

همین!

اینم یه تجربه پراکنده دیگه!

امروز داشتم توی وب میگشتم و در مورد docker میخوندم که چشمم به این پست خورد که چطور توی pantheon تونستن با استفاده از تکنولوژی‌های مرتبط به container تونستن یه بیزنس بسیار جذاب برای ارائه یه نوع هاستینگ خاص مرتبط با drupal و wordpress بسازن. اما نکته‌ای که داشت این بود که یکی از نکاتی که منجر به خفن شدن سیستم اونها شده بود استفاده از یه ویژگی systemd به نام socket activation بود. که من یکم در موردش خوندم و بسیار ازش لذت بردم و گفتم ازش بنویسم.

قصه از اینجا شروع میشه که توی سیستم‌های مبتنی بر یونیکس از قدیم الایام یه چیزی وجود داشته با نام SysV که قدمهای مرتبط با boot سیستم توی اونجا انجام میشده. یعنی اینکه فرض کنید برای اینکه لینوکس بطور کامل بالا بیاد نیاز به داره که ۱۰ تا سرویس اجرا بشه و هر کدوم از اونها یه سری وابستگی دارن که این سیستم این قدم‌ها رو با توجه به وابستگی‌هاش اجرا میکنه. نحوه اجرا شدن این قدم‌ها توی SysV بصورت ترتیبی است که این روند باعث افزایش زمان مورد نیاز برای boot شدن سیستم میشه. توی MacOs از سیستم به نام launchd برای کاهش زمان boot پیاده‌سازی شده. همچنین توی لینوکس  راه حل‌هایی برای اجرای موازی سرویسهایی که به همدیگه وابسته نیستن مثل upstart اوبونتو توسعه پیدا کرده. اما مشکل سرعت کم boot بصورت کامل رفع نشده. به همین خاطر systemd پیاده‌سازی شده و از ساز و کاری به اسم socket activation ایجاد شده که سرویس‌ها هم بتونن با سرعت لود بشن هم مشکلی بوجود نیاد.

تقریبا همه چیز در لینوکس معمولا این سرویسها ارتباطشون از طریق socket ها ایجاد میشه و از socket به عنوان IPC استفاده میشه. حالا ایده اینه که بجای اینکه کل پروسس مربوط به یک سرویس بالا بیاد و بعد اون بیاد و سرویس رو ایجاد کنه، systemd بجای پروسه اصلی سوکت رو ایجاد کنه و هر وقت اولین درخواست به این سوکت رسید سرویس اجرا بشه و سوکت باز شده هم در اختیار پروسس این سرویس قرار بگیره. خوبی این روش هم اینه که تقریبا تمام سرویس‌های متونن همزمان اجرا بشن و خب در صورتی که یه سرویس زودتر از یه سرویس دیگه اجرا بشه و به سرویس کند نیاز داشته باشه درخواست‌هاش رو به سوکت مربوطه میفرسته تا زمانی که این سوکت بتونه چیزی دریافت کنه-لازمه بگم که هر سوکت میتونه به حجم محدودی اطلاعات رو توی خودش نگه داره- پس از اون کرنل دخالت میکنه و برنامه‌ یا برنامه ‌هایی که میخوان به اون سوکت بنویسن رو بصورت موقت متوقف میکنه. تا سرویس دهنده اجرا بشه و بیاد به درخواست‌ها پاسخ بده و سوکت با امکان نوشته شدن داشته باشه. این ایده جدید نبوده و توی inted ملقب به superserver هم برای سرویس دهنده‌های اینترنتی مثل ftp مورد استفاده قرار گرفته. اما systemd ایده رو توسعه داده و بجای سوکت‌های اینترنتی از همه نوع سوکتی پشتیبانی میکنه. این روش سود‌های دیگه‌ای هم داره:

  • وابستگی‌ها بصورت واضحی معرفی نمیشن چون همه سوکت‌ها برای دریافت اطلاعات وجود دارن
  • اگر یک سرویس به علتی خراب بشه و به اصطلاح بمیره سوکت اون وجود خواهد داشت و پس از شروع مجدد دوباره کارهاش رو از همون جایی که بوده پی میگیره
  • اگر یک سرویس بروزرسانی بشه میتوان اون رو به راحتی restart کرد ولی سوکت مربوطه وجود داره به سرویس دهی ادامه میده
  • تغییر سرویس هم از دید کاربر مخفی میمونه

همین!

منابع:

حالا اگه کسی خواست واقعا بیشتر بدونه بهتره به منابع پایین یه سری بزنه

  1. این سایت اصلیه که کلی اطلاعات جذاب توش داره.
  2. این سایت نویسنده یا یکی از نویسندگان اصلی systemd هست
  3. اگه برنامه نویس هستید و میخواد با این مفهوم بیشتر آشنا بشید این مطلب و این یکی رو هم بخونید