Есть ли способ проверить, встроены ли лямбда-функции C ++ компилятором? - PullRequest
6 голосов
/ 11 апреля 2020

Я программирую с помощью C ++ lambdas. Из соображений производительности я хочу убедиться, что вызов лямбды встроен компилятором. Например, у меня есть этот упрощенный фрагмент кода:

template <typename T>
auto gen_fn1(T x1, T x2) {
    auto fn1 = [x1, x2]() {
        return x1 + x2;
    };
    return fn1;
}

template <typename T>
auto gen_fn2(T x1, T x2) {
    auto fn2 = [x1, x2]() {
        auto fn1 = gen_fn1(x1, x2);
        return fn1() * fn1();
    };
    return fn2;
}

int test_1() {
    auto fn2 = gen_fn2(1, 2);
    return fn2();
}

Я хочу убедиться, что при лямбда-генерации и вызове в test_1 () не возникает никаких дополнительных затрат. Я могу вручную проверить код сборки, сгенерированный компиляцией. С оптимизацией '-O2' в clang ++ 8 я могу видеть желаемый результат: почти просто 'return 9' в сгенерированном коде Итак, мой вопрос: есть ли способ автоматически проверить, что я всегда могу получить желаемый результат? В частности, я хочу проверить:

  1. Нет вызова метода для создания лямбд в 'test_1 ()', включая 'gen_fn2 ()' и 'gen_fn1 ()'.
  2. Никаких затрат на лямбда-вызовы в «test_1 ()» или «gen_fn2 ()», таких как «fn1 ()» и «fn2 ()». Я ожидаю, что они могут быть встроены. Так как их идентифицировать и проверить, что они встроены?

Вопрос 2 мне интереснее. Быть способным проверить это программируемым способом наиболее ценно, например, 'assert (gen_fn2 (1, 2) == () [] {return 9;}'. Если это невозможно, проверить промежуточный файл компилятора также полезно или файл сборки. Но как?

Ответы [ 3 ]

2 голосов
/ 11 апреля 2020

TL; DR: не без рассмотрения результатов компиляции.

Во-первых, как указывают другие ответы, лямбды C ++ - это в основном анонимные классы с методом operator(); Итак, ваш вопрос ничем не отличается от того, «есть ли способ проверить, что определенный вызов метода объекта встроен?»

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

То, что вы можете сделать, - это одна из двух вещей:

  • Внешняя проверка вывода компиляции (самый простой способ - компиляция без сборки, например, gcc -S или clang++ -S; хотя теоретически встраивание все еще может произойти во время компоновки)
  • Внутренне попытайтесь определить побочные эффекты выбора встраивания. Например, у вас может быть функция, которая получает адрес функции, которую вы хотите проверить; затем вы читаете - во время выполнения - инструкции этой функции, чтобы увидеть, есть ли у нее какие-либо вызовы функций, найдите вызываемые адреса в таблице символов и посмотрите, происходит ли имя символа от некоторой лямбды. Это уже довольно сложно, подвержено ошибкам, зависит от платформы c и хрупко - и есть тот факт, что в одной и той же функции может использоваться две лямбда-выражения. Поэтому я бы не рекомендовал делать что-то подобное.
1 голос
/ 11 апреля 2020

Если что-то было встроено или нет (что бы это ни значило), конечно, можно обнаружить, только взглянув на сгенерированный код. Например, с помощью g ++ вы можете скомпилировать с помощью -S и затем выполнить поиск по тому, что вы ищете в сгенерированном источнике сборки.

Однако, если вы действительно заботитесь о производительности, вам нужно смотреть на производительность, а не на встраивание.

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

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

1 голос
/ 11 апреля 2020

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

auto temp = []() {
return true;
}
std::cout << typeid(temp).name() << "\n";

[] -> предложение захвата компилятор записывает частный элемент данных в класс для каждого члена предложения захвата. () -> параметры, компилятор перегружает функцию вызова оператора для класса и пишет что-то вроде этого для этого кода.

class Temp12343786 {
public:
auto operator()() {
return true;
}
};

, и, как вы можете видеть, это встроенная функция для CLASS.

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