Оптимизированы ли когда-нибудь строковые литералы из 1 символа для простых литералов типа char?

Часто при кодировании на C ++ я заканчиваю cout заявления с новой строки (\n). Тем не менее, мой инстинкт всегда заключался в выражении этой новой строки в виде строкового литерала: "\n", хотя это один символ и может быть более эффективно выражен как символ'\n').
Например:

cout << "The value of var is " << var << "\n";

Eсть много кода с этим явлением. Итак, вопрос в следующем:

  1. Есть какая разница в эффективности двух разных способов выражения константы символа новой строки? Меня не волнует какая-либо реальная разница в выполнении созданной программы (что, я думаю, было бы тривиально); Скорее, меня просто беспокоит то, что какая-то эффективность, хотя и незначительная, может быть потеряна без причины.

  2. Если строково-буквенная версия менее эффективна, оптимизирует ли ее компилятор до версии с символьной константой, поскольку эти два варианта обеспечивают одинаковое поведение?

  3. Я также знаком с std::endl, В документации сказано, что «этот манипулятор часто ошибочно используется, когда требуется простой перевод строки, что приводит к снижению производительности буферизации». И указывает на Эта статья для дополнительной информации. Однако в этой статье говорится, что упомянутая «низкая производительность» относится только к файловому вводу-выводу и что при использовании endl для записи на экран может реально улучшить производительность. Что за дело с этим?

Я искал стандартную библиотеку C ++, но не смог найти реализации соответствующих перегрузок << оператор. Я нашел декларации в ostream.tcc:

extern template ostream& operator<<(ostream&, char);
extern template ostream& operator<<(ostream&, const char*);

Но нет никаких подсказок относительно того, как механика сводится к реализации.

Это скорее теоретический вопрос, чем что-либо еще, поэтому мне не интересно читать «Нет практичное разница между этими двумя. «Я знаю это. Мне просто интересно, есть ли какая-либо разница вообще и как компилятор справляется с этим.

7

Решение

Они, вероятно, оптимизированы под одну строку (на единицу компиляции) — большинство компиляторов «объединяют строки одного и того же содержимого».

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

На ваши конкретные вопросы:

  1. Да, есть небольшая разница, как char * потребует некоторого косвенного обращения и, следовательно, сгенерирует несколько дополнительных инструкций для выполнения. Для вывода на консоль (а не на вывод в файл) это не важно, поскольку прокрутка консоли даже в полноэкранном текстовом режиме в> 100 раз больше инструкций.
  2. Сомневаюсь.
  3. Так std::endl очистит буфер, который действительно уменьшает вывод в файлы, потому что частичные сектора или блоки записываются в файл, что увеличивает издержки системного вызова. Если вы используете "\n"файл не очищается до тех пор, пока сам буфер не будет заполнен, который будет занимать не менее 512 байт, возможно, до нескольких десятков килобайт. Но что касается ответа № 1, производительность вывода консоли будет больше зависеть от скорости, с которой экран может прокручиваться.
3

Другие решения

Различия между строковым литералом \n а также endl в том, что:

\n строковый литерал, который добавляется к stdout.
endl также добавит символ новой строки в stdout, однако он также очистит буфер stdout. Следовательно, может потребоваться больше обработки. Кроме этого, не должно быть никакой практической разницы.

2

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

Тем не менее, если компилятор делает достаточно агрессивное встраивание (встраивание самой функции и постоянных данных в функцию), вы можете в итоге получить тот же код. Например, Clang компилирует следующее:

#include <iostream>
using namespace std;

int main() {
cout << "X" << "\n";
cout << "Y" << '\n';
}

в это:

movq    std::cout@GOTPCREL(%rip), %rbx
leaq    L_.str(%rip), %rsi
movq    %rbx, %rdi
movl    $1, %edx
callq   std::basic_ostream<char, std::char_traits<char> >& std::__ostream_insert<char, std::char_traits<char> >(std::basic_ostream<char, std::char_traits<char> >&, char const*, long)
leaq    L_.str1(%rip), %rsi
movq    %rbx, %rdi
movl    $1, %edx
callq   std::basic_ostream<char, std::char_traits<char> >& std::__ostream_insert<char, std::char_traits<char> >(std::basic_ostream<char, std::char_traits<char> >&, char const*, long)
leaq    L_.str2(%rip), %rsi
movq    %rbx, %rdi
movl    $1, %edx
callq   std::basic_ostream<char, std::char_traits<char> >& std::__ostream_insert<char, std::char_traits<char> >(std::basic_ostream<char, std::char_traits<char> >&, char const*, long)
leaq    -9(%rbp), %rsi
movb    $10, -9(%rbp)
movq    %rbx, %rdi
movl    $1, %edx
callq   std::basic_ostream<char, std::char_traits<char> >& std::__ostream_insert<char, std::char_traits<char> >(std::basic_ostream<char, std::char_traits<char> >&, char const*, long)
xorl    %eax, %eax
addq    $8, %rsp
popq    %rbx
popq    %rbp

Как вы можете видеть, встраивание сделало два случая практически идентичными. (И, на самом деле, '\n' case немного сложнее, потому что персонаж должен быть помещен в стек.)

1
По вопросам рекламы [email protected]