ظاهر پنهان قدرت: چطور پردازش موازی، بازی‌هات رو منفجر می‌کنه؟

ظاهر پنهان قدرت: چطور پردازش موازی، بازی‌هات رو منفجر می‌کنه؟

فهرست محتوا

قدرت پنهان در بازی‌ها: چگونه پردازش موازی، عملکرد بازی شما را متحول می‌کند؟

احتمالاً برای شما هم پیش آمده که در حین اجرای یک بازی ویدیویی، با افت فریم ریت یا کندی در پردازش روبرو شده باشید. اما آیا می‌دانستید که یکی از عوامل کلیدی در بهبود عملکرد بازی‌ها، استفاده از تکنیکی به نام پردازش موازی یا Multi-Threading است؟

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

در این مقاله، قصد داریم به بررسی یکی از مفاهیم کلیدی در دنیای بازی‌سازی، یعنی چند ریسمانی (Multi-Threading) و تاثیر آن بر عملکرد بازی‌ها بپردازیم.

CPU (واحد پردازش مرکزی): مغز متفکر سیستم شما

یک مغز دیجیتال

نگاهی به اجزا

برای درک بهتر این موضوع، ابتدا باید با مهم‌ترین قطعه سخت‌افزاری سیستم خود، یعنی CPU آشنا شویم.

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

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

تصویری از ALU

اما چه چیزی باعث می‌شود یک CPU در انجام وظایفش بهتر عمل کند؟ بیایید نگاهی دقیق‌تر به اجزای تشکیل‌دهنده آن بیندازیم. CPU از واحدهای مختلفی تشکیل شده است. واحد محاسبه و منطق (ALU) وظیفه انجام محاسبات ریاضی و منطقی را بر عهده دارد. واحد کنترل (Control Unit) وظیفه مدیریت و هماهنگی بین اجزای مختلف CPU را بر عهده دارد. رجیسترها (Registers) حافظه‌های کوچک و پرسرعتی هستند که برای نگهداری داده‌های مورد نیاز در حین پردازش استفاده می‌شوند.

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

تعداد هسته‌های یک CPU:

تعداد هسته‌های یک CPU

هسته؛ قلب تپنده چند ریسمانی

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

در دهه‌های گذشته، ساختار CPU با تغییرات فراوانی مواجه بوده است. در گذشته، CPUها تنها از یک واحد پردازشی تشکیل می‌شدند، اما امروزه، CPUها از چندین هسته پردازشی تشکیل شده‌اند. هر هسته (Core) را می‌توان به نوعی یک مغز کوچک تعبیر کرد که در داخل یک مغز بزرگ‌تر قرار دارد. هر کدام از این هسته‌ها قادر به انجام وظایف مستقل خود هستند.

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

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

چند ریسمانی و تک ریسمانی

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

🧐 اما سوال نیست که برخی بازی‌ها در هنگام اجرا، باعث بالا رفتن دمای CPU می‌شوند؟

در بین تمام ویژ‌گی‌هایی که باید داشته باشید از جمله، دانش بسیار بالا در ریاضی (جبر و ریاضیات سه‌بعدی)، زبان ++C (معمولاً تجربه چند ساله)، سابقه ساخت یک اثر یا همکاری با یک استودیو، یک مورد بسیار مهم در این بین وجود دارد: تجربه و توانایی نوشتن کدهای MultiThreading.

در اینجا لازم است مفهوم چند نخی (Multi Threading) را به دفعات بسیار برای شما روشن کنیم. اما چند نخی چیست؟ چرا نسبت به گذشته از اهمیت بسیار بالایی برخوردار شده؟ برای فهم این موضوع از واژه «ریسمان» شروع می‌کنیم.

تصویر شماتیک از multi thread

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

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

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

بیایید با یک مثال دیگر جلو برویم. شما یک برنامه نویسنده‌اید، و تمامی این خطوط برنامه قرار است پشت سر هم اجرا شوند. دقت کنید پشت سر هم! اما یک برنامه نویس قصد ندارد برنامه‌اش از خط یک به تا خط ده به صورت پیوسته اجرا شود، بلکه همه‌چیز خطوط به طور همزمان اجرا شوند.
من قصد دارم برنامه واکنش پذیر و بسیار سریع‌تر باشد. اگر برنامه فتوشاپ برای 100 قطعه عکس، رویه تک نخی داشته باشد، باید برای هر عکس که تنها تغییرات رنگ را (فرضا سفید به سیاه بود) کاربر ممکن است چندین ساعت در انتظار بماند.

مزایای استفاده از نخ:

  1. استفاده از چند نخ باعث بهبود عملکرد می‌شود.
  2. برنامه نسبت به ورودی کاربر سریع‌تر واکنش نشان می‌دهد.
  3. سازمان‌دهی بهتر وظایف، یعنی هر نخ مسئولیت یک کار خاص را دارد.

بد نیست بدانید:

  • CPU از معمولا از چند الگوریتم استفاده می‌کند:
  • FCFS (First Come First Serve): اجرای به ترتیب فعالیت‌ها.
  • SJF (Shortest Job First): اجرای عملیاتی که منابع کمتری درخواست می‌کنند.
  • Round Robin: هر پردازش برای مدت کوتاهی اجرا می‌شود و بعد به سراغ فعالیت‌های بعدی می‌رود.
  • Priority Scheduling: پردازش‌ها بر اساس اولویت انتخاب می‌شوند.

تنها برنامه نویس خوب

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

کابوسی به نام چند ریسمانی

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

یکی دیگر از چالش‌های مربوط به “قفل شدن متقابل” یا Dead Lock است. این اتفاق زمانی رخ می‌دهد که چند نخ برای ادامه کار خود، نیازمند منبعی مشترک هستند که توسط نخ‌های دیگر قفل شده‌اند. بدین صورت هیچ ریسمانی نمیتواند به وظیفه خود برسد، زیرا منتظر یکدیگر هستند!

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

یک مثال ساده؛ دنیای رد دد ریدمپشن 2

به جهان Red Dead Redemption 2 فکر کنید. سوار بر اسب شده‌اید و یک مسیر را طی می‌کنید. اسب با دستور و سرعت مد نظر حرکت می‌کند، نور می‌تابد، فیزیک اسب مثل زدن عرق به گوش می‌رسد و صدا در جریان و آهنگی که قصد رفع تشنگی دارد.

به یک باره چند فرد ناشناس از راه می‌رسند و قصد نیت آ‌ن‌ها آنچنان دوستانه نیست اما به نظر هدف خوبی در سر ندارند. ناگهان متوجه می‌شوند که شما عضوی از گنگ Van Der Linde هستید و بدون لحظه‌ای درنگ اسلحه خود را بیرون آورده و شروع به تیراندازی می‌کنند. تُن موسیقی تغییر می‌کند، آب هم‌چنان جریان دارد اما با صدای شلیک برای نجات جان خود پا به فرار می‌گذارید. اسب ترس را بروز می‌دهد و آرتور برای دفاع از خود شروع به شلیک می‌کند. همه‌چیز در کسری از ثانیه تغییر کرد. چگونه این حجم از هماهنگی رخ می‌دهد؟

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

برنامه‌نویسان مجبور شدند از کتابخانه‌های خارجی به‌مانند Posix Threads در سیستم‌عامل‌های شبه یونیکس یا Windows Threads API استفاده کنند.

تا پیش از سال 2011 هیچ راه استانداردی برای نوشتن یک برنامه چند ریسمانی در ++C وجود نداشت. ++C هم چون پدر خود یعنی C، از همان کتابخانه‌های خارجی برای حل این موضوع استفاده می‌کرد. پس از سال C++ 11، این زبان به خودکفایی رسید، حالا زبان ++C قدرتمندتر از گذشته بود! دیگر نیازی به پلتفرم خاصی نیست، بلکه تنها لازم است در برنامه خود از “std::thread” برای ساخت یک ریسمان استفاده کنید. همچنین قابلیت‌هایی چون “std::mutex” و “std::lock_guard” برای تداخل ریسمان‌ها (صرفاً برای بهبود عملکرد نه رفع کامل) وجود داشتند. برخی‌ها با استاندارد‌های جدیدتر به‌مانند ++C17 تا به امروز یعنی ++C20 مشکلات یکی پس از دیگری را رفع و ++C را عمل‌کرد بهتری نسبت به گذشته ارائه می‌دهند.

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

دیدگاهتان را بنویسید

نشانی ایمیل شما منتشر نخواهد شد. بخش‌های موردنیاز علامت‌گذاری شده‌اند *