C言語が最高のファイルAPIを持つ理由
Andrej Karpathyは、C言語のメモリマッピングによるファイルAPIが、他の言語のシリアライゼーションや逐次アクセスに比べて、大規模データ処理において効率的で柔軟なアクセスを可能にする点で優れていると指摘している。
キーポイント
C言語のメモリマッピングの利点
C言語のmmap()を使用すると、ファイルをメモリ上のデータと同じように直接アクセスでき、大規模ファイルでもRAMに収まらない場合や逐次アクセス制限なく効率的に処理できる。
他言語のファイルAPIの限界
多くの言語ではread()/write()による逐次アクセスやシリアライゼーションが必要で、Pythonのpickleのようにセキュリティリスクやパフォーマンス制限がある。
実用性と現実的な課題
Cの実装にはページフォルトやエンディアン処理などのオーバーヘッドがあるが、データをディスクから直接扱える利点は大きく、特に大規模データ処理で有用である。
ファイルシステムの未活用
ファイルシステムは本来NoSQLデータベースとして機能しうるが、多くの言語では単なるreaddir()のラッパーに留まり、より高度な活用が進んでいない。
影響分析・編集コメントを表示
影響分析
この記事は、プログラミング言語のファイルAPI設計における根本的な課題を提起し、特に大規模データ処理や効率的なI/O操作の重要性を再認識させる。AI/機械学習分野では大規模データセットの処理が日常的であるため、ファイルアクセスの効率化は間接的にパフォーマンス向上に寄与する可能性がある。
編集コメント
技術的には興味深い考察だが、AI業界への直接的な関連性は薄く、既知のシステムプログラミングの知見を再確認する内容。実務でのファイル処理効率向上には参考になるが、業界を変えるような新規性は限定的。
優れたプログラミング言語は数多くありますが、ファイル操作は常に後回しにされているように思えます。
通常はread()、write()、そして何らかのシリアライゼーションライブラリしか提供されません。
C言語では、メモリ内のデータと全く同じ方法でファイルにアクセスできます:
/* HTMLタグ、キーワード、コマンド */
h-n {color: #F27;}
/* 値 */
h-v {color: #B8F;}
/* CSSセレクタ、属性/変数名、ファイル名 */
h-s {color: #AEE;}
/* コメント */
h-c {color: #777;}
h-e {color: #F6F;}
#include
#include
#include
#include
#include
void main() {
// 1000個の符号なし整数を含むファイルを作成/開く
// 全てゼロで初期化
int len = 1000 * sizeof(uint32_t);
int file = open("numbers.u32", O_RDWR | O_CREAT, 0600);
ftruncate(file, len);
// メモリにマップする
uint32_t* numbers = mmap(NULL, len,
PROT_READ | PROT_WRITE, MAP_SHARED,
file, 0);
// 何か処理を行う:
printf("%d\n", numbers[42]);
numbers[42] = numbers[42] + 1;
// クリーンアップ
munmap(numbers, len);
close(file);
}
メモリマッピングは、ファイルをメモリに読み込むこととは同じではありません。
ファイルがRAMに収まらない場合でも機能します。
データは必要に応じて読み込まれるため、テラバイト級のファイルを開くのに一日かかることはありません。
これは全てのデータ型で機能し、自動的にキャッシュされます。
このキャッシュは、システムが他の目的でメモリを必要とする場合、自動的にクリアされます。
しかし、他のほとんどの言語では、
小さな塊でread()し、パースし、処理し、シリアライズし、最終的にwrite()でディスクに書き戻さなければなりません。
これは機能しますが、遅く、不必要にシーケンシャルアクセスに制限されています。
コンピュータは何十年もテープを使っていないのです。
もし幸運にもメモリマッピングが利用できたとしても、バイト配列に制限されるでしょう。
それでも明示的なパース/シリアライズが必要です。
結局はread()とwrite()を呼び出す、より洗練された方法に過ぎません。
ほとんどの言語がすでにカスタムアロケータをサポートしていることを考えると、ファイルにアクセスするより良い方法を追加することは十分に実現可能に思えます...
しかし、私の知る限り、C言語はバイナリフォーマットを指定してそのまま使用できる唯一の言語です。
C言語の実装はそれほど優れているわけでもありません。
メモリマッピングにはある程度のオーバーヘッド(ページフォルト、TLBフラッシュ)があり、C言語はエンディアンを処理するための何も行いません——
しかし、何もないよりはましです。
確かに、何らかのパースやバリデーションを行いたいかもしれませんが、これはデータがディスクから読み出されるたびに必要とされるべきではありません。
大きなデータを扱う場合、メモリ不足になることは非常に一般的で、全てをRAMにパースすることは不可能になります。
コードを複雑にすることなく、単にデータをオフロードできることは非常に便利です。
Pythonのpickleを見てください。
それは完全に安全でないシリアライゼーション形式です。
ファイルをロードすると、単にいくつかの数値が欲しいだけなのに、コード実行が引き起こされる可能性があります...
それでも、Pythonのコードとデータを混在させるモデルに合っているため、非常に広く使われています。
多くのファイルは、信頼できない外部ソースからのものではありません。
ファイル操作も同様に軽視されています。
ファイルシステムはオリジナルのNoSQLデータベースですが、C言語のreaddir()のラッパー以上のものを得ることはほとんどありません。
これは通常、ファイルシステムの上にSQLiteのような別のデータベースを実行する結果になります。
しかし、リレーショナルデータベースは決してプログラムに完全に適合しません。
...そしてSQLはファイルよりもさらに統合が悪いです。
全てのデータをシリアライズしなければならないことに加えて、アクセスするためだけに全く別の言語でコードを書かなければなりません!
ほとんどのプログラマは単にキーバリューストアとして使用し、独自のメタデータ処理を実装します。
奇妙な三重にネストしたデータベースを作成することになるのです。
だからタイトルに答えるために、
私はこれは悪い前提の結果だと思います。
ファイルから読み取られるデータはどこか別の場所から来ていて、パースする必要があるという前提...
そしてディスクに書き込まれるデータはどこかに送られていて、標準フォーマットにシリアライズする必要があるという前提。
これはメモリ制約のあるシステムでは単純に真実ではありません——
そして100GBのファイルを扱う場合——
あらゆるシステムがメモリ制約を受けるのです。
原文を表示
There are a lot of nice programming languages, but files always seem like an afterthought.
You usually only get read(), write() and some kind of serialization library.
In C, you can access files exactly the same as data in memory:
/* HTML tags, keywords, commands */
h-n {color: #F27;}
/* Values */
h-v {color: #B8F;}
/* CSS selectors, attribute/varable names, file names */
h-s {color: #AEE;}
/* Comments */
h-c {color: #777;}
h-e {color: #F6F;}
#include
#include
#include
#include
#include
void main() {
// Create/open a file containing 1000 unsigned integers
// Initialized to all zeros.
int len = 1000 * sizeof(uint32_t);
int file = open("numbers.u32", O_RDWR | O_CREAT, 0600);
ftruncate(file, len);
// Map it into memory.
uint32_t* numbers = mmap(NULL, len,
PROT_READ | PROT_WRITE, MAP_SHARED,
file, 0);
// Do something:
printf("%d\n", numbers[42]);
numbers[42] = numbers[42] + 1;
// Clean up
munmap(numbers, len);
close(file);
}
Memory mapping isn't the same as loading a file into memory:
It still works if the file doesn't fit in RAM.
Data is loaded as needed, so it won't take all day to open a terabyte file.
It works with all datatypes and is automatically cached.
This cache is cleared automatically if the system needs memory for something else.
However, in other most languages,
you have to read() in tiny chunks, parse, process, serialize and finally write() it back to the disk.
This works, but is slow and needlessly limited to sequential access:
Computers haven't used tape for decades.
If you're lucky enough to have memory mapping, it will be limited to byte arrays,
which still require explicit parsing/serialization.
It ends up as just a nicer way to call read() and write()
Considering that most languages already support custom allocators, adding a better way to access files seems very doable...
but, as far as I'm aware, C is the only language that lets you specify a binary format and just use it.
C's implemenation isn't even very good:
Memory mapping comes some overhead (page faults, TLB flushes) and C does nothing to handle endianness —
but it doesn't take much to beat nothing.
Sure, you might want to do some parsing and validation, but this shouldn't be required every time data leaves the disk.
When working with larger data, it's very common to run out of memory, making it impossible to just parse everything into RAM.
Being able to just offload data without complicating the code is very useful.
Just look at Python's pickle:
it's a completely insecure serialization format.
Loading a file can cause code execution even if you just wanted some numbers...
but still very widely used because it fits with the mix-code-and-data model of python.
A lot of files are not untrusted.
File manipulation is similarly neglected.
The filesystem is the original NoSQL database, but you seldom get more then a wrapper around C's readdir().
This usually results in people running another database, such as SQLite, on top of the filesystem,
but relational databases never quite fit your program.
... and SQL integrates even worse than files:
On top of having to serialize all your data, you have to write code in a whole separate language just to access it!
Most programmers just use it as a key-value store, and implement their own metadata handling:
creating a bizarre triple nested database.
So to answer the title,
I think it's a result of a bad assumption:
That data being read from a file is coming from somewhere else and needs to be parsed...
and that data being written to disk is being sent somewhere and needs to be serialized into a standard format.
This simply isn't true on memory constrained systems —
and with 100 GB files —
every system is memory constrained.
関連記事
今日のまとめ
AI日報で今日の重要ニュースをまとめ読み