私は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++のコードのメモリ使用量の削減を見ました。
何故ですか?
[〜#〜] edit [〜#〜]
Cコードで_unsigned long
_を_unsigned int
_に修正しました。これは_unsigned int
_であるはずであり、上記の結果は新しい(_unsigned int
_)バージョンに関連しています。
両方のプログラムはまったく同じことを行います。それらは同じ正確なアルゴリズムを使用し、その複雑さが低いことを考えると、パフォーマンスは主に入出力処理の効率に制限されます。
一方をscanf("%d", &fact_num);
で、もう一方をcin >> fact_num;
で入力をスキャンすることは、どちらの方法でもそれほど費用がかかりそうにありません。実際、C++では変換の種類がコンパイル時にわかっており、C++コンパイラーによって正しいパーサーを直接呼び出すことができるため、C++ではコストが低くなります。出力についても同じことが言えます。あなたは、printf("%s","\n");
の別の呼び出しを書くことさえしますが、Cコンパイラはこれをputchar('\n');
の呼び出しとしてコンパイルするのに十分です。
したがって、I/Oと計算の両方の複雑さを見ると、C++バージョンはCバージョンよりも高速であるはずです。
stdout
のバッファリングを完全に無効にすると、C実装がC++バージョンよりもさらに遅くなります。最後のprintf
の後にfflush(stdout);
を指定したAlexLopによる別のテストでは、C++バージョンと同様のパフォーマンスが得られます。出力は一度に1バイトではなく小さなチャンクでシステムに書き込まれるため、バッファリングを完全に無効にするほど遅くはありません。
これは、C++ライブラリの特定の動作を指しているようです:cin
のシステムの実装は、cout
からの入力が要求されたときに、cout
の出力をcin
にフラッシュします。一部のCライブラリもこれを行いますが、通常は端末との間で読み取り/書き込みを行う場合のみです。 www.spoj.comサイトで行われたベンチマークは、おそらくファイルとの間で入出力をリダイレクトします。
AlexLopは別のテストを行いました。ベクトル内のすべての入力を一度に読み取り、続いてすべての出力を計算して書き込むと、C++バージョンが非常に遅い理由を理解するのに役立ちます。 Cバージョンよりもパフォーマンスが向上し、これが私のポイントを証明し、C++フォーマットコードに対する疑念を取り除きます。
Blastfurnaceによる別のテストでは、すべての出力をstd::ostringstream
に保存し、最後に1つのブラストでフラッシュすると、C++のパフォーマンスが基本的なCバージョンのパフォーマンスに向上します。 QED。
cin
からの入力とcout
への出力をインターレースすると、I/O処理が非常に非効率的になり、ストリームバッファリングスキームが無効になるようです。パフォーマンスを10分の1に削減します。
PS:fact_num >= UINT_MAX / 5
は、fives *= 5
になる前にオーバーフローしてラップアラウンドするため、アルゴリズムは> fact_num
に対して正しくありません。これらのタイプのいずれかがunsigned long
より大きい場合、fives
をunsigned long long
またはunsigned int
にすることでこれを修正できます。また、%u
をscanf
形式として使用します。幸運なことに、www.spoj.comのメンバーは、ベンチマークが厳しすぎていません。
編集:後でvitauxで説明したように、この動作は実際にC++標準によって義務付けられています。 cin
はデフォルトでcout
に関連付けられています。入力バッファの補充が必要なcin
からの入力操作により、cout
が保留中の出力をフラッシュします。 OPの実装では、cin
はcout
を体系的にフラッシュするように見えますが、これは少し過剰であり、明らかに非効率的です。
イリヤ・ポポフは、このための簡単な解決策を提供しました。cin
は、std::ios_base::sync_with_stdio(false);
に加えて別の魔法の呪文を唱えることにより、cout
から解くことができます。
cin.tie(nullptr);
また、このような強制フラッシュは、std::endl
の代わりに'\n'
を使用してcout
の行末を生成するときにも発生することに注意してください。出力行をよりC++の慣用的で無邪気なcout << num_of_trailing_zeros << endl;
に変更すると、同様にパフォーマンスが低下します。
iostream
とcin
の両方を使用するときにcout
sを高速化する別のトリックは、
cin.tie(nullptr);
デフォルトでは、cin
から何かを入力すると、cout
がフラッシュされます。インターリーブされた入力と出力を行うと、パフォーマンスを著しく損なう可能性があります。これは、プロンプトを表示してからデータを待つコマンドラインインターフェイスで使用します。
std::string name;
cout << "Enter your name:";
cin >> name;
この場合、入力を待機する前に、プロンプトが実際に表示されることを確認する必要があります。上記の行でその関係を破ると、cin
とcout
が独立します。
C++ 11以降、iostreamでパフォーマンスを向上させるもう1つの方法は、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。私は 投稿 C++で入力する数値のいくつかの方法を比較しました。オンライン審査員にとっては便利ですが、ロシア語でしかありません。ただし、コードサンプルとファイナルテーブルは理解できるはずです。
問題は、引用 cppreference :
std :: cinからの入力、std :: cerrへの出力、またはプログラムの終了により、std :: cout.flush()の呼び出しが強制されます。
これは簡単にテストできます:交換する場合
cin >> fact_num;
と
scanf("%d", &fact_num);
cin >> num_of_inputs
でも同じですが、cout
を保持すると、C++バージョン(またはIOStreamバージョン)でC 1とほぼ同じパフォーマンスが得られます。
cin
を保持するが、置き換える場合も同じことが起こります
cout << num_of_trailing_zeros << "\n";
と
printf("%d", num_of_trailing_zeros);
printf("%s","\n");
簡単な解決策は、Ilya Popovが述べたようにcout
とcin
を解くことです:
cin.tie(nullptr);
標準ライブラリの実装では、特定の場合にフラッシュの呼び出しを省略することができますが、常にではありません。 C++ 14 27.7.2.1.3からの引用です(chqrlieに感謝):
クラスbasic_istream :: sentry:最初に、is.tie()がNULLポインターでない場合、関数はis.tie()-> flush()を呼び出して、関連する外部Cストリームと出力シーケンスを同期します。ただし、is.tie()のput領域が空の場合、この呼び出しを抑制できます。さらに、実装はis.rdbuf()-> underflow()の呼び出しが発生するまでフラッシュする呼び出しを延期することができます。 sentryオブジェクトが破棄される前にそのような呼び出しが発生しない場合、flushの呼び出しは完全に削除されます。