カスタムアロケータ

メモリ割り当てが常に簡単に出来るとは限りません。ほとんどの場合、Rustが既定の方法でメモリ割り当てを行いますが、割り当て方法をカスタマイズする必要が出てくる場合があります。現在、コンパイラと標準ライブラリはコンパイル時に既定のグローバルアロケータを切り替えることが出来ます。詳細は RFC 1183 に書かれていますが、ここではどのように独自のアロケータを作成するか順を追って説明します。

既定のアロケータ

現在コンパイラは alloc_systemalloc_jemalloc (jemallocのないターゲットもあります)という2つの既定のアロケータを提供しています。これらのアロケータは単に普通のRustのクレートで、メモリの割り当てと解放の手続きを実装しています。標準ライブラリはどちらか一方を前提としてコンパイルされているわけではありません。コンパイラは生成する成果物の種類に応じてどちらのアロケータを使用するかをコンパイル時に決定します。

バイナリを生成する場合、既定では(もし可能なら) alloc_jemalloc を使用します。この場合、コンパイラは最後のリンクにまで影響力を持っているという意味で、「全世界を支配」しています。従ってアロケータの選択はコンパイラに委ねることができます。

一方、動的あるいは静的ライブラリの場合、既定では alloc_system を使用します。他のアプリケーションや他の環境など、使用するアロケータの決定権がない世界において、Rustは「お客」に過ぎません。そのため、メモリの割り当てと解放を行うには、標準API(例えば mallocfree )に頼ることになります。

アロケータの切り替え

コンパイラによる既定の選択はほとんどの場合うまく動きますが、しばしば多少の調整が必要になることがあります。コンパイラのアロケータ選択を上書きするには、単に希望のアロケータとリンクするだけです。

#![feature(alloc_system)] extern crate alloc_system; fn main() { // let a = Box::new(4); // allocates from the system allocator let a = Box::new(4); // システムアロケータからのメモリ割り当て println!("{}", a); }
#![feature(alloc_system)]

extern crate alloc_system;

fn main() {
    let a = Box::new(4); // システムアロケータからのメモリ割り当て
    println!("{}", a);
}

この例で生成されるバイナリは既定のjemallocとリンクする代わりに、システムアロケータを使います。逆に既定でjemallocを使う動的ライブラリを生成するには次のようにします。

#![feature(alloc_jemalloc)] #![crate_type = "dylib"] extern crate alloc_jemalloc; pub fn foo() { // let a = Box::new(4); // allocates from jemalloc let a = Box::new(4); // jemallocからのメモリ割り当て println!("{}", a); } fn main() {}
#![feature(alloc_jemalloc)]
#![crate_type = "dylib"]

extern crate alloc_jemalloc;

pub fn foo() {
    let a = Box::new(4); // jemallocからのメモリ割り当て
    println!("{}", a);
}

カスタムアロケータを書く

時々jemallocとシステムアロケータの選択では足りず、全く新しいカスタムアロケータが必要になることがあります。この場合、アロケータAPI(例えば alloc_systemalloc_jemalloc と同様のもの)を実装した独自のクレートを書くことになります。例として、 alloc_system の簡素な注釈付きバージョンを見てみましょう。

// only needed for rustdoc --test down below #![feature(lang_items)] //// The compiler needs to be instructed that this crate is an allocator in order //// to realize that when this is linked in another allocator like jemalloc should //// not be linked in // コンパイラがリンク時に他のアロケータ(例えばjemalloc)とリンクしてしまうことを防ぐため、 // このクレートがアロケータであることを示す必要があります。 #![feature(allocator)] #![allocator] //// Allocators are not allowed to depend on the standard library which in turn //// requires an allocator in order to avoid circular dependencies. This crate, //// however, can use all of libcore. // 循環依存を避けるため、アロケータはアロケータを使う標準ライブラリに依存してはいけません。 // しかしlibcoreについては全ての機能を使用できます。 #![no_std] //// Let's give a unique name to our custom allocator // カスタムアロケータに固有の名前を付けてください。 #![crate_name = "my_allocator"] #![crate_type = "rlib"] //// Our system allocator will use the in-tree libc crate for FFI bindings. Note //// that currently the external (crates.io) libc cannot be used because it links //// to the standard library (e.g. `#![no_std]` isn't stable yet), so that's why //// this specifically requires the in-tree version. // この独自アロケータはFFIバインディングのためにRustコンパイラのソースコードに // 同梱されているlibcクレートを使います。 // 注記: 現在の外部libc(crate.ioのもの)は標準ライブラリとリンクしているため使用できません // ( `#![no_std]` がまだstableではないためです)。そのため特別に同梱版のlibcが必要になります。 #![feature(libc)] extern crate libc; //// Listed below are the five allocation functions currently required by custom //// allocators. Their signatures and symbol names are not currently typechecked //// by the compiler, but this is a future extension and are required to match //// what is found below. //// //// Note that the standard `malloc` and `realloc` functions do not provide a way //// to communicate alignment so this implementation would need to be improved //// with respect to alignment in that aspect. // 今のところ、カスタムアロケータには以下の5つのメモリ割り当て関数が必要です。 // コンパイラは現時点では関数のシグネチャとシンボル名の型検査を行いません。 // しかし、将来的には以下の型に一致する必要があります。 // // 注記: 標準の `malloc` と `realloc` 関数へはアラインメントについての情報を // 渡すことができません。そのためアラインメントを考慮するためにはこの実装は // 改良の必要があります。 #[no_mangle] pub extern fn __rust_allocate(size: usize, _align: usize) -> *mut u8 { unsafe { libc::malloc(size as libc::size_t) as *mut u8 } } #[no_mangle] pub extern fn __rust_deallocate(ptr: *mut u8, _old_size: usize, _align: usize) { unsafe { libc::free(ptr as *mut libc::c_void) } } #[no_mangle] pub extern fn __rust_reallocate(ptr: *mut u8, _old_size: usize, size: usize, _align: usize) -> *mut u8 { unsafe { libc::realloc(ptr as *mut libc::c_void, size as libc::size_t) as *mut u8 } } #[no_mangle] pub extern fn __rust_reallocate_inplace(_ptr: *mut u8, old_size: usize, _size: usize, _align: usize) -> usize { // old_size // this api is not supported by libc old_size // このAPIはlibcではサポートされていません。 } #[no_mangle] pub extern fn __rust_usable_size(size: usize, _align: usize) -> usize { size } // just needed to get rustdoc to test this fn main() {} #[lang = "panic_fmt"] fn panic_fmt() {} #[lang = "eh_personality"] fn eh_personality() {} #[lang = "eh_unwind_resume"] extern fn eh_unwind_resume() {} #[no_mangle] pub extern fn rust_eh_register_frames () {} #[no_mangle] pub extern fn rust_eh_unregister_frames () {}
// コンパイラがリンク時に他のアロケータ(例えばjemalloc)とリンクしてしまうことを防ぐため、
// このクレートがアロケータであることを示す必要があります。
#![feature(allocator)]
#![allocator]

// 循環依存を避けるため、アロケータはアロケータを使う標準ライブラリに依存してはいけません。
// しかしlibcoreについては全ての機能を使用できます。
#![no_std]

// カスタムアロケータに固有の名前を付けてください。
#![crate_name = "my_allocator"]
#![crate_type = "rlib"]

// この独自アロケータはFFIバインディングのためにRustコンパイラのソースコードに
// 同梱されているlibcクレートを使います。
// 注記: 現在の外部libc(crate.ioのもの)は標準ライブラリとリンクしているため使用できません
// ( `#![no_std]` がまだstableではないためです)。そのため特別に同梱版のlibcが必要になります。
#![feature(libc)]
extern crate libc;

// 今のところ、カスタムアロケータには以下の5つのメモリ割り当て関数が必要です。
// コンパイラは現時点では関数のシグネチャとシンボル名の型検査を行いません。
// しかし、将来的には以下の型に一致する必要があります。
//
// 注記: 標準の `malloc` と `realloc` 関数へはアラインメントについての情報を
// 渡すことができません。そのためアラインメントを考慮するためにはこの実装は
// 改良の必要があります。

#[no_mangle]
pub extern fn __rust_allocate(size: usize, _align: usize) -> *mut u8 {
    unsafe { libc::malloc(size as libc::size_t) as *mut u8 }
}

#[no_mangle]
pub extern fn __rust_deallocate(ptr: *mut u8, _old_size: usize, _align: usize) {
    unsafe { libc::free(ptr as *mut libc::c_void) }
}

#[no_mangle]
pub extern fn __rust_reallocate(ptr: *mut u8, _old_size: usize, size: usize,
                                _align: usize) -> *mut u8 {
    unsafe {
        libc::realloc(ptr as *mut libc::c_void, size as libc::size_t) as *mut u8
    }
}

#[no_mangle]
pub extern fn __rust_reallocate_inplace(_ptr: *mut u8, old_size: usize,
                                        _size: usize, _align: usize) -> usize {
    old_size // このAPIはlibcではサポートされていません。
}

#[no_mangle]
pub extern fn __rust_usable_size(size: usize, _align: usize) -> usize {
    size
}

このクレートをコンパイルすると、次のように使えるようになります。

extern crate my_allocator; fn main() { // let a = Box::new(8); // allocates memory via our custom allocator crate let a = Box::new(8); // カスタムアロケータによるメモリ割り当て println!("{}", a); }
extern crate my_allocator;

fn main() {
    let a = Box::new(8); // カスタムアロケータによるメモリ割り当て
    println!("{}", a);
}

カスタムアロケータの制限

カスタムアロケータを使用する場合、コンパイルエラーの原因となりうるいくつかの制限があります。