はじめに
この記事はComputer Society Advent Calender 2025の5日目の記事です。
4日目
↑
この記事
↓
6日目
執筆者は競プロ歴、C++使用歴ともに半年程度の初心者なので至らぬ点も多々あるとは思いますが温かい目で見守っていただけると幸いです。
本題
競技プログラミングをしている際mod計算をする場面があると思います。その際modの法が$10^9$を超えても使えるように__int128を使おうとしたのですが調べてみるとlong longと違い__int128は四則演算をする際に複数の命令に分割するため実行速度が遅くなってしまうという問題があるらしいとわかりました。
そこで実際どれくらい差があるのか気になったのでC++の有名な以下の高速化コード
#define endl "\n"
std::ios::sync_with_stdio(false);
std::cin.tie(nullptr);
と共に実測していきたいと思います。
実行環境
今回はAtCoderのコードテスト(新ジャッジシステム)のC++ (GCC 15.2.0)を用いました。
検証
1.#define endl "\n"
これはcoutの際に使用するendlをデフォルトの関数endlを使わず、改行の文字列を使うことによってendlのたびにflushをしてしまい余計な処理が起こることを防ぐ手法です。flushはそれまでにたまっているバッファにたまっている出力を強制的に出力させる効果がありますが、インテラクティブな問題以外の複数回出力する問題(クエリ問題など)ではまとめてから出力させればいいので複数回flushするのは余計に時間がかかることになるのを防ぐために使います。
2.std::ios::sync_with_stdio(false);
これは標準入力オブジェクトとC言語の標準入力ストリームの同期を解除する関数です。これを使うとcinとscanfを併用することができなくなる代わりに同期の重い処理をしなくて済むため速くなるというものです。
3.std::cin.tie(nullptr);
C++ではふつうはcinとcoutを結んでおく(tie)ことによってcinをする前にcoutのflushを行うようになっています。ただ前述のとおりflushを行う処理はそれなりに重いため入力の数が多い問題なんかではTLEする危険性もあるためそれを防ぐために使います。
4.long longと__int128
今回の本題です。今回は$1≤i≤N$の$i^4$の総和を998244353で割ったあまりを求めるプログラムで速度差を測りました。
検証に使ったプログラムは最後の使用コードに置いておきました。
測定結果
注意
実行する状況によって今回の結果と異なる結果が出る可能性がありますので結果はあくまで参考程度に見てください。
1. #define endl "\n"
| あり(1e7) | なし(1e7) | あり(1e8) | なし(1e8) | |
|---|---|---|---|---|
| 実行時間 | 396ms | 3465ms | 6942ms | >10000ms |
| メモリ | 79724KiB | 79748KiB | 859680KiB | 236004KiB |
2. std::ios::sync_with_stdio(false);,3. std::cin.tie(nullptr)
いずれも$10^7$回の処理における検証
| 2 あり | 2 なし | 3 あり | 3 なし | |
|---|---|---|---|---|
| 実行時間 | 100ms | 136ms | 110ms | 107ms |
| メモリ | 10608KiB | 10504KiB | 10580KiB | 10480KiB |
4.long longと__int128
ll(1e7) |
__int128(1e7) |
ll(1e8) |
__int128 (1e8) |
|
|---|---|---|---|---|
| 実行時間 | 36ms | 71ms | 343ms | 828ms |
| メモリ | 3552KiB | 3480KiB | 3524KiB | 3616KiB |
結論
endlでflushを使わないようにする対処はTLEするかしないかレベルで影響するがsync_with_stdio(false)やcin.tie(nullptr)は実行時間に大きな差を与えないことがわかりました。また今回調べたかったlong longと__int128に関しては定数倍で落ちる可能性があるアルゴリズムを使っている際は関係があるという程度のものであるという結果になりました。
記事を書くという行為自体初めてで読みづらい点があったとは思いますが最後まで読んでいただきありがとうございました。
使用コード
1.#define endl "\n"
#include <bits/stdc++.h>
using namespace std;
using ll = long long;
using ull = unsigned long long;
using ovfl = __int128;
using vi = vector<int>;
using vll = vector<ll>;
using vs = vector<string>;
using pii = pair<int, int>;
using pll = pair<ll, ll>;
#define rep(i,n) for (int i = 0; i < (int) (n); i++)
#define all(v) (v).begin(), (v).end()
#define endl '\n'
#define YES(b) (b) ? cout << "YES" << endl : cout << "NO" << endl
#define Yes(b) (b) ? cout << "Yes" << endl : cout << "No" << endl
#define yes(b) (b) ? cout << "yes" << endl : cout << "no" << endl
int main() {
ios::sync_with_stdio(false);
cin.tie(nullptr);
ll N = 1e7;
rep(i,N) {
cout << i << endl;
}
return 0;
}
2,3.sync_with_stdio(false),cin.tie(nullptr)
#include <bits/stdc++.h>
using namespace std;
using ll = long long;
using ull = unsigned long long;
using ovfl = __int128;
using vi = vector<int>;
using vll = vector<ll>;
using vs = vector<string>;
using pii = pair<int, int>;
using pll = pair<ll, ll>;
#define rep(i,n) for (int i = 0; i < (int) (n); i++)
#define all(v) (v).begin(), (v).end()
#define endl '\n'
#define YES(b) (b) ? cout << "YES" << endl : cout << "NO" << endl
#define Yes(b) (b) ? cout << "Yes" << endl : cout << "No" << endl
#define yes(b) (b) ? cout << "yes" << endl : cout << "no" << endl
int main() {
ios::sync_with_stdio(false);
cin.tie(nullptr);
ll N = 1e7;
rep(i,N) {
char s;
cin >> s;
cout<<s;
}
return 0;
}
4.long longと__int128
#include <bits/stdc++.h>
using namespace std;
using ll = long long;
using ull = unsigned long long;
using ovfl = __int128;
using vi = vector<int>;
using vll = vector<ll>;
using vs = vector<string>;
using pii = pair<int, int>;
using pll = pair<ll, ll>;
#define rep(i,n) for (int i = 0; i < (int) (n); i++)
#define all(v) (v).begin(), (v).end()
#define endl '\n'
#define YES(b) (b) ? cout << "YES" << endl : cout << "NO" << endl
#define Yes(b) (b) ? cout << "Yes" << endl : cout << "No" << endl
#define yes(b) (b) ? cout << "yes" << endl : cout << "no" << endl
int main() {
ios::sync_with_stdio(false);
cin.tie(nullptr);
constexpr ll N = 1e7;
constexpr __int128 m = 998244353;
__int128 ans = 0;
rep(i,N) {
__int128 tmp = i;
tmp *= tmp;
tmp *= tmp;
tmp %= m;
ans += tmp;
ans %= m;
}
cout << (ll) ans << endl;
return 0;
}