Учитывая новые процессоры с новыми инструкциями для перемещения и новые контроллеры памяти, если в C ++ у меня есть вектор Derived
объекты, где Derived
состоит из виртуальных функций-членов, хорошо это или плохо для местности?
А что если у меня есть вектор указателей на базовый класс Base*
где я храню ссылки на производные объекты, которые на 1-2-3 уровня выше Base
?
В основном динамическая типизация применима к обоим случаям, но какой из них лучше для кэширования и доступа к памяти?
У меня есть предпочтение между этими 2, но я хотел бы увидеть полный ответ по этому вопросу.
Есть что-то новое, что можно рассматривать как торможение со стороны аппаратной промышленности за последние 2-3 года?
хранения Derived
скорее, чем Base *
в векторе лучше, потому что он устраняет один дополнительный уровень косвенности, и у вас есть все объекты, расположенные «вместе» в непрерывной памяти, что, в свою очередь, облегчает жизнь аппаратному средству предварительной выборки, помогает с подкачкой страниц, пропусками TLB и т. д. Однако, если вы сделаете это, убедитесь, что вы не представляете проблему нарезки.
Что касается виртуальной диспетчеризации в этом случае, то это практически не имеет значения, за исключением настройки, необходимой для «этого» указателя. Например, если Derived
переопределяет виртуальную функцию, которую вы вызываете, и у вас уже есть указатель на Devied *
, тогда корректировка «this» не требуется, в противном случае она должна быть настроена на одно из значений «this» базового класса (это также зависит от размера классов в иерархии наследования).
Пока все классы в векторе имеют одинаковые перегрузки, ЦП сможет предсказать, что происходит. Однако, если у вас есть смесь различных реализаций, тогда CPU не будет иметь никакого представления о том, какая функция будет вызываться для каждого следующего объекта, и это может вызвать проблемы с производительностью.
И не забывайте всегда делать профиль до и после внесения изменений.
Современные процессоры знают, как оптимизировать зависящие от данных инструкции перехода, а также могут это делать для зависимых от данных «ветвящихся» инструкций — процессор «узнает», что «в прошлый раз, когда я прошел здесь, я пошел НАСТОЯЩИМ путем», и если Достаточная уверенность (пройденная несколько раз с одним и тем же результатом) будет продолжать идти по этому пути.
Конечно, это не помогает, если экземпляры представляют собой полный случайный выбор различных классов, каждый из которых имеет свою собственную виртуальную функцию.
Cache-locality, конечно, немного другой вопрос, и он действительно зависит от того, храните ли вы экземпляры объекта или указатели / ссылки на экземпляры в векторе.
И, конечно, важным фактором является «какая альтернатива?» — если вы используете виртуальные функции «правильно», это означает, что (по крайней мере) имеется одна менее условная проверка в пути кода, потому что решение было принято на гораздо более ранней стадии. Это условие будет (при условии, что вероятность соответствует одной и той же) вероятности перехода решения, если вы решите его с помощью другого метода — который будет по крайней мере так же плохо для производительности, как virtual
функции с той же вероятностью (есть вероятность, что это хуже, потому что теперь у нас есть if (x) foo(); else bar();
тип сценария, поэтому мы должны сначала оценить x
затем выберите путь. obj->vfunc()
будет просто непредсказуемым, потому что выборка для vtable дает непредсказуемый результат — но по крайней мере vtable
сам кешируется.