Как узнать, как компилятор С++ реализует что-то, кроме проверки испускаемого машинного кода?

Предположим, что я создал набор классов для абстрагирования чего-то, и теперь я беспокоюсь, сможет ли мой компилятор С++ очистить эти обертки и испустить действительно чистый, сжатый и быстрый код. Как узнать, что решил компилятор?

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

Как еще я могу узнать, как компилятор решил реализовать то, что я закодировал на С++?

Ответ 1

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

Ответ 2

Я действительно интересовался этим.

Мне очень интересны последние несколько месяцев в проекте Clang.

Один из особых интересов Clang, wrt optimization, заключается в том, что вы можете испускать оптимизированный ИК-код LLVM вместо машинного кода. IR - это высокоуровневый язык ассемблера с понятием структуры и типа.

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

Я знаю, что это еще сборка (хотя и более высокого уровня), но мне кажется более читаемой:

  • гораздо меньше опкодов
  • набранные объекты/указатели
  • нет "зарегистрировать" вещи или "магические" знания.

Это вам подойдет:)?

Ответ 3

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

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

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

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

Ответ 4

Вы можете найти компилятор, у которого была возможность сбросить пост-оптимизированное AST/представление - как это было бы понятно, это другое дело. Если вы используете GCC, есть шанс, что это будет не слишком сложно, и кто-то, возможно, уже это сделал, - GCCXML делает что-то смутное сходство. Нечего использовать, если компилятор, который вы хотите создать свой производственный код, не может этого сделать.

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

Что касается упомянутых недостатков:

компилятор может сделать это по-другому, когда он снова скомпилирует тот же код

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

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

Возникает вопрос: что было бы лучше. Я могу изобразить какой-то процесс, в котором вы запускаете компилятор над своим кодом, и записывает, когда переменные кэшируются в регистры в точках использования, какие вызовы функций встроены, даже максимальное количество циклов процессора, которые может потребоваться для команды (когда это известно во время компиляции ) и т.д., и производит их некоторую запись, затем источник/редактор источника, который кодирует цвета и соответствующим образом комментирует источник. Это то, что вы имеете в виду? Было бы полезно? Возможно, несколько больше, чем другие. черно-белая информация об использовании регистра игнорирует полезность различных уровней кэша процессора (и использования во время выполнения); компилятор, вероятно, даже не пытается моделировать это в любом случае. Знать, где была сделана настоящая инфильтрация, дало бы мне теплое нечеткое чувство. Но профилирование кажется более перспективным и полезным в целом. Я боюсь, что преимущества более интуитивно реальны, чем на самом деле, а авторы компиляторов лучше избегают использования функций С++ 0x, инструментария времени выполнения, самоанализа или записи D "на стороне"; -).

Ответ 5

Вы хотите знать, создал ли компилятор "чистый, сжатый и быстрый код".

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

"Краткий код" имеет два значения. Для исходного кода речь идет о сохранении скудных ресурсов программиста и мозговых ресурсов, но, как я уже указывал выше, это не относится к выходу компилятора, поскольку в данный момент нет человека. Другим значением является компактный код, который, таким образом, имеет меньшую стоимость хранения. Это может повлиять на скорость выполнения, поскольку оперативная память медленная, и, следовательно, вы действительно хотите, чтобы самые внутренние петли вашего кода соответствовали кэшу уровня 1 процессора. Размер функций, создаваемых компилятором, может быть получен с помощью некоторых инструментов разработчика; в системах, которые используют GNU binutils, вы можете использовать команду size, чтобы получить общий размер кода и данных в объектном файле (скомпилированный .o) и objdump, чтобы получить дополнительную информацию. В частности, objdump -x даст размер каждой отдельной функции.

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

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

Ответ 6

Ответ на ваш вопрос был в значительной степени пригвожден Карлом. Если вы хотите посмотреть, что сделал компилятор, вам нужно начать с кода сборки, который он произвел - требуется смазка локтя. Что касается раскрытия "почему" за "как" того, как он реализовал ваш код... каждый компилятор (и каждая сборка, потенциально), как вы упомянули, отличается. Существуют разные подходы, разные оптимизации и т.д. Однако я не стал бы беспокоиться о том, следует ли исправить чистый, сжатый машинный код - чистота и краткость следует оставить в исходном коде. Скорость, с другой стороны, в значительной степени является ответственностью программиста (профилирование ftw). Более интересные проблемы - это правильность, ремонтопригодность, удобочитаемость и т.д. Если вы хотите увидеть, сделала ли он определенную оптимизацию, документы-компиляторы могут помочь (если они доступны для вашего компилятора). Вы также можете просто попробовать выполнить поиск, чтобы убедиться, что компилятор реализует известный метод оптимизации. Если эти подходы потерпят неудачу, вы вернетесь к чтению кода сборки. Имейте в виду, что код, который вы проверяете, может практически не влиять на производительность или размер исполняемого файла - захватите некоторые жесткие данные перед погружением в любой из этих материалов.

Ответ 7

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

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

Ответ 8

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

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