Преимущество эффективности статического оператора C++ 23()

Я смотрю на мотивацию статического оператора(), изложенную в этой статье.

https://www.open-std.org/jtc1/sc22/wg21/docs/papers/2021/p1169r2.html

где они объясняют, что функциональные объекты, часто используемые в алгоритмах STL, несут дополнительные затраты на хранение своего указателя this в регистре, даже если оператор() его не использует.

Для этого они приводят следующий пример кода: https://godbolt.org/z/ajTZo2

Если запуск алгоритма для использования статической функции функционального объекта, а не нестатического оператора(), действительно приводит к более короткому ассемблерному коду. (Они запускают его один раз с -DSTATIC и один раз без )

struct X {
    bool operator()(int) const;
    static bool f(int);
};

inline constexpr X x;

int count_x(std::vector<int> const& xs) {
    return std::count_if (xs.begin(), xs.end(),
#ifdef STATIC
    X::f
#else
    x
#endif
    );
}   

Однако, когда я изменяю функции, чтобы они действительно что-то делали, будь то просто возврат истины или сравнение параметра с жестко закодированным значением, различия, кажется, исчезают, и создается впечатление, что созданная сборка идентична, оставляя меня все еще искать преимущество статического оператора().

#include <vector>
#include <algorithm>
using namespace std;

struct X {
    bool operator()(int data ) { return data > 5; } ;
    static bool f(int data) { return data > 5; } ;
};

inline constexpr X x;

int count_x(std::vector<int> const& xs) {
    return std::count_if (xs.begin(), xs.end(),
#ifdef STATIC
    X::f
#else
    x
#endif
    );
}

может кто-нибудь объяснить?

Редактировать: После публикации этого вопроса и понимания из комментариев, что встраивание может быть причиной идентичной сборки, я изменил код на следующий, что снова делает сборку для статической версии более эффективной.

struct X {
    bool operator()(int data ) ;
    static bool f(int data);
};

 __attribute__ ((noinline))  bool X::operator() (int data) {
     { return data > 5; } 
}

 __attribute__ ((noinline)) bool X::f(int data )
     { return data > 5; } 

inline constexpr X x;

int count_x(std::vector<int> const& xs) {
    return std::count_if (xs.begin(), xs.end(),
#ifdef STATIC
    X::f
#else
    x
#endif
    );
}

При невидимом определении (разделённом на несколько файлов) компилятор не сможет удалить лишний this в нестатическом случае (если только LTO не увенчается успехом, но это сложнее).

Jarod42 15.05.2024 13:58

Я еще этого не пробовал. Я подозревал встраивание и попробовал определить функции вне тела класса, но сборка по-прежнему идентична.

Gonen I 15.05.2024 14:00

В нескольких файлах cpp?

Jarod42 15.05.2024 14:04

Оригинальный пример — это, по сути, то, о чем говорит Джарод. Без тел на виду при составлении этого ТУ мы не можем просто опустить лишний аргумент.

StoryTeller - Unslander Monica 15.05.2024 14:08

А как насчет статических лямбда-операторов? Кажется, тела всегда будут на виду. Смогу ли я увидеть какую-либо разницу?

Gonen I 15.05.2024 14:27

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

463035818_is_not_an_ai 15.05.2024 14:42

Содержит ли пост и вопрос, и ответ? Здесь что-нибудь требует ответа?

anatolyg 15.05.2024 14:57

Что касается меня, меня не особо волнует «использование регистра для передачи указателя this». По большому счету это ничего не значит. Вместо этого мне иногда просто нужен static operator(), чтобы я мог использовать синтаксис () вместо static perform() или чего-то подобного - иногда мне просто нужен синтаксис вызова функции для удобства чтения, а иногда нет.

Christian Stieber 15.05.2024 17:37

@ChristianStieber: Хотя я не уверен, насколько это значимо. Нельзя Typename(). Ну, вы можете, но это означает «инициализировать объект этого типа с этими параметрами», как всегда. Поэтому он всегда будет называться object(). Если разрешить static operator(), читаемость не изменится.

Nicol Bolas 15.05.2024 18:05
Стоит ли изучать PHP в 2026-2027 годах?
Стоит ли изучать PHP в 2026-2027 годах?
Привет всем, сегодня я хочу высказать свои соображения по поводу вопроса, который я уже много раз получал в своем сообществе: "Стоит ли изучать PHP в...
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
В JavaScript одним из самых запутанных понятий является поведение ключевого слова "this" в стрелочной и обычной функциях.
Приемы CSS-макетирования - floats и Flexbox
Приемы CSS-макетирования - floats и Flexbox
Здравствуйте, друзья-студенты! Готовы совершенствовать свои навыки веб-дизайна? Сегодня в нашем путешествии мы рассмотрим приемы CSS-верстки - в...
Тестирование функциональных ngrx-эффектов в Angular 16 с помощью Jest
В системе управления состояниями ngrx, совместимой с Angular 16, появились функциональные эффекты. Это здорово и делает код определенно легче для...
Концепция локализации и ее применение в приложениях React ⚡️
Концепция локализации и ее применение в приложениях React ⚡️
Локализация - это процесс адаптации приложения к различным языкам и культурным требованиям. Это позволяет пользователям получить опыт, соответствующий...
Пользовательский скаляр GraphQL
Пользовательский скаляр GraphQL
Листовые узлы системы типов GraphQL называются скалярами. Достигнув скалярного типа, невозможно спуститься дальше по иерархии типов. Скалярный тип...
1
9
153
1
Перейти к ответу Данный вопрос помечен как решенный

Ответы 1

Ответ принят как подходящий

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

По сути, в этом суть возможности аннотировать операторов вызовов как static. Если вам не нужен параметр объекта, просто дайте понять из подписи, что параметр объекта вам не нужен. Вы уже можете сделать это с именованными функциями, просто вы не смогли бы этого сделать, если бы ваша функция-член имела имя (). Это было произвольное ограничение, которое не позволяло вам четко выразить свое намерение.

Кроме того, это упрощает работу компилятора - не нужно оптимизировать параметр объекта, если его изначально не было. Сбои при встраивании — это испытание, приводящее к смерти от тысячи сокращений, поэтому помогает каждая мелочь.

(Источник: это было мое предложение)

Спасибо. Я подумывал просто отправить вам этот вопрос по электронной почте, но подумал, что, возможно, здесь он может быть полезен другим.

Gonen I 16.05.2024 13:41

Другие вопросы по теме