% Пользовательские менеджеры памяти
Выделение памяти — это не самая простая задача, и Rust обычно заботится об этом сам, но часто нужно тонко управлять выделением памяти. Компилятор и стандартная библиотека в настоящее время позволяют глобально переключить используемый менеджер во время компиляции. Описание сейчас находится в RFC 1183, но здесь мы рассмотрим как сделать ваш собственный менеджер.
Стандартный менеджер памяти
В настоящее время компилятор содержит два стандартных менеджера: alloc_system
и alloc_jemalloc
(однако у некоторых платформ отсутствует jemalloc).
Эти менеджеры стандартны для контейнеров Rust и содержат реализацию подпрограмм
для выделения и освобождения памяти. Стандартная библиотека не компилируется
специально для использования только одного из них. Компилятор будет решать какой
менеджер использовать во время компиляции в зависимости от типа производимых
выходных артефактов.
По умолчанию исполняемые файлы сгенерированные компилятором будут использовать
alloc_jemalloc
(там где возможно). В таком случае компилятор "контролирует
весь мир", в том смысле что у него есть власть над окончательной компоновкой.
Однако динамические и статические библиотеки по умолчанию будут использовать
alloc_system
. Здесь Rust обычно в роли гостя в другом приложении или вообще в
другом мире, где он не может авторитетно решать какой менеджер использовать.
В результате он возвращается назад к стандартным API (таких как malloc
и
free
), для получения и освобождения памяти.
Переключение менеджеров памяти
Несмотря на то что в большинстве случаев нам подойдёт то, что компилятор выбирает по умолчанию, часто бывает необходимо настроить определенные аспекты. Для того, чтобы переопределить решение компилятора о том, какой именно менеджер использовать, достаточно просто скомпоновать с желаемым менеджером:
#![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); // выделение памяти с помощью jemalloc
println!("{}", a);
}
fn main() {}
Написание своего менеджера памяти
Иногда даже выбора между jemalloc и системным менеджером недостаточно и
необходим совершенно новый менеджер памяти. В этом случае мы напишем наш
собственный контейнер, который будет предоставлять API менеджера памяти (также
как и alloc_system
или alloc_jemalloc
). Для примера давайте рассмотрим
упрощенную и аннотированную версию alloc_system
:
// only needed for rustdoc --test down below #![feature(lang_items)] // Компилятору нужно указать, что этот контейнер является менеджером памяти, для // того что бы при компоновке он не использовал другой менеджер. #![feature(allocator)] #![allocator] // Менеджерам памяти не позволяют зависеть от стандартной библиотеки, которая в // свою очередь зависит от менеджера, чтобы избежать циклической зависимости. // Однако этот контейнер может использовать все из libcore. #![no_std] // Давайте дадим какое-нибудь уникальное имя нашему менеджеру. #![crate_name = "my_allocator"] #![crate_type = "rlib"] // Наш системный менеджер будет использовать поставляемый вместе с компилятором // контейнер libc для связи с FFI. Имейте ввиду, что на данный момент внешний // (crates.io) libc не может быть использован, поскольку он компонуется со // стандартной библиотекой (`#![no_std]` все еще нестабилен). #![feature(libc)] extern crate libc; // Ниже перечислены пять функций, необходимые пользовательскому менеджеру памяти. // Их сигнатуры и имена на данный момент не проверяются компилятором, но это // вскоре будет реализовано, так что они должны соответствовать тому, что // находится ниже. // // Имейте ввиду, что стандартные `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 // libc не поддерживает этот API } #[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 () {}
После того как мы скомпилировали этот контейнер, мы можем использовать его следующим образом:
extern crate my_allocator;
fn main() {
let a = Box::new(8); // выделение памяти с помощью нашего контейнера
println!("{}", a);
}
Ограничения пользовательских менеджеров памяти
Несколько ограничений при работе с пользовательским менеджером памяти, которые могут быть причиной ошибок компиляции:
-
Любой артефакт может быть скомпонован только с одним менеджером. Исполняемые файлы, динамические библиотеки и статические библиотеки должны быть скомпонованы с одним менеджером, и если не один не был указан, то компилятор сам выберет один. В то же время Rust библиотеки (rlibs) не нуждаются в компоновке с менеджером (но это возможно).
-
Потребитель какого-либо менеджера памяти имеет пометку
#![needs_allocator]
(в данном случае контейнерliballoc
) и какой-либо контейнер#[allocator]
не может транзитивно зависеть от контейнера, которому нужен менеджер (т.е. циклическая зависимость не допускается). Это означает, что менеджеры памяти в данный момент должны ограничить себя только libcore.