Почему код, сгенерированный компилятором, отличается от шаблонов лямбды? - PullRequest
1 голос
/ 29 марта 2019

Когда я пытаюсь использовать следующий код в проводнике компилятора с флагом -O3, сгенерированный код сокращается до 3-х основных инструкций по сборке - mov, imul, mov:

#include <functional>

template <typename T>
int o2 (T f, int x)
{
    return f(x);
}

auto f2 = [](int x) -> int { return x*x; };

int x = 2;
int y = o2(f2, x);

Теперь вместо шаблонной функции,если я использую std::function, как показано ниже, фактический вызов по-прежнему 3 инструкции, но код для лямбда f2 и o2 все еще там:

#include <functional>

int o2(std::function<int(int)> f, int x)
{
    return f(x);
}

auto f2 = [](int x) -> int { return x*x; };

int x = 2;
int y = o2(f2, x);

Почему компилятор не оптимизируетих прочь?Есть ли способ заставить компилятор так, чтобы код, сгенерированный для этих двух фрагментов, был одинаковым?

Я выступал за усиление проверок, используя std::function и заменяя стиль шаблона.Иногда сообщения об ошибках шаблона не понятны.Но это не эквивалентно или я что-то упустил?

Ответы [ 3 ]

3 голосов
/ 29 марта 2019

Оптимизация классов-оболочек, таких как std::function, компилятору сложнее оптимизировать. Это требует потенциально кучного разрешения и механизма, доступного только в некоторой конкретной реализации.

По сути, std::function гораздо более непрозрачен, чем параметр шаблона, поскольку параметр шаблона разрешается непосредственно к нужному типу.

Я бы порекомендовал избегать использования std::function в параметре функции. Хотя иногда это неизбежно, например, при демонстрации виртуальной функции.

Где std::function действительно сияет как хранилище. Это обычно, когда тип вызываемого объекта неизвестен, тогда как он почти всегда известен в параметре функции.

2 голосов
/ 29 марта 2019

хорошо

Во-первых, как вы заметили, глобальный оптимизатор генерирует одинаковый полностью встроенный код для обоих вариантов :

    mov     DWORD PTR [rsp-8], 2
    mov     eax, DWORD PTR [rsp-8]
    imul    eax, eax
    mov     DWORD PTR [rsp-4], eax

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

Плохой; -)

Теперь ваша функциональная версия o2() имеет внешнюю связь . Таким образом, он может быть вызван из другого блока перевода, если вы свяжете его с тем, который будет ссылаться на него. Вот почему вызываемый код для функции также генерируется под меткой o2(std::function<int (int)>, int) и сохраняется, несмотря на то, что он не нужен «встроенному» коду.

И безобразный

В функциональном варианте есть еще какой-то дополнительный код, который менее очевиден. Если присмотреться повнимательнее, я предполагаю, что используемая реализация std::function создает экземпляры некоторых классов (возможно, с использованием CRTP), которые представляют лямбду как вызываемый класс:

  • функция-член, которая реализует лямбду таким образом, чтобы она вызывалась как функция-член (типичный подход для создания вызываемого объекта) под меткой std::_Function_handler<int (int), main::{lambda(int)#1}>::_M_invoke(std::_Any_data const&, int&&):
  • функция-член, которая реализует некоторый код котельной пластины для вызова предыдущего под меткой std::_Function_base::_Base_manager<main::{lambda(int)#1}>::_M_manager(std::_Any_data&, std::_Function_base::_Base_manager<main::{lambda(int)#1}> const&, std::_Manager_operation):
  • некоторые typeinfo и виртуальный стол. Это показывает, что хотя бы один из этих классов является полиморфным.
  • некоторый код для исключения (поскольку function может выдать)

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

0 голосов
/ 30 марта 2019

Это помогло мне.

typedef int (*func_t)(int);

static int o2(func_t f, int x)
{
    return f(x);
}

auto f2 = [](int x) -> int { return x*x; };

int x = 2;
int y = o2(f2, x);

Создание o2 статического убрано. Проводник компилятора делает lib? Я не уверен, что здесь происходит.

std::function создавал своего рода менеджера. Когда я использовал тип указателя на функцию, он также исчез.

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