为什么输出流优先使用“ \ n”而不是“ \ n”?

Fur*_*ish 29 c++ performance cout string-literals

这个答案中,我们可以看到:

我猜想using '\n'或using 之间没有什么区别"\n",但是后者是一个(两个)字符的数组,必须逐个字符地打印,为此必须建立一个循环,这比输出单个字符更为复杂。

重点矿

这对我来说很有意义。我认为,输出const char*需要一个循环,这将测试空终止符,其必须推出比更多的操作,比方说,一个简单的putchar(不是暗示std::coutchar代表们呼吁-它只是引进一个实例的简化)。

说服我使用

std::cout << '\n';
std::cout << ' ';
Run Code Online (Sandbox Code Playgroud)

而不是

std::cout << "\n";
std::cout << " ";
Run Code Online (Sandbox Code Playgroud)

在这里值得一提的是,我知道性能差异几乎可以忽略不计。但是,有些人可能会争辩说,前一种方法的意图是实际上传递单个字符,而不是恰好是一个char长字符(如果算上是2 char s '\0')的字符串文字。

最近,我为使用后一种方法的人做了一些代码修改。我对此案发表了一点评论,然后继续。然后,开发人员感谢我,并说他甚至都没有想到这种差异(主要集中在意图上)。完全没有影响(毫不奇怪),但是采用了更改。

然后,我开始怀疑这种变化到底有多重要,所以我狂奔。令我惊讶的是,当在带有标志的GCC(树干)上进行测试时,它显示出以下结果-std=c++17 -O3。为以下代码生成的程序集:

#include <iostream>

void str() {
    std::cout << "\n";
}

void chr() {
    std::cout << '\n';
}

int main() {
    str();
    chr();
}
Run Code Online (Sandbox Code Playgroud)

我很惊讶,因为看起来chr()实际上生成的指令实际上是生成指令的两倍str()

.LC0:
        .string "\n"
str():
        mov     edx, 1
        mov     esi, OFFSET FLAT:.LC0
        mov     edi, OFFSET FLAT:_ZSt4cout
        jmp     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)
chr():
        sub     rsp, 24
        mov     edx, 1
        mov     edi, OFFSET FLAT:_ZSt4cout
        lea     rsi, [rsp+15]
        mov     BYTE PTR [rsp+15], 10
        call    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)
        add     rsp, 24
        ret
Run Code Online (Sandbox Code Playgroud)

这是为什么?为什么他们最终都std::basic_ostreamconst char*参数调用同一个函数?这是否意味着char文字方法不仅比字符串文字方法,而且实际上更糟糕

Pau*_*ers 29

其他答案都无法真正解释为什么编译器会生成您在Godbolt链接中执行的代码,因此我认为自己会参与其中。

如果查看生成的代码,可以看到:

std::cout << '\n';
Run Code Online (Sandbox Code Playgroud)

编译为:

const char c = '\n';
std::cout.operator<< (&c, 1);
Run Code Online (Sandbox Code Playgroud)

为此,编译器必须为function生成一个堆栈框架chr(),这是许多额外指令的来源。

另一方面,在编译时:

std::cout << "\n";
Run Code Online (Sandbox Code Playgroud)

编译器可以优化str()为简单的“尾调用” operator<< (const char *),这意味着不需要堆栈框架。

因此,将调用operator<<放在单独的函数中会使您的结果有些不正确。内联进行这些调用更具启发性,请参阅:https : //godbolt.org/z/OO-8dS

现在您可以看到,尽管输出'\n'仍然要贵一点(因为没有特定的重载ofstream::operator<< (char)),但差异没有您的示例那么明显。

  • @Fureeish是的,我也很惊讶。我在Godbolt中进行了简短的检查,Clang与gcc的功能相同。另一方面,MSVC似乎具有特定的重载`operator &lt;&lt;(char)`,请参阅:https://godbolt.org/z/AQiyMw (5认同)
  • 人们常常会忘记`&lt;&lt;`是*格式化的*输出-需要根据流的宽度/填充/标志填充字符。这是一个不错的代码块,可以在`char`和`const char *`之间重用,所以我对它们共享一个通用实现并不感到惊讶。如果您只想输出一个字符,则可以使用未格式化的`put`。 (2认同)

gez*_*eza 5

是的,对于您的示例,对于此特定实现,char版本比字符串版本慢一点。

这两个版本都调用write(buffer, bufferSize)样式函数。对于字符串版本,bufferSize在编译时(1个字节)是已知的,因此无需查找零终止符运行时。对于该char版本,编译器会在堆栈上创建一个1字节的缓冲区,将字符放入其中,然后将该缓冲区传递出去以进行写出。因此,该char版本要慢一些。


Mic*_*ahn 5

Keep in mind though that what you see in the assembly is only the creation of the callstack, not the execution of the actual function.

std::cout << '\n'; is still much slightly faster than std::cout << "\n";

I've created this little program to measure the performance and it's about 20 times slightly faster on my machine with g++ -O3. Try it yourself!

Edit: Sorry noticed typo in my program and it's not that much faster! Can barely measure any difference anymore. Sometimes one is faster. Other times the other.

#include <chrono>
#include <iostream>

class timer {
    private:
        decltype(std::chrono::high_resolution_clock::now()) begin, end;

    public:
        void
        start() {
            begin = std::chrono::high_resolution_clock::now();
        }

        void
        stop() {
            end = std::chrono::high_resolution_clock::now();
        }

        template<typename T>
        auto
        duration() const {
            return std::chrono::duration_cast<T>(end - begin).count();
        }

        auto
        nanoseconds() const {
            return duration<std::chrono::nanoseconds>();
        }

        void
        printNS() const {
            std::cout << "Nanoseconds: " << nanoseconds() << std::endl;
        }
};

int
main(int argc, char** argv) {
    timer t1;
    t1.start();
    for (int i{0}; 10000 > i; ++i) {
        std::cout << '\n';
    }
    t1.stop();

    timer t2;
    t2.start();
    for (int i{0}; 10000 > i; ++i) {
        std::cout << "\n";
    }
    t2.stop();
    t1.printNS();
    t2.printNS();
}
Run Code Online (Sandbox Code Playgroud)

Edit: As geza suggested I tried 100000000 iterations for both and sent it to /dev/null and ran it four times. '\n' was once slower and 3 times faster but never by much, but it might be different on other machines:

Nanoseconds: 8668263707
Nanoseconds: 7236055911

Nanoseconds: 10704225268
Nanoseconds: 10735594417

Nanoseconds: 10670389416
Nanoseconds: 10658991348

Nanoseconds: 7199981327
Nanoseconds: 6753044774
Run Code Online (Sandbox Code Playgroud)

I guess overall I wouldn't care too much.

  • 您应该使用更大的迭代计数,并将stdout重定向到某个文件或`/ dev / null`。然后,差异应该小得多(因为我们不想基准噪声,CPU的动态频率缩放,控制台输出速度等)。 (4认同)
  • 已使用MSVC构建并重定向到NUL运行了100000000次迭代。现在桌子已经翻了。在3次运行中,“ char”输出始终快一些,但速度并不快,平均而言,“ char”输出快了1.7%。 (3认同)
  • 另外,我认为当需要在换行符之前插入字符时,“\n”更容易维护。如果我写“xyz\n”,MSVC 不会抱怨,因为这很容易在匆忙中被忽略。 (2认同)
  • 几乎可以肯定,您的性能结果主要由刷新控制,因为您尚未关闭`sync_with_stdio`。 (2认同)