Влияние на производительность объектов

0

Я начинающий программист с некоторым опытом программирования c и c++. Я был назначен университетом для создания физического симулятора, так как вы могли бы представить себе большой акцент на производительности.

Мои вопросы заключаются в следующем:

  1. Сколько инструкций по сборке выполняет доступ к экземпляру данных экземпляра через перевод указателя (например, для примера vector-> x)?
  2. Разве это гораздо больше, чем другой подход, когда вы просто обращаетесь к памяти через символ char * (в том же месте памяти переменной x), или это то же самое?
  3. Есть ли большое влияние на производительность компилятора, если я использую объект для доступа к этой ячейке памяти или если я просто обращаюсь к ней?
  4. Другой вопрос, связанный с предметом, будет заключаться в том, быстрее или быстрее доступ к памяти кучи, чем доступ к памяти стека?
  • 5
    C сочетает в себе производительность ассемблера с гибкостью языка ассемблера.
  • 5
    Здесь есть как минимум две разные темы; пожалуйста, ограничьте себя одной темой в каждом сообщении. Благодарю.
Показать ещё 1 комментарий
Теги:
performance

4 ответа

7

C++ - это скомпилированный язык. Доступ к ячейке памяти с помощью указателя одинаковый независимо от того, является ли указатель на объект или указателем на char* - это одна команда в любом случае. Есть пара пятен, где C++ добавляет накладные расходы, но он всегда покупает вам некоторую гибкость. Например, для вызова виртуальной функции требуется дополнительный уровень косвенности. Тем не менее, вам понадобится такая же косвенность, если вы хотите эмулировать виртуальную функцию с помощью указателей функций, или вы потратили бы сравнимое количество циклов ЦП, если бы вы эмулировали ее с помощью switch или последовательности if.

В общем, вам не следует начинать оптимизацию, прежде чем вы узнаете, какую часть вашего кода нужно оптимизировать. Обычно основная часть вашего процессора отвечает за небольшую часть вашего кода. Вы не знаете, какую часть нужно оптимизировать, пока вы не профилируете свой код. Почти повсеместно это код программиста, а не особенности языка C++, который отвечает за замедление. Единственный способ узнать наверняка - это профиль.

  • 4
    +1 Я программирую игры на C ++, поэтому часто сталкиваюсь с проблемами производительности. До сих пор у меня еще не было проблем с производительностью напрямую из-за C ++. Каждый раз это было из-за того, как это использовалось. Вы не можете победить лучшую логику.
3
  1. На x86 доступ к указателю, как правило, является одной дополнительной инструкцией, выше и выше, чем обычно требуется для выполнения операции (ex y = object->x; будет одной нагрузкой адреса в object, а один нагрузка значения x и один магазин для y - в ассемблере x86 и загружает, и сохраняет команды mov с целью памяти). Иногда это "нулевые" инструкции, потому что компилятор может оптимизировать загрузку указателя объекта. В других архитектурах все зависит от того, как работает архитектура: некоторые архитектуры имеют очень ограниченные возможности доступа к памяти и/или загрузки адресов указателям и т.д., Что затрудняет доступ к указателям.

  2. Точно такое же количество инструкций - это относится ко всем

  3. Как №2 - объекты сами по себе не оказывают никакого влияния.

  4. Память кучи и стек памяти одинаковы. В одном ответе говорится, что "стек памяти всегда находится в caceh", что верно, если оно "находится ближе к вершине стека", где все действия продолжаются, но если у вас есть объект, который передается вокруг того, что было создано в main и указатель на него используется для передачи его для нескольких уровней вызовов функций, а затем доступа через указатель, существует очевидная вероятность того, что эта память долгое время не использовалась, поэтому нет реальной разница там тоже). Большая разница заключается в том, что "кучевая память - это много места, стека ограничено", а "выход из кучи - это возможное ограниченное восстановление, истечение стека - это немедленный конец исполнения" [без трюков, которые не очень переносимы) "

Если вы рассматриваете class как синоним struct в C (который, помимо некоторых деталей, они действительно есть), тогда вы поймете, что class и объекты на самом деле не добавляют лишних "усилий" к генерируемому коду.

Разумеется, используется правильно, C++ может сделать намного проще писать код, где вы имеете дело с вещами, которые "делают это очень похоже, но тонко по-разному". В C вы часто получаете:

   void drawStuff(Shape *shapes, int count)
   {
     for(i = 0; i < count; i++)
     {
        switch (shapes[i].shapeType)
       {
       case Circle:
         ... code to draw a circle ... 
         break;
       case Rectangle:
         ... code to draw a rectangle ... 
         break;
       case Square:
         ... 
         break;
       case Triangle:
         ...
         break;
       }
     }
   }

В C++ мы можем сделать это во время создания объекта, и ваш "drawStuff" становится следующим:

void drawStuff(std::vector<Shape*> shapes)
{
   for(auto s : shapes)
   {
      s->Draw();
   }
}

"Посмотрите Ма, нет переключателя...";) (Конечно, вам нужен переключатель или что-то делать для выбора того, какой объект создавать, но как только выбор сделан, если ваши объекты и окружающая архитектура хорошо определены, все должно работать "магически", как в приведенном выше примере).

Наконец, если это ВАЖНО с производительностью, затем запустите тесты, запустите профилирование и проверьте, где код тратит время. Не оптимизируйте слишком рано (но если у вас есть строгие критерии эффективности для чего-то, следите за ним, потому что решаете на прошлой неделе проекта, что вам нужно, чтобы реорганизовать ваши данные и код резко, потому что производительность отстой из-за некоторых плохое решение также не лучшее из идей!). И не оптимизируйте отдельные инструкции, посмотрите, где потрачено время, и придумайте лучшие алгоритмы, в которых вам нужно. (В приведенном выше примере использование const std::vector<Shape*>& shapes эффективно передает указатель на вектор формы, переданный в, вместо копирования всего объекта - что может иметь значение, если в нем несколько тысяч элементов в shapes).

1
  • Это зависит от вашей целевой архитектуры. Структура в C (и класс в C++) - это всего лишь блок памяти, содержащий элементы в последовательности. Доступ к такому полю через указатель означает добавление смещения к указателю и загрузку оттуда. Многие архитектуры позволяют нагрузке уже указать смещение к целевому адресу, что означает, что в нем нет штрафа за производительность; но даже на экстремальных RISC-машинах, у которых этого нет, добавление смещения должно быть настолько дешевым, что нагрузка полностью его тень.
  • Стек и память кучи - это одно и то же. Просто разные области. Поэтому их базовая скорость доступа одинакова. Основное отличие состоит в том, что стек, скорее всего, уже будет в кеше, несмотря ни на что, в то время как память кучи может отсутствовать, если в последнее время он не был доступен.
  • 0
    Доступ к данным «в куче» неизменно требует разыменования указателя в некоторой точке; Доступ к данным «в стеке» чаще всего не дает.
  • 2
    Конечно, это так - вам нужно разыменовать указатель стека. Который есть в регистре, правда, но все же указатель.
0
  1. Переменный. На большинстве процессоров инструкции переводится на микрокод, похожий на то, как Java-байт-код переводится в инструкции, специфичные для процессора, перед его запуском. Сколько фактических инструкций вы получаете, различаются между разными производителями процессоров и моделями.
  2. Как и выше, это зависит от внутренних компонентов процессора, о которых большинство из нас мало знает.

1 + 2. То, что вы должны спросить, - сколько циклов тактовых операций. На современных платформах ответ один. Неважно, сколько инструкций у них есть, современный процессор имеет оптимизацию для запуска обоих тактов. Здесь я не буду подробно разбираться. Другими словами, когда речь идет о загрузке ЦП, нет никакой разницы.

  1. Здесь у вас есть сложная часть. Хотя нет никакой разницы в том, сколько часов циклов выполняет сама инструкция, для ее работы необходимо иметь данные из памяти, прежде чем она сможет работать - это может привести к огромному количеству тактов. На самом деле кто-то доказал несколько лет назад, что даже при очень оптимизированной программе процессор x86 тратит не менее 50% своего времени на ожидание доступа к памяти.

  2. Когда вы используете стек памяти, вы на самом деле делаете то же самое, что и создание массива структур. Для данных инструкции не дублируются, если у вас нет виртуальных функций. Это приводит к выравниванию данных, и если вы собираетесь выполнять последовательный доступ, у вас будут оптимальные кэш-запросы. Когда вы используете память кучи, вы создадите массив указателей, и каждый объект будет иметь свою собственную память. Эта память НЕ будет выровнена, и поэтому в последовательном доступе будет много промахов в кэше. И промахи в кэше - это то, что действительно замедлит ваше приложение, и его следует избегать любой ценой.

Я не знаю точно, что вы делаете, но во многих случаях даже использование объектов происходит намного медленнее, чем простые массивы. Массив объектов выравнивается [object1] [object2] и т.д. Если вы делаете что-то вроде псевдокода "для каждого объекта o {o.setX() = o.getX() + 1}"... это означает, что вы будете только доступ к одной переменной, и ваш последовательный доступ, следовательно, перепрыгнет через другие переменные в каждом объекте и получит больше промахов в кеше, чем если бы ваши X-переменные были выровнены в их собственном массиве. И если у вас есть код, который использует все переменные в вашем объекте, стандартные массивы не будут медленнее, чем массив объектов. Он просто загрузит различные массивы в разные блоки кэша.

Хотя в C++ стандартные массивы быстрее, они намного быстрее на других языках, таких как Java, где вам никогда не нужно хранить массивные данные в объектах, поскольку объекты Java используют больше памяти и всегда хранятся в куче. Это самая распространенная ошибка, которую программисты C++ делают на Java, а затем жалуются на то, что Java медленная. Однако, если они знают, как писать оптимальные программы C++, они хранят данные в массивах, которые так же быстры в Java, как в C++.

То, что я обычно делаю, это класс для хранения данных, содержащий массивы. Даже если вы используете кучу, это всего лишь один объект, который становится так же быстро, как использование стека. Тогда у меня есть что-то вроде "class myitem {private: int pos; mydata data; public getVar1() {return data.getVar1 (pos);}}". Я не пишу здесь весь код, просто иллюстрируя, как я это делаю. Затем, когда я повторяю его, класс итератора фактически не возвращает новый экземпляр myitem для каждого элемента, он увеличивает значение pos и возвращает тот же объект. Это означает, что вы получаете хороший OO API, в то время как на самом деле у вас есть только несколько объектов и красиво выровненные массивы. Этот шаблон является самым быстрым шаблоном в C++, и если вы не используете его в Java, вы узнаете боль.

Тот факт, что мы получаем несколько вызовов функций, не имеет большого значения. Современные процессоры имеют что-то, называемое отраслевым прогнозом, которое позволит снять стоимость подавляющего большинства этих вызовов. Задолго до того, как код действительно будет работать, предсказатель ветвления будет выяснять, что делают цепочки вызовов и заменяет их одним вызовом в сгенерированном микрокоде.

Кроме того, даже если бы все вызовы выполнялись, каждый из них занимал бы гораздо меньше тактовых циклов доступ к памяти, который им нужен, что, как я указывал, делает выравнивание памяти единственной проблемой, которая вас беспокоит.

Ещё вопросы

Сообщество Overcoder
Наверх
Меню