[解決済み】malloc+memsetはcallocより遅いのはなぜ?
質問
知られているのは
calloc
とは異なります。
malloc
は、割り当てられたメモリを初期化する点です。また
calloc
の場合、メモリはゼロに設定されます。また
malloc
の場合、メモリはクリアされません。
だから、普段の仕事でも、私は
calloc
として
malloc
+
memset
.
ちなみに、遊び心で、ベンチマーク用に以下のようなコードを書いてみました。
結果は紛糾しました。
コード1
#include<stdio.h>
#include<stdlib.h>
#define BLOCK_SIZE 1024*1024*256
int main()
{
int i=0;
char *buf[10];
while(i<10)
{
buf[i] = (char*)calloc(1,BLOCK_SIZE);
i++;
}
}
コード1の出力。
time ./a.out
**real 0m0.287s**
user 0m0.095s
sys 0m0.192s
コード2
#include<stdio.h>
#include<stdlib.h>
#include<string.h>
#define BLOCK_SIZE 1024*1024*256
int main()
{
int i=0;
char *buf[10];
while(i<10)
{
buf[i] = (char*)malloc(BLOCK_SIZE);
memset(buf[i],'\0',BLOCK_SIZE);
i++;
}
}
コード2の出力。
time ./a.out
**real 0m2.693s**
user 0m0.973s
sys 0m1.721s
置き換え
memset
を
bzero(buf[i],BLOCK_SIZE)
のコード2でも同じ結果になります。
質問です。
なぜ
malloc
+
memset
よりもはるかに遅いので
calloc
? どうして
calloc
ということです。
どのように解決するのですか?
簡単に言うと 常に
calloc()
の代わりに
malloc()+memset()
. ほとんどの場合、それらは同じになります。 場合によっては
calloc()
をスキップすることができるため、より少ない作業で
memset()
を完全に削除します。 それ以外の場合は
calloc()
は、メモリを割り当てないというズルもできるのです! しかし
malloc()+memset()
は常に全量を処理します。
このことを理解するためには、メモリシステムを少し見学する必要があります。
メモリのクイックツアー
ここには、プログラム、標準ライブラリ、カーネル、ページテーブルの4つの主要な部分があります。 あなたはすでに自分のプログラムを知っているので...
のようなメモリアロケータは
malloc()
と
calloc()
は、ほとんどの場合、小さな割り当て(1バイトから数百キロバイトまで)を、より大きなメモリプールにまとめるために存在します。 たとえば、16バイトを割り当てる場合。
malloc()
は、まずプールの1つから16バイトを取り出そうとし、プールが枯渇したときにカーネルにさらなるメモリを要求します。 しかし、ご質問のプログラムは、一度に大量のメモリに対して割り当てを行うため
malloc()
と
calloc()
は、そのメモリをカーネルから直接要求するだけです。 この動作の閾値はシステムに依存しますが、私は1MiBが閾値として使われているのを見たことがあります。
カーネルは、各プロセスに実際のRAMを割り当て、プロセスが他のプロセスのメモリに干渉しないようにする役割を担っています。 これは
メモリ保護
あるプログラムがクラッシュしてもシステム全体がダウンすることがないのは、このおかげです。 そのため、プログラムがより多くのメモリを必要とする場合、単にメモリを取得するのではなく、次のようなシステムコールを使ってカーネルにメモリを要求します。
mmap()
または
sbrk()
. カーネルは、ページテーブルを変更することで、各プロセスにRAMを与えます。
ページテーブルは、メモリアドレスを実際の物理的なRAMにマッピングします。 プロセスのアドレス、32ビットシステムでは0x00000000から0xFFFFFFFは実メモリではなく、代わりに 仮想メモリ プロセッサはこれらのアドレスを4KiBのページに分割し、ページテーブルを変更することによって、各ページを物理RAMの別の部分に割り当てることができます。 ページテーブルを変更できるのはカーネルのみです。
動作しない仕組み
256MiBを割り当てるとどうなるかというと、次のようになります。 ない が動作します。
-
あなたのプロセスコール
calloc()
で、256MiBを要求しています。 -
標準ライブラリが呼び出す
mmap()
で、256MiBを要求しています。 -
カーネルは256MiBの未使用RAMを見つけ、ページテーブルを修正することであなたのプロセスに与えます。
-
標準ライブラリは、RAMをゼロにするために
memset()
から戻りcalloc()
. -
あなたのプロセスは最終的に終了し、カーネルはRAMを再要求して、他のプロセスが使用できるようにします。
実際の動作
上記のプロセスでうまくいくはずなのですが、この方法ではうまくいきません。 大きな違いは3つあります。
-
あなたのプロセスがカーネルから新しいメモリを取得するとき、そのメモリはおそらく以前に他のプロセスによって使用されたものです。 これはセキュリティ上のリスクです。 もし、そのメモリにパスワードや暗号化キー、秘密のサルサレシピが入っていたらどうでしょう? 機密データが漏れないように、カーネルはプロセスにメモリを渡す前に必ずメモリをスクラップします。 メモリをゼロにすることでスクラップし、新しいメモリがゼロになった場合はそれを保証するようにした方がいいかもしれないので
mmap()
は、それが返す新しいメモリが常にゼロであることを保証しています。 -
世の中には、メモリを確保してもすぐには使わないプログラムがたくさんあります。 メモリが確保されても使われないこともある。 カーネルはこのことを知っていて、怠慢なのです。 新しいメモリを割り当てるとき、カーネルはページテーブルにまったく触れず、プロセスにいかなるRAMも与えません。 その代わり、カーネルはプロセス内のあるアドレス空間を見つけ、そこに入るはずのものを記録し、プログラムが実際に使うことがあればそこにRAMを置くと約束するのです。 プログラムがこれらのアドレスから読み書きをしようとすると、プロセッサは ページフォルト カーネルがそのアドレスにRAMを割り当てて、プログラムを再開します。 メモリを使用しない場合、ページフォルトは発生せず、プログラムが実際に RAM を取得することはありません。
-
プロセスによっては、メモリを確保した後、そのまま読み出すものもあります。 つまり、異なるプロセス間のメモリ上の多くのページが、"0 "から "0 "を返したもので埋め尽くされている可能性があります。
mmap()
. これらのページはすべて同じであるため、カーネルはこれらの仮想アドレスがゼロで満たされたメモリの単一の共有4KiBページを指すようにします。 そのメモリに書き込もうとすると、プロセッサは別のページフォルトを引き起こし、カーネルは他のプログラムと共有されていないゼロの新鮮なページを提供するために介入するのです。
最終的な処理は、もっとこんな感じです。
-
あなたのプロセスは
calloc()
で、256MiBを要求しています。 -
標準ライブラリが呼び出す
mmap()
で、256MiBを要求しています。 -
カーネルは256MiBの未使用の のアドレス空間があります。 は、そのアドレス空間が現在何に使用されているかをメモして、戻ります。
-
標準ライブラリは
mmap()
は常にゼロで埋め尽くされる(または になります。 そのため、メモリに触れることはなく、ページフォルトも発生せず、RAMがあなたのプロセスに与えられることはありません。 -
あなたのプロセスは最終的に終了し、カーネルはRAMを取り戻す必要がありません。なぜなら、RAMはそもそも割り当てられていなかったからです。
を使用する場合
memset()
でページをゼロにします。
memset()
はページフォルトを起こし、RAM を確保し、そしてすでに 0 で満たされているにもかかわらず、それを 0 にします。 これは膨大な量の余分な作業であり、なぜ
calloc()
よりも高速です。
malloc()
と
memset()
. どうせメモリを使うことになるなら
calloc()
の方がまだ速いです。
malloc()
と
memset()
が、その差はなかなかバカにできない。
これは常に機能するわけではありません
すべてのシステムにページングされた仮想メモリがあるわけではないので、すべてのシステムがこれらの最適化を使用できるわけではありません。 これは、80286 のような非常に古いプロセッサや、高度なメモリ管理ユニットを搭載するには小さすぎる組み込みプロセッサに当てはまります。
また、割り当て量が少ない場合は、必ずしもうまくいきません。 より小さなアロケーションでは
calloc()
は、カーネルに直接行くのではなく、共有プールからメモリを取得します。 一般に、共有プールには、古いメモリが使用されたり
free()
ということで
calloc()
は、そのメモリを受け取って
memset()
を実行して、それを消去します。 一般的な実装では、共有プールのどの部分が原始的で、まだゼロで満たされているかを追跡しますが、すべての実装がこれを行うわけではありません。
いくつかの間違った答えを払拭する
オペレーティングシステムによって、カーネルはその空き時間にメモリをゼロにしたりしなかったりします。 Linuxでは、先にメモリをゼロにすることはありませんし Dragonfly BSDも最近この機能をカーネルから削除しました。 . しかし、他のカーネルでは、先にメモリをゼロにするものもあります。 アイドル時にページをゼロにすることは、大きな性能差を説明するのに十分ではありません。
その
calloc()
関数は、特別なメモリー・アラインド・バージョンの
memset()
ということであり、いずれにせよ、それほど速くなるわけではありません。 ほとんどの場合
memset()
最近のプロセッサの実装はこのような感じです。
function memset(dest, c, len)
// one byte at a time, until the dest is aligned...
while (len > 0 && ((unsigned int)dest & 15))
*dest++ = c
len -= 1
// now write big chunks at a time (processor-specific)...
// block size might not be 16, it's just pseudocode
while (len >= 16)
// some optimized vector code goes here
// glibc uses SSE2 when available
dest += 16
len -= 16
// the end is not aligned, so one byte at a time
while (len > 0)
*dest++ = c
len -= 1
ということで、お分かりになると思います。
memset()
は非常に高速で、大きなメモリブロックに対してこれ以上のものはないでしょう。
というのは
memset()
は、すでにゼロ化されているメモリをゼロ化するため、メモリが2回ゼロ化されることになりますが、これは2倍の性能差しか説明できません。 この性能差はもっと大きいです。
malloc()+memset()
と
calloc()
).
パーティーの仕掛け
10回ループさせる代わりに、以下の時間までメモリを確保するプログラムを書いてください。
malloc()
または
calloc()
はNULLを返す。
を追加した場合はどうなるのでしょうか?
memset()
?
関連
-
[解決済み] (.text+0x20): `main'への未定義の参照と関数への未定義の参照
-
[解決済み】 switch case: error: case label does not reduce to an integer constant
-
[解決済み】MB/sとMiB/sを計算する方法は?
-
[解決済み】makefile:4。*** missing separator. 停止する
-
[解決済み】execvp: バッドアドレスエラー
-
[解決済み] mallocの結果はキャストするのですか?
-
[解決済み] 配列の場合、なぜ a[5] == 5[a] になるのでしょうか?
-
[解決済み] Cプリプロセッサはなぜ "linux "という単語を定数 "1 "と解釈するのですか?
-
[解決済み] mallocとcallocの違い?
-
[解決済み] プログラム終了前にmallocの後にfreeをしないと本当に何が起こるのか?
最新
-
nginxです。[emerg] 0.0.0.0:80 への bind() に失敗しました (98: アドレスは既に使用中です)
-
htmlページでギリシャ文字を使うには
-
ピュアhtml+cssでの要素読み込み効果
-
純粋なhtml + cssで五輪を実現するサンプルコード
-
ナビゲーションバー・ドロップダウンメニューのHTML+CSSサンプルコード
-
タイピング効果を実現するピュアhtml+css
-
htmlの選択ボックスのプレースホルダー作成に関する質問
-
html css3 伸縮しない 画像表示効果
-
トップナビゲーションバーメニュー作成用HTML+CSS
-
html+css 実装 サイバーパンク風ボタン
おすすめ
-
[解決済み】Cコンパイルエラー。"変数サイズのオブジェクトが初期化されていない可能性がある"
-
[解決済み】C言語で「関数の型が競合しています」と表示される、なぜ?
-
[解決済み] [Solved] なぜこのようなエラーが発生するのでしょうか。「データ定義に型またはストレージクラスがない」?
-
[解決済み】EAGAINとはどういう意味ですか?
-
[解決済み】Linuxでexeclp()がどのように動作するのか理解できません。
-
[解決済み】C言語で多重定義を防ぐには?
-
[解決済み】「複数の定義」「最初に定義されたのはここです」エラーについて
-
[解決済み] char pointers: 'char*' から 'char' への無効な変換?
-
[解決済み] Cプログラムで「配列の添え字が整数でない」。
-
[解決済み】C言語でpow( )への未定義参照、math.hを含むにもかかわらず【重複】。