Мені подобається думати про ефективність з точки зору " меж ". Це зручний спосіб концептуалізації досить складної взаємопов’язаної системи. Коли у вас є проблеми з продуктивністю, ви задаєте питання: "В які межі я потрапляю?" (Або: "Я пов'язаний CPU / GPU?")
Ви можете розбити його на кілька рівнів. На найвищому рівні у вас є процесор і GPU. Ви можете бути прив'язаними до процесора (GPU сидіти в режимі очікування, що чекає CPU) або GPU (CPU очікує на GPU). Ось хороша публікація в темі.
Ви можете розбити його далі. На стороні процесора ви можете використовувати всі свої цикли на даних, які вже є в кеші CPU. Або у вас може бути обмежено пам'ять , залишаючи процесор очікувати, коли дані надійдуть з основної пам'яті ( так оптимізуйте макет даних ). Ви можете все-таки розбити його.
(Хоча я роблю широкий огляд продуктивності щодо XNA, я зазначу, що виділення еталонного типу ( class
не struct
), хоча це зазвичай дешево, може викликати збирач сміття, який спалить багато циклів - особливо на Xbox 360 . Дивіться тут для більш докладної інформації).
Що стосується GPU , то я розпочну, вказавши на цей чудовий пост у блозі, який містить багато деталей. Якщо ви хочете шаленого рівня деталізації в роботі, прочитайте цю серію публікацій блогу . ( Ось простіший ).
Простіше кажучи, деякі з великих є: " ліміт заповнення " (скільки пікселів ви можете записати в backbuffer - часто скільки ви можете перевищити ), " limit shader " (наскільки складними можуть бути ваші шейдери та скільки даних ви можете просунути через них), " обмеження пропускної здатності текстури / пропускної здатності текстури " (скільки текстурних даних ви можете отримати доступ).
І ось тепер ми підійшли до великого - про що ви справді запитуєте - де процесор і GPU мають взаємодіяти (через різні API та драйвери). Нещільно є " пакетна межа " та " пропускна здатність ". (Зауважте, частина першої серії, про яку я згадував раніше, детально описується.)
Але, в основному, пакет ( як ви вже знаєте ) відбувається, коли ви викликаєте одну з GraphicsDevice.Draw*
функцій (або частина XNA, як-от SpriteBatch
, робить це за вас). Як ви, без сумніву, вже прочитали, ви отримуєте кілька тисяч * з них за кадр. Це обмеження для процесора - тому воно конкурує з вашим іншим використанням процесора. Це в основному драйвер, який упаковує все про те, що ви сказали йому намалювати, і відправивши його в GPU.
А далі є пропускна здатність до GPU. Це стільки необроблених даних, які ви можете передати туди. Сюди входить вся інформація про стан, яка постачається з партіями - все, від встановлення констант / параметрів стану та шейдерних констант / параметрів (що включає такі речі, як матриці світ / погляд / проект), до вершин при використанні DrawUser*
функцій. Він також включає будь-які дзвінки до SetData
та GetData
на текстурах, вершинні буфери тощо.
На цьому етапі слід сказати, що все, на що можна закликати SetData
(текстури, вершинні та індексні буфери тощо), а також Effect
s - залишається в пам'яті GPU. Він не постійно надсилається до GPU. Команда малювання, яка посилається на ці дані, просто надсилається вказівником на ці дані.
(Також: ви можете надсилати команди малювання лише з основного потоку, але ви можете SetData
з будь-якого потоку.)
XNA ускладнює речі кілька з його роблять державні класи ( BlendState
, DepthStencilState
і т.д.). Цей стан дані будуть відправлений за виклик відтворення (в кожній партії). Я не на 100% впевнений, але перебуваю під враженням, що він відправлений ліниво (він лише надсилає стан, який змінюється). Так чи інакше, зміни в державі дешеві до точки вільного, відносно вартості партії.
Нарешті, останнє, що потрібно згадати, - це внутрішній конвеєр GPU . Ви не хочете змушувати його спалахувати, записуючи дані, які йому ще потрібно читати, або читаючи дані, які ще потрібно записати. Промивання трубопроводу означає, що він чекає завершення операцій, щоб все було в узгодженому стані, коли доступ до даних.
Два особливих випадки, на які слід стежити, - це дзвінок GetData
на будь-що динамічне, особливо на RenderTarget2D
те, на що може писати GPU. Це надзвичайно погано для продуктивності - не робіть цього.
Інший випадок викликає SetData
вершинні / індексні буфери. Якщо вам потрібно часто це робити, використовуйте DynamicVertexBuffer
(також DynamicIndexBuffer
). Вони дозволяють GPU знати, що вони часто змінюватимуться, і робити якусь магію буферизації всередині, щоб уникнути вимивання трубопроводу.
(Також зауважте, що динамічні буфери швидші, ніж DrawUser*
методи, але їх потрібно попередньо виділити за максимально необхідним розміром.)
... І це майже все, що я знаю про продуктивність XNA :)