我试图从www.spoj.com解决此练习:FCTRL - Factorial
你真的不必阅读它,只要你好奇就去做吧。)
首先我在 C ++ 中实现了它(这是我的解决方案):
#include <iostream>
using namespace std;
int main() {
unsigned int num_of_inputs;
unsigned int fact_num;
unsigned int num_of_trailing_zeros;
std::ios_base::sync_with_stdio(false); // turn off synchronization with the C library’s stdio buffers (from https://stackoverflow.com/a/22225421/5218277)
cin >> num_of_inputs;
while (num_of_inputs--)
{
cin >> fact_num;
num_of_trailing_zeros = 0;
for (unsigned int fives = 5; fives <= fact_num; fives *= 5)
num_of_trailing_zeros += fact_num/fives;
cout << num_of_trailing_zeros << "\n";
}
return 0;
}
我上传了它作为 g ++ 5.1
的解决方案但后来我看到一些评论声称他们的执行时间不到0.1。由于我无法考虑更快的算法,我尝试在 C :
中实现相同的代码#include <stdio.h>
int main() {
unsigned int num_of_inputs;
unsigned int fact_num;
unsigned int num_of_trailing_zeros;
scanf("%d", &num_of_inputs);
while (num_of_inputs--)
{
scanf("%d", &fact_num);
num_of_trailing_zeros = 0;
for (unsigned int fives = 5; fives <= fact_num; fives *= 5)
num_of_trailing_zeros += fact_num/fives;
printf("%d", num_of_trailing_zeros);
printf("%s","\n");
}
return 0;
}
我上传了它作为 gcc 5.1
的解决方案现在代码几乎相同,我将std::ios_base::sync_with_stdio(false);
添加到C ++代码中,如同建议here一样,以关闭与C库的stdio缓冲区的同步。我还将printf("%d\n", num_of_trailing_zeros);
拆分为printf("%d", num_of_trailing_zeros); printf("%s","\n");
以补偿operator<<
中cout << num_of_trailing_zeros << "\n";
的双重调用。
但我仍然看到 x9更好的性能并降低C与C ++代码中的内存使用量。
为什么?
修改
我在C代码中将unsigned long
修复为unsigned int
。它应该是unsigned int
,上面显示的结果与新的(unsigned int
)版本相关。
答案 0 :(得分:56)
两个程序完全相同。它们使用相同的精确算法,并且由于其低复杂性,它们的性能主要受输入和输出处理效率的限制。
在一侧用scanf("%d", &fact_num);
扫描输入,在另一侧用cin >> fact_num;
扫描输入,无论如何都不是很昂贵。事实上,它在C ++中的成本应该更低,因为转换类型在编译时是已知的,并且正确的解析器可以由C ++编译器直接调用。输出也是如此。你甚至要为printf("%s","\n");
编写单独的调用,但C编译器足以将其编译为对putchar('\n');
的调用。
因此,考虑到I / O和计算的复杂性,C ++版本应该比C版本更快。
完全禁用stdout
的缓冲会使C实现速度慢于C ++版本。 AlexLop在最后fflush(stdout);
之后使用printf
进行的另一项测试产生与C ++版本类似的性能。它没有完全禁用缓冲那么慢,因为输出以小块而不是一次一个字节写入系统。
这似乎指向C ++库中的特定行为:我怀疑当{{1}请求输入时,系统的cin
和cout
实现会将输出刷新到cout
}}。一些C库也这样做,但通常只在读/写终端时才这样做。 www.spoj.com网站完成的基准测试可能会重定向文件的输入和输出。
AlexLop做了另一个测试:在向量中一次读取所有输入,然后计算和写入所有输出有助于理解为什么C ++版本要慢得多。它将性能提升到C版本的性能,这证明了我的观点并消除了对C ++格式代码的怀疑。
Blastfurnace的另一项测试,将所有输出存储在一个cin
中,并在最后一次冲洗中,确实将C ++性能提高到基本C版本的性能。 QED。
从
std::ostringstream
和输出到cin
的隔行输入似乎导致非常低效的I / O处理,从而破坏了流缓冲方案。将性能降低10倍。
PS:cout
的算法不正确,因为fact_num >= UINT_MAX / 5
会溢出并在变为fives *= 5
之前回绕。如果其中一种类型大于> fact_num
,则可以fives
unsigned long
或unsigned long long
来更正此问题。同时使用unsigned int
作为%u
格式。你很幸运www.spoj.com的人在他们的基准测试中并不是太严格。
编辑:正如后来的vitaux所解释的那样,这种行为确实是由C ++标准强制执行的。默认情况下,scanf
与cin
绑定。来自cout
的输入缓冲区需要重新填充的输入操作将导致cin
刷新挂起的输出。在OP的实现中,cout
似乎系统地刷新cin
,这有点矫枉过正,效率明显低。
伊利亚·波波夫为此提供了一个简单的解决方案:除了cout
之外,还可以通过施放另一个魔法咒语来解除cin
cout
:
std::ios_base::sync_with_stdio(false);
另请注意,使用cin.tie(nullptr);
代替std::endl
在'\n'
上生成行尾时,也会发生强制同花。将输出行更改为更多C ++惯用且无辜的cout
会以相同的方式降低性能。
答案 1 :(得分:44)
同时使用iostream
和cin
时,让cout
更快的另一个技巧是调用
cin.tie(nullptr);
默认情况下,当您从cin
输入任何内容时,它会刷新cout
。如果进行交错输入和输出,则会严重损害性能。这是为命令行界面使用完成的,在那里显示一些提示,然后等待数据:
std::string name;
cout << "Enter your name:";
cin >> name;
在这种情况下,您需要确保在开始等待输入之前实际显示提示。通过上面的一行,您可以打破这种平局,cin
和cout
变得独立。
从C ++ 11开始,使用iostream实现更好性能的另一种方法是将std::getline
与std::stoi
一起使用,如下所示:
std::string line;
for (int i = 0; i < n && std::getline(std::cin, line); ++i)
{
int x = std::stoi(line);
}
这种方式可以接近C风格的性能,甚至超过scanf
。使用getchar
,特别是getchar_unlocked
和手写解析仍可提供更好的性能。
PS。我写了a post比较了几种用C ++输入数字的方法,这对于在线评委很有用,但是只有俄语,对不起。但是,代码示例和最终表格应该是可以理解的。
答案 2 :(得分:27)
问题是,引用cppreference:
来自std :: cin的任何输入,输出到std :: cerr,或程序终止强制调用std :: cout.flush()
这很容易测试:如果你替换
cin >> fact_num;
与
scanf("%d", &fact_num);
和cin >> num_of_inputs
相同,但保持cout
你将在C ++版本(或者说IOStream版本)中获得与C一样相同的性能:
如果您保留cin
但替换
cout << num_of_trailing_zeros << "\n";
与
printf("%d", num_of_trailing_zeros);
printf("%s","\n");
一个简单的解决方案是解开Ilya Popov提到的cout
和cin
:
cin.tie(nullptr);
在某些情况下,允许标准库实现省略对flush的调用,但并非总是如此。这是C ++ 14 27.7.2.1.3的引用(感谢chqrlie):
类basic_istream :: sentry:首先,如果is.tie()不是空指针,则函数调用is.tie() - &gt; flush()以使输出序列与任何关联的外部C流同步。除非is.tie()的put区域为空,否则可以抑制此调用。此外,允许实现将调用推迟到刷新,直到发生is.rdbuf() - &gt; underflow()的调用。如果在销毁岗哨对象之前没有发生此类调用,则可以完全取消对flush的调用。