为什么 '\n' 对于输出流比 "\n" 更受欢迎?
Why is '\n' preferred over "\n" for output streams?
在this答案中我们可以读到:
I suppose there's little difference between using '\n'
or using "\n"
, but the latter is an array of (two) characters, which has to be printed character by character, for which a loop has to be set up, which is more complex than outputting a single character.
强调我的
这对我来说很有意义。我认为输出 const char*
需要一个循环来测试空终止符, 必须 引入比简单的 putchar
(并不意味着 std::cout
和 char
委托调用它 - 这只是介绍示例的简化)。
这说服了我使用
std::cout << '\n';
std::cout << ' ';
而不是
std::cout << "\n";
std::cout << " ";
这里值得一提的是,我知道性能差异几乎可以忽略不计。尽管如此,有些人可能会争辩说,前一种方法的意图实际上是传递单个字符,而不是恰好是 char
长的字符串文字(two char
s long if you count the '[=22=]'
).
最近我为使用后一种方法的人做了一些小的代码审查。我对这个案子发表了一点评论,然后继续前进。开发人员随后向我表示感谢,并表示他根本没有想到这种差异(主要看意图)。它根本没有影响(不出所料),但更改被采纳了。
然后我开始想 这个变化到底有多重要,所以我 运行 神马。令我惊讶的是,在带有 -std=c++17 -O3
标志的 GCC(主干)上进行测试时,它显示了 following results。为以下代码生成的程序集:
#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
文字方法不仅 没有更好 ,而且实际上 比字符串文字方法更差?
是的,对于这个特定的实现,对于您的示例,char
版本比字符串版本慢一点。
两个版本都调用了 write(buffer, bufferSize)
风格的函数。对于字符串版本,bufferSize
在编译时已知(1 字节),因此不需要在 运行 时寻找零终止符。对于 char
版本,编译器在堆栈上创建一个 1 字节的小缓冲区,将字符放入其中,然后将此缓冲区传递给写出。所以,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
我想总的来说我不会太在意。
None 的其他答案确实解释了为什么编译器会生成它在您的 Godbolt 中生成的代码 link,所以我想我会参与其中。
如果您查看生成的代码,您会发现:
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)
没有特定的重载),但差异不如您的示例明显。
在this答案中我们可以读到:
I suppose there's little difference between using
'\n'
or using"\n"
, but the latter is an array of (two) characters, which has to be printed character by character, for which a loop has to be set up, which is more complex than outputting a single character.
强调我的
这对我来说很有意义。我认为输出 const char*
需要一个循环来测试空终止符, 必须 引入比简单的 putchar
(并不意味着 std::cout
和 char
委托调用它 - 这只是介绍示例的简化)。
这说服了我使用
std::cout << '\n';
std::cout << ' ';
而不是
std::cout << "\n";
std::cout << " ";
这里值得一提的是,我知道性能差异几乎可以忽略不计。尽管如此,有些人可能会争辩说,前一种方法的意图实际上是传递单个字符,而不是恰好是 char
长的字符串文字(two char
s long if you count the '[=22=]'
).
最近我为使用后一种方法的人做了一些小的代码审查。我对这个案子发表了一点评论,然后继续前进。开发人员随后向我表示感谢,并表示他根本没有想到这种差异(主要看意图)。它根本没有影响(不出所料),但更改被采纳了。
然后我开始想 这个变化到底有多重要,所以我 运行 神马。令我惊讶的是,在带有 -std=c++17 -O3
标志的 GCC(主干)上进行测试时,它显示了 following results。为以下代码生成的程序集:
#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
文字方法不仅 没有更好 ,而且实际上 比字符串文字方法更差?
是的,对于这个特定的实现,对于您的示例,char
版本比字符串版本慢一点。
两个版本都调用了 write(buffer, bufferSize)
风格的函数。对于字符串版本,bufferSize
在编译时已知(1 字节),因此不需要在 运行 时寻找零终止符。对于 char
版本,编译器在堆栈上创建一个 1 字节的小缓冲区,将字符放入其中,然后将此缓冲区传递给写出。所以,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
我想总的来说我不会太在意。
None 的其他答案确实解释了为什么编译器会生成它在您的 Godbolt 中生成的代码 link,所以我想我会参与其中。
如果您查看生成的代码,您会发现:
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)
没有特定的重载),但差异不如您的示例明显。