Должен ли я думать о скомпилированном машинном коде, когда пишу свой код?

20

Например, у меня есть следующий код:

auto z = [](int x) -> int {
    if (x > 0) {
        switch (x) {
            case 2: return 5;
            case 3: return 6;
            default: return 1;
            }
        }
    return 0;
    };

И позже я звоню это несколько раз. В коде asm я вижу внешние вызовы с лямбдой .... что-то ... Это становится не так легко читать, и я думаю, что это также может привести к производительности. Может быть, я выиграл в метапрограммировании, но проиграл ли я в отладке и производительности asm? Должен ли я избегать возможностей современного языка, макросов и других аспектов метапрограммирования, чтобы быть уверенным в производительности и простоте отладки?

CND
источник
1
В зависимости от версии компилятора и его стандартной библиотеки, лямбда может быть реализована неэффективно. Смотрите этот вопрос на Stackoverflow. Однако ответственность за улучшение должна лежать на поставщике компилятора.
Rwong
15
Вам не нужно отлаживать ассемблерный код, если только вы не находитесь на критическом пути производительности. Также "чистый код"! = "Хорошие показатели".
BЈовић
Исправьте свой отступ, пожалуйста. Я пытался сделать это, но, кажется, вы не можете редактировать только пробелы.
Кристофер Хаммарстрем
3
@Heather: Вы, кажется, используете стиль Ratliff , который я никогда раньше не видел, и который трудно прочитать. Это, безусловно, один из менее известных. У меня сложилось впечатление, что вы не сделали отступ правильно. Не берите в голову тогда, если Вы находите это читабельным, я просто не согласен.
Кристофер Хаммарстрём,
1
Он ifполностью избыточен в коде примера, и хотя компилятор, вероятно, поймает, что нет причин искушать неверный прогноз ветвления.
dmckee

Ответы:

59

Должен ли я думать о скомпилированном машинном коде, когда пишу свой код?

Нет , не тогда, когда вы пишете свой код в первый раз и не страдаете от реальных, измеримых проблем с производительностью. Для большинства задач это стандартный случай. Слишком рано думать об оптимизации называют «преждевременной оптимизацией», и есть веские причины, по которым Д. Кнут назвал это «корнем всего зла» .

Да , когда вы измеряете реальное, доказуемое узкое место в производительности и определяете именно эту лямбда-конструкцию как основную причину. В этом случае может быть хорошей идеей вспомнить «закон утечек абстракций» Джоэла Спольски и подумать о том, что может произойти на уровне ассемблера. Но будьте осторожны, вы можете быть удивлены, насколько незначительным будет увеличение производительности, когда вы заменяете лямбда-конструкцию «не очень современной» языковой конструкцией (по крайней мере, при использовании достойного компилятора C ++).

Док Браун
источник
2
+1 Краткий, точный и понятный для обычного Дока, рад, что вы здесь.
Джимми Хоффа
Договорились, очень четкий ответ.
cnd
8

Выбор между лямбда-классом и функтором является компромиссом.

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

С точки зрения производительности это не хуже, чем класс функторов , который является структурой или классом C ++, который содержит один «метод». Фактически, компиляторы обрабатывают лямбду не иначе, как сгенерированный компилятором класс функторов за сценой.

// define the functor method somewhere
struct some_computer_generated_gibberish_0123456789
{
    int operator() (int x) const
    {
        if (x == 2) return 5;
        if (x == 3) return 6;
        return 0;
    }
};

// make a call
some_computer_generated_gibberish_0123456789 an_instance_of_0123456789;
int outputValue = an_instance_of_0123456789(inputValue);

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

int some_computer_generated_gibberish_0123456789_method_more_gibberish(int x)
{
    if (...) return ...;
    return ...;
}

Отладка любого нетривиального кода C ++ с использованием дизассемблера всегда была сложной задачей. Это верно с использованием лямбды или без нее. Это вызвано сложной оптимизацией кода компилятором C ++, которая привела к переупорядочению, перемежению и удалению мертвого кода.

Аспект искажения имен несколько неприятен, и поддержка лямбды в отладчике все еще находится в зачаточном состоянии . Можно только надеяться, что поддержка отладчика со временем улучшится.

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

rwong
источник
3

Чтобы добавить к ответу @DocBrown, помните, что в наши дни центральные процессоры дешевы, но рабочая сила стоит дорого.

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

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

Падди Ландау
источник
Только отчасти правда. Если ваш код работает O (n ^ 2) (квадратично), и вы можете сделать что-то лучше, скажем, O (log (n)) (логарифмически), то аппаратное обеспечение никогда не будет сильно увеличивать производительность при изменении кода. В случае, указанном оригинальным постером, это маловероятно.
gnash117
@ gnash117 - да, вы правы, если код будет запускаться много раз; спасибо за указание на это. В таких случаях четкое документирование кода будет поддерживать его в обслуживании, позволяя при этом повысить производительность.
Пэдди Ландау
«Труд дорогой» - правильно. Время вашего клиента очень важно и часто дорого.
Cerad