Как стать автором
Обновить

Комментарии 19

Оптимизации без замеров производительности...

Насколько процентов будет повышена производительность в реальном коде? Будет забавно, если мы экономим такты процессора, а сенсоры имеют время ответа, скажем, в десятки секунд.

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

Пусть даже оптимизация в конкретном случае окажется и не шибко эффективная, но если она "бесплатная", то почему бы нет? Хуже-то точно не сделает.

Первое шаг в оптимизации - замерять фактическую производительность. Не факт, что расстановка final даст прирост производительности больший, чем оптимизация другого участка кода. Возможно, приложить усилия нужно в другом месте.

Не совсем она бесплатная - она требует добавления ключевого слова в исходники и немного меняет семантику описания метода. Код засоряется, появляются лишние семантические связи, и ошибки дольше искать.

И не совсем оптимизация - ассемблерный выход с final и без может быть одинаков.

Для общего случая - целиком и полностью согласен. Всерьёз оптимизировать нужно там, где наличествуют бутылочные горлышки, а их покажут только замеры. Но здесь не тот случай. Я считаю, что добавление ключевого слова, которое придаст коду больше однозначности - само по себе полезно. Поэтому и считаю "бесплатным". Спецификатор override тоже ведь можно не писать (и я его поначалу его не использовал), но с ним код становится понятнее (и когда я это заметил, то стал стараться его использовать).

И не совсем оптимизация - ассемблерный выход с final и без может быть одинаков.

Да, и я именно это и имел в виду, когда писал, что "хуже не сделает". Если final делает код (или, может, видение автора кода) понятнее, и при этом даёт хотя бы малую вероятность, что станет чуть быстрее, почему бы его не использовать?

final помимо оптимизации, еще и задает семантику "класс/метод не готов к перегрузкам". Естественно, что через некоторое время именно этот метод и хочется перезагрузить, но final говорит, что ни в коем случае нельзя, не объясняя почему - снизится скорость или нужно поправить другой метод или нужно на самом деле использовать другой подход?
В общем, когда final стоит просто на всякий случай, он создает значительное напряжение при поддержке кода.

Вообще наследование классов не хорошо, почти всегда можно использовать агрегацию.

У меня крепнет ощущение, что я был неправильно понят. Я ни в коем случае не предлагаю использовать final ни "для оптимизации", ни "на всякий случай". Только для того, для чего оно предназначено. Например, чтобы явно указать поддерживающему программисту, у которого по какой-то причине возникнет желание метод перегрузить, что он идёт в неверном направлении, и автором предполагалось, что работать оно должно не так, как он считает. Да, действительно, хорошо бы и комментарий иметь от автора, где сказано, почему оно так. Но один комментарий сам по себе делу может и не помочь, его могут не заметить; final же гарантирует, что не пропустят. А вероятность того, что код, возможно, станет чуточку быстрее, можно воспринимать просто как приятный бонус.

Лично мне нравится идея в kotlin, что все классы финальны/закрыты по умолчанию. То есть если вы видите перед классом open, то автор (наверное) подумал несколько раз, делая его открытым. И считаю это правильным, так как редко кто специально думает над возможностью наследования каждого своего класса/структуры - просто это поведение по умолчанию (в c++/java), и возможно не самое удачное поведение по умолчанию.

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

В java такой "прямой" рекомендации/практики как-бы нет или не было (возможно зря, считаю многие старые стандартные классы нужно было изначально делать final и sealed). Но вот новые классы (из jdk) уже сделали/делают final (думаю финальные record и lombok data классы, как пример здесь не подходят).

возможно не самое удачное поведение по умолчанию

А эти поведения по умолчанию почти во всех языках разные. Где-то нельзя наследовать по умолчанию, где-то можно, где-то вообще нельзя, где-то всегда можно, где-то можно ограничить расположение потомков текущим файлом.

Даже приватность по умолчанию бывает private, package-private, public, published.

Даже непонятно, какое из этих поведений "удачное". Возможно, как в Rust - он всё-таки последним разрабатывался.

Я не являюсь экспертом С++, и поправьте меня, пожалуйста, если моё направление мыслей неправильно. Я не могу сказать насколько эквивалентны final в C++ и C# (Насколько я знаю - одно и то же). По поводу замеров производительности есть замечательное видео от MVP Microsoft (https://www.youtube.com/watch?v=d76WWAD99Yo). Разница ощутима, но опять же - поправьте, если есть существенная разница в языках.

Я несколько лет назад проводил подобные тесты на Java и на С++, и, ЕМНИП, разницы не было (во всех 4х вариантах).

У меня нет существенного опыта в дотнете, но когда я проводил бенчмарки, оптимизатор в дотнете казался более слабым, чем в java, хотя у него были свои плюсы.

Но самое главное - не во сколько раз быстрее выполняется виртуальный вызов, а сколько времени (или сколько процентов времени) это экономит в общем алгоритме. В видео разница 10 крат, но эти 10 крат - это 1.5нс. Скорее всего, 10 млн. таких вызовов (15мс) пользователь никак не заметит, 100 млн. - заметит с трудом. Очень редко приходится оптимизировать код настолько сурово.

Шаблоны сами по себе не могут рассматриваться как альтернатива, потому что это совершенно другой код, если вы можете заменить динамический полиморфизм на шаблоны, то изначальный код использовал virtual по приколу.

Вот если бы шаблоны вы совместили с type erasure, то код бы действительно стал лучше. Вы могли бы использовать динамический полиморфизм только там, где он вам действительно нужен, без переписывания всего кода. Компилятор бы понимал, что нет никаких других наследников, потому что это не иерархия и смог бы оптимизировать.(в вашем коде иерархии нет, а виртуальные функции имеют смысл только для них)

Да, если уж совсем нужно воспроизвести полиморфизм на шаблонах, то его можно сделать используя CRTP

нет, это статичесий полиморфизм

3 x на глубину наследования, не?

что?

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

Откуда там будут дополнительные инструкции? Независимо от глубины наследования, компилятору всё ещё надо взять метод по индексу из плоской vtable.


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

Зарегистрируйтесь на Хабре, чтобы оставить комментарий