繁体   English   中英

为什么 '\\n' 优先于输出流的“\\n”?

[英]Why is '\n' preferred over "\n" for output streams?

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

我想使用'\\n'或使用"\\n"之间几乎没有区别,但后者是一个(两个)字符的数组,必须逐个字符地打印,必须为其设置一个循环,其中比输出单个字符更复杂

强调我的

这对我来说很有意义。 我认为输出一个const char*需要一个循环来测试空终止符,它必须引入比简单的putchar更多的操作(并不意味着带有char std::cout委托调用它 - 这只是介绍一个例子的简化)。

这说服我使用

std::cout << '\n';
std::cout << ' ';

而不是

std::cout << "\n";
std::cout << " ";

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

最近,我为使用后一种方法的人做了一些小的代码审查。 我对这个案子做了一个小小的评论,然后继续前进。 开发者随后感谢我并说他根本没有想到这种差异(主要是在意图上)。 它根本没有影响(不出所料),但更改被采用。

然后我开始想知道这个变化到底多大意义,所以我跑到了 Godbolt。 令我惊讶的是,在带有-std=c++17 -O3标志的 GCC(主干)上测试时,它显示了以下结果 为以下代码生成的程序集:

#include <iostream>

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

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

int main() {
    str();
    chr();
}

我很惊讶,因为看起来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

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

其他答案都没有真正解释为什么编译器会生成它在您的 Godbolt 链接中执行的代码,所以我想我会插手。

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

std::cout << '\n';

编译为,实际上:

const char c = '\n';
std::cout.operator<< (&c, 1);

为了使这项工作,编译器必须为函数chr()生成一个堆栈帧,这是许多额外指令的来源。

另一方面,在编译时:

std::cout << "\n";

编译器可以将str()优化为简单的 'tail call' operator<< (const char *) ,这意味着不需要堆栈帧。

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

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

请记住,您在程序集中看到的只是调用堆栈的创建,而不是实际函数的执行。

std::cout << '\\n'; 仍有很大略快于std::cout << "\\n";

我创建了这个小程序来测量性能,它在我的机器上使用 g++ -O3 稍微快了 20 倍 自己试试吧!

编辑:抱歉注意到我的程序中有错字,而且速度并没有那么快! 几乎无法衡量任何差异了。 有时一个更快。 其他时间其他。

#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();
}

编辑:正如 geza 建议的那样,我尝试了 100000000 次迭代并将其发送到 /dev/null 并运行了四次。 '\\n' 曾经更慢,快 3 倍,但从未如此之快,但在其他机器上可能会有所不同:

Nanoseconds: 8668263707
Nanoseconds: 7236055911

Nanoseconds: 10704225268
Nanoseconds: 10735594417

Nanoseconds: 10670389416
Nanoseconds: 10658991348

Nanoseconds: 7199981327
Nanoseconds: 6753044774

我想总的来说我不会太在意。

是的,对于这个特定的实现,例如, char版本比 string 版本慢一点。

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

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM