※本記事の内容はあくまで「目安」で、厳密な比較はしていません。ご了承ください。
※コンパイラや実際のコードによって結果が異なる可能性があります。
※本記事で言っている「安全」は、「ソースコードに人為的ミスがあったとしてもメモリリークを起こさない」という意味で書いています。
0. まとめ
使い分けとしては
- 速度よりも「安全」性が重要の場合
-
std::vector
等のコンテナ-
std::vector::resize()
の速度はrealloc()
よりも速い - 他にも
std::array
等の便利なコンテナがある
-
-
std::make_unique
等のスマートポインタのヘルパ関数
-
- 「安全」性よりもメモリ確保時の速度の方が重要の場合、または同等に重要の場合
- コンパイラの最適化を信用するなら
std::unique_ptr
&new
- コンパイラの最適化を信用せず、メモリサイズを変更しないなら
new
- コンパイラの最適化を信用せず、メモリサイズを変更するなら
malloc()
- ただし、メモリサイズ変更の速度は最適化によらず
std::vector
よりも遅い
- ただし、メモリサイズ変更の速度は最適化によらず
- コンパイラの最適化を信用するなら
のようになるかと思います。
特徴は以下の通り:
- C++ の
new
は Java 等と異なり、プリミティブ型でも使用できる -
new
よりスマートポインタの方が「安全」 (メモリリークが発生しにくい) - スマートポインタより
new
の方が高速または同じ速度- (コンパイラの最適化により、同じ速度になる可能性があります。)
-
malloc()
とnew
はほぼ同じ速度- (「
new
は関数呼び出しでないからnew
の方が速い」という説明を見かけたのですが、ここでは確認できませんでした。)
- (「
-
malloc()
/free()
ではクラスを扱った場合にコンストラクタ・デストラクタが呼ばれない -
std::make_unique
やstd::vector
等のコンテナは、new
やmalloc()
より遅い- (コンパイラの最適化により、実行時間を少し改善できる可能性があります。)
-
malloc()
で確保したメモリはrealloc()
でサイズを変更できる- 仕様上、メモリ位置の変更と値のコピーが発生する可能性あり
-
std::vector
は配列サイズを変更できる- 仕様上、少なくとも追加分の値のコピーが発生する
- 仕様上、既存の値に関して、メモリ位置の変更と値のコピーが発生するかは未定義?
-
new
で確保した配列はサイズを変更できない
参考「malloc/free - ゼロから学ぶ C++」
参考「Unterschied zwischen new und malloc ()」
1. メモリの確保・解放の速度
厳密な比較にはなっていないかもしれませんが、おおよそ
- 最適化ありの場合:
new
=malloc()
= (std::unique_ptr
&new
) <<std::make_unique
=std::vector
- 最適化なしの場合:
new
=malloc()
< (std::unique_ptr
&new
) <<std::make_unique
<<std::vector
のような順の実行時間になりました。
※ものによっては確実に解放されるとは限らない仕様になっているため、厳密な比較にはなっていません。
1.1. テスト用コード
#include <iostream>
#include <memory>
#include <fcntl.h>
#include <chrono>
#include <stdlib.h>
#include <vector>
#define N (1024 * 1024)
#define N2 10
//
void printTimeOfMakedUniquePtr();
void printTimeOfUniquePtr();
void printTimeOfVector();
void printTimeOfNew();
void printTimeOfMAlloc();
//
int wmain() {
// 出力の文字コード指定
_setmode(fileno(stdout), _O_U8TEXT);
_setmode(fileno(stderr), _O_U8TEXT);
//
for (int i = 0; i < N2; i++) {
printTimeOfMakedUniquePtr();
printTimeOfUniquePtr();
printTimeOfVector();
printTimeOfNew();
printTimeOfMAlloc();
std::wcout << L"\n";
}
std::wcout << std::flush;
return 0;
}
void printTimeOfMakedUniquePtr() {
auto begin = std::chrono::system_clock::now();
for (int i = 0; i < N; i++) {
auto array = std::make_unique<int []>(1024);
array = nullptr;
}
auto end = std::chrono::system_clock::now();
std::wcout << std::chrono::duration_cast<std::chrono::milliseconds>(end - begin).count() << L"\t";
}
void printTimeOfUniquePtr() {
auto begin = std::chrono::system_clock::now();
for (int i = 0; i < N; i++) {
auto array = std::unique_ptr<int []>(new int[1024]);
array = nullptr;
}
auto end = std::chrono::system_clock::now();
std::wcout << std::chrono::duration_cast<std::chrono::milliseconds>(end - begin).count() << L"\t";
}
void printTimeOfVector() {
auto begin = std::chrono::system_clock::now();
for (int i = 0; i < N; i++) {
std::vector<int> v(1024);
v.clear();
v.shrink_to_fit();
}
auto end = std::chrono::system_clock::now();
std::wcout << std::chrono::duration_cast<std::chrono::milliseconds>(end - begin).count() << L"\t";
}
void printTimeOfNew() {
auto begin = std::chrono::system_clock::now();
for (int i = 0; i < N; i++) {
int * const array = new int[1024];
delete[] array;
}
auto end = std::chrono::system_clock::now();
std::wcout << std::chrono::duration_cast<std::chrono::milliseconds>(end - begin).count() << L"\t";
}
void printTimeOfMAlloc() {
auto begin = std::chrono::system_clock::now();
for (int i = 0; i < N; i++) {
int * const array = (int *) malloc(sizeof(int) * 1024);
if ( array == NULL ) {
std::wcerr << L"Error: malloc()" << std::endl;
break;
}
free(array);
}
auto end = std::chrono::system_clock::now();
std::wcout << std::chrono::duration_cast<std::chrono::milliseconds>(end - begin).count() << L"\t";
}
1.2. 結果
std::make_unique
-
std::unique_ptr
&new
std::vector
new
malloc()
と、それぞれの解放処理を含めた各実行時間:
878 151 2905 82 77
857 149 2907 81 78
853 151 2911 80 82
849 154 2914 86 77
857 148 2907 82 78
855 150 2908 81 78
854 150 2909 82 78
854 149 2914 82 76
854 149 2904 79 76
851 150 2904 81 78
480 86 492 88 89
488 87 484 86 84
490 88 483 88 92
492 87 487 90 85
482 87 486 91 86
489 89 485 87 85
487 86 486 86 92
482 85 486 87 86
481 87 494 91 86
497 86 485 86 85
207 81 211 80 80
209 82 206 80 79
207 81 207 84 76
206 80 212 80 76
208 81 208 79 80
207 80 207 82 77
206 81 211 82 77
210 80 205 81 80
207 81 209 80 78
207 80 212 80 77
2. std::vector::resize()
と realloc()
の速度比較
具体的な値などの場合によるかもしれませんが、ここでは std::vector::resize()
の方が明らかに高速でした。
2.1. テスト用コード
#include <iostream>
#include <fcntl.h>
#include <chrono>
#include <stdlib.h>
#include <vector>
#define N (1024 * 100)
#define N2 10
//
void printTimeOfVectorResize();
void printTimeOfVectorReAlloc();
//
int wmain() {
// 出力の文字コード指定
_setmode(fileno(stdout), _O_U8TEXT);
_setmode(fileno(stderr), _O_U8TEXT);
//
for (int i = 0; i < N2; i++) {
printTimeOfVectorResize();
printTimeOfVectorReAlloc();
std::wcout << L"\n";
}
std::wcout << std::flush;
return 0;
}
void printTimeOfVectorResize() {
std::vector<int> v(16);
auto begin = std::chrono::system_clock::now();
for (int i = 2; i < N; i++) {
v.resize(i * 16);
v[i * 16 - 1] = 23;
}
auto end = std::chrono::system_clock::now();
std::wcout << std::chrono::duration_cast<std::chrono::milliseconds>(end - begin).count() << L"\t";
}
void printTimeOfVectorReAlloc() {
int * array = (int *) malloc(sizeof(int) * 16);
if ( array == NULL ) {
std::wcerr << L"Error: malloc()" << std::endl;
return;
}
auto begin = std::chrono::system_clock::now();
for (int i = 2; i < N; i++) {
int * const newArray = (int *) realloc(array, sizeof(int) * i * 16);
if ( newArray == NULL ) {
std::wcerr << L"Error: realloc()" << std::endl;
break;
}
array = newArray;
array[i * 16 - 1] = 23;
}
auto end = std::chrono::system_clock::now();
free(array);
std::wcout << std::chrono::duration_cast<std::chrono::milliseconds>(end - begin).count() << L"\t";
}
2.2. 結果
std::vector::resize()
は最適化で高速化しましたが、realloc()
の速度は変わりませんでした。
std::vector::resize()
と realloc()
の実行時間:
12 2000
13 2007
14 2005
13 1999
14 2024
15 2115
14 2088
13 2024
14 2129
15 2041
5 2056
6 2036
7 2050
6 2058
8 2048
5 2040
5 2041
5 2046
6 2036
7 2043
6 2084
6 2055
6 2061
5 2045
5 2054
5 2046
6 2041
6 2051
6 2049
5 2049
3. その他
3.1. メモリリークに注意
スマートポインタや std::vector
等のコンテナは (基本的には) 解放し忘れが起きないので「安全」です。
new
/ delete
や malloc()
/ free()
等、解放処理を忘れないよう注意が必要です。
特に、同一のスコープ内でメモリを確保した後にエラーが発生したときに、return
や try
/ catch
する場合等に解放処理を忘れがちです (エラー発生時にプログラム本体を終了しない場合に注意) 。
new
で確保した「配列」は、delete[]
で解放する必要があります (配列を delete
した場合の動作は仕様上未定義) 。
※ std::unique_ptr
と new
を使用した場合にメモリリークする可能性が理論上存在します。
参考「c++ - make_uniqueの利点 - スタック・オーバーフロー」
(※そもそも関数の引数に複数 new
を書くような書き方自体良くない気がしますが…。)
3.2. 可変長配列 (VLA) について
C++ の仕様としては非推奨ですが、gcc や g++ 等で可変長配列 (Variable Length Array) を使用することができます。
※確保するサイズを実行時に決定可能ですが、確保後にサイズを変更することはできません。
#include <iostream>
#include <fcntl.h>
//
void printA(size_t size);
//
int wmain() {
// 出力の文字コード指定
_setmode(fileno(stdout), _O_U8TEXT);
//
printA(3);
printA(5);
std::wcout << std::flush;
return 0;
}
void printA(size_t size) {
wchar_t str[size + 1]; // ★
std::fill_n(str, size, L'あ');
str[size] = L'\0';
std::wcout << str << L"\n";
}
あああ
あああああ
3.3. 本記事で使用したコンパイルオプション
x86_64-w64-mingw32-g++ -Wall \
-finput-charset=UTF-8 -fexec-charset=CP932 \
-municode \
-static-libgcc -static-libstdc++ \
-O0 \
-o main.exe \
main.cpp