2
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

intとかarrayとかvectorとかresizeとかreserveとかassignとかpushとかemplaceなんだよ

2
Last updated at Posted at 2026-05-19

 と思ったので、調べて実験してみました!

 ベンチマークも一応しますが、主に競プロ観点(その場で解ければいい、解ける限りにおいては遅くてもよい)です。

int[]とarrayとvector

 配列を定義するにはおおきくわけて以下の $3$ つがあります。

int a[100];
array<int,100> a;
vector<int> a(100);

 それぞれの利点を一言でまとめると、

  • int[] : 速い
  • array : ある程度速くて安全
  • vector : 長さを変えられる

 となります。実際に速さを計ってみます。大量の配列の読み書きをさせます。vector は、最初に長さを確保するもの(fixed)と、逐次的に長さを確保していくもの(not fixed)とわけます。配列サイズは 2e6 程度、回数は 1e8 程度です。

ベンチマークコード
#include <bits/stdc++.h>
using namespace std;

using ll = long long;

template <class F>
double bench(const string& name, F f) {
    auto st = chrono::high_resolution_clock::now();

    volatile ll res = f(); // 最適化で消されるのを防ぐ

    auto ed = chrono::high_resolution_clock::now();
    double ms = chrono::duration<double, milli>(ed - st).count();

    cout << name << ": " << ms << " ms, res = " << res << '\n';
    return ms;
}

int main() {
    constexpr int N = 2000000;
    constexpr int LOOP = 50;

    static int c_array[N];
    static array<int, N> std_array;
    vector<int> vec_fix(N);

    bench("C array write", [&]() -> ll {
        ll s = 0;
        for (int r = 0; r < LOOP; r++) {
            for (int i = 0; i < N; i++) {
                c_array[i] = i;
            }
        }
        for (int i = 0; i < N; i++) s += c_array[i];
        return s;
    });

    bench("std::array write", [&]() -> ll {
        ll s = 0;
        for (int r = 0; r < LOOP; r++) {
            for (int i = 0; i < N; i++) {
                std_array[i] = i;
            }
        }
        for (int i = 0; i < N; i++) s += std_array[i];
        return s;
    });

    bench("vector fixed write", [&]() -> ll {
        ll s = 0;
        for (int r = 0; r < LOOP; r++) {
            for (int i = 0; i < N; i++) {
                vec_fix[i] = i;
            }
        }
        for (int i = 0; i < N; i++) s += vec_fix[i];
        return s;
    });

    bench("vector not fixed write", [&]() -> ll {
        ll s = 0;
        vector<int> v;
        v.reserve(N);
        for (int r = 0; r < LOOP; r++) {
            v.clear();
            for (int i = 0; i < N; i++) {
                v.push_back(i);
            }
            if(r == LOOP-1){
                for(int i = 0; i < N; i++){
                    s += v[i];
                }
            }
        }
        return s;
    });

    cout << '\n';

    bench("C array read", [&]() -> ll {
        ll s = 0;
        for (int r = 0; r < LOOP; r++) {
            for (int i = 0; i < N; i++) {
                s += c_array[i];
            }
        }
        return s;
    });

    bench("std::array read", [&]() -> ll {
        ll s = 0;
        for (int r = 0; r < LOOP; r++) {
            for (int i = 0; i < N; i++) {
                s += std_array[i];
            }
        }
        return s;
    });

    bench("vector read", [&]() -> ll {
        ll s = 0;
        for (int r = 0; r < LOOP; r++) {
            for (int i = 0; i < N; i++) {
                s += vec_fix[i];
            }
        }
        return s;
    });
}
C array write: 81.0115 ms, res = 1999999000000
std::array write: 124.096 ms, res = 1999999000000
vector fixed write: 302.737 ms, res = 1999999000000
vector not fixed write: 1198.77 ms, res = 1999999000000

C array read: 147.627 ms, res = 99999950000000
std::array read: 129.98 ms, res = 99999950000000
vector read: 290.593 ms, res = 99999950000000
  • int[] > array > vector(fixed)>>> vector(not fixed)

 という感じです。vector(not fixed) は、サイズ更新や容量チェックなどが入るため、添字アクセスでの代入より遅くなっています。

 ただ、これだけ大規模に書き込ませる問題は稀だと思うので、この差によって TLE でペナることまずないと思います。更に言えば、特殊な設定な問題でない限り、入力の最大サイズは明らかなので、先に最大長を確保しておけばその差は更に縮まります。

危険な要素

 int の生配列はいちばんプリミティブです。シンプルかつ速いですが色々危険です。

int main(){
    int a[10];
    for(int i = 0; i < 20; i++){
        cout << a[i] << endl;
    }
}
-889193280
171
-16574424
32758
458617856
538
0
1
0
0
0
11
-889193152
171
-16575680
32758
1
0
-16543736
32758

(ローカルでは初期化していないと)めちゃくちゃな値がでてきますし、範囲外も指定できるのでバグの元です。arrayvector は範囲外を at するとエラーが出るので安心です。

種類 速度 初期化 範囲外参照
int[] されない ノーチェック
array されない at() はチェック
vector される at() はチェック

vector に対する操作色々

 vector は実行時にサイズを指定できます。以下のような命令があります。

  • resize
  • reserve
  • assign

 resizeassign はほとんど使い方は同じです。

int main(){
    vector<int> a;
    a.resize(4,3); // {3,3,3,3}
    vector<int> b;
    b.assign(4,3); // {3,3,3,3}
}

 このふたつはどちらも同じ {3,3,3,3} になります。

 違いが出るのは既存の配列に変更を加えたときです。

int main(){
    vector<int> a = {3,3,3,3};
    a.resize(5,6); // {3,3,3,3,6}
    vector<int> b = {3,3,3,3};
    b.assign(5,6); // {6,6,6,6,6}
}

 resize は既存の要素に変更を加えません。なので、初期化したと思ったらしていない事故が起こります。配列を初期化して使い回すような場合は、assign の方が良いでしょう。

 reserve はちょっと特殊で、メモリだけを先に確保します。これによって動的確保が起こらずに速くなるらしいです。ベンチマークをしてみましょう。

ベンチマークコード
#include <bits/stdc++.h>
using namespace std;

using ll = long long;

template <class F>
double bench(const string& name, F f) {
    auto st = chrono::high_resolution_clock::now();

    volatile ll res = f(); // 最適化で消されるのを防ぐ

    auto ed = chrono::high_resolution_clock::now();
    double ms = chrono::duration<double, milli>(ed - st).count();

    cout << name << ": " << ms << " ms, res = " << res << '\n';
    return ms;
}

int main() {
    constexpr int N = 2000000;
    constexpr int LOOP = 50; // N * LOOP = 1e8

    bench("vector resize + index write", [&]() -> ll {
        ll s = 0;
        vector<int> v;

        for (int r = 0; r < LOOP; r++) {
            v.resize(N);

            for (int i = 0; i < N; i++) {
                v[i] = i;
            }

            if (r == LOOP - 1) {
                for (int i = 0; i < N; i++) {
                    s += v[i];
                }
            }
        }

        return s;
    });

    bench("vector assign + index write", [&]() -> ll {
        ll s = 0;
        vector<int> v;

        for (int r = 0; r < LOOP; r++) {
            v.assign(N, 0);

            for (int i = 0; i < N; i++) {
                v[i] = i;
            }

            if (r == LOOP - 1) {
                for (int i = 0; i < N; i++) {
                    s += v[i];
                }
            }
        }

        return s;
    });

    bench("vector reserve + push_back", [&]() -> ll {
        ll s = 0;
        vector<int> v;
        v.reserve(N);

        for (int r = 0; r < LOOP; r++) {
            v.clear();

            for (int i = 0; i < N; i++) {
                v.push_back(i);
            }

            if (r == LOOP - 1) {
                for (int i = 0; i < N; i++) {
                    s += v[i];
                }
            }
        }

        return s;
    });

    bench("vector push_back without reserve", [&]() -> ll {
        ll s = 0;

        for (int r = 0; r < LOOP; r++) {
            vector<int> v;

            for (int i = 0; i < N; i++) {
                v.push_back(i);
            }

            if (r == LOOP - 1) {
                for (int i = 0; i < N; i++) {
                    s += v[i];
                }
            }
        }

        return s;
    });
}
vector resize + index write: 297.01 ms, res = 1999999000000
vector assign + index write: 341.335 ms, res = 1999999000000
vector reserve + push_back: 972.193 ms, res = 1999999000000
vector push_back without reserve: 1437.46 ms, res = 1999999000000

 速度については、resizeassign >> reserve + push_back > push_back without reserve という感じです。

 なので、reserve をあえて使う意味は(競プロ的な文脈では)あまりないと思います。そもそも reverse と紛らわしすぎる

push_back と emplace_back

 vector に要素を追加するときの命令。push_back では要素そのものを、emplace_back では要素をつくるときのコンストラクタを呼びます。emplace_back の方が速いらしいです。比較してみましょう。

ベンチマークコード
#include <bits/stdc++.h>
using namespace std;

using ll = long long;

template <class F>
double bench(const string& name, F f) {
    auto st = chrono::high_resolution_clock::now();

    volatile ll res = f(); // 最適化で消されるのを防ぐ

    auto ed = chrono::high_resolution_clock::now();
    double ms = chrono::duration<double, milli>(ed - st).count();

    cout << name << ": " << ms << " ms, res = " << res << '\n';
    return ms;
}

int main() {
    constexpr int N = 500000;
    constexpr int LOOP = 50; // N * LOOP = 1e8

    bench("pair push_back({a,b})", [&]() -> ll {
        ll s = 0;
        vector<pair<int, int>> v;
        v.reserve(N);

        for (int r = 0; r < LOOP; r++) {
            v.clear();

            for (int i = 0; i < N; i++) {
                v.push_back({i, i + 1});
            }

            if (r == LOOP - 1) {
                for (int i = 0; i < N; i++) {
                    s += v[i].first;
                    s += v[i].second;
                }
            }
        }

        return s;
    });

    bench("pair emplace_back(a,b)", [&]() -> ll {
        ll s = 0;
        vector<pair<int, int>> v;
        v.reserve(N);

        for (int r = 0; r < LOOP; r++) {
            v.clear();

            for (int i = 0; i < N; i++) {
                v.emplace_back(i, i + 1);
            }

            if (r == LOOP - 1) {
                for (int i = 0; i < N; i++) {
                    s += v[i].first;
                    s += v[i].second;
                }
            }
        }

        return s;
    });
}
pair push_back({a,b}): 998.568 ms, res = 250000000000
pair emplace_back(a,b): 1104.2 ms, res = 250000000000

 なぜか push_back の方が速くなりましたが、正直誤差レベルな感じです。もはや趣味でいいと思います。

違いがでるとき

 以下のような場合では挙動に違いが出ます。

int main(){
    vector<vector<int>> a;
    a.push_back({3,4}); // {3,4} という vector<int> を追加
    vector<vector<int>> b;
    b.emplace_back(3,4); // vector<int>(3,4) を追加、つまり {4,4,4} を追加
}
2
1
7

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
2
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?