Eugene-Usachev
@Eugene-Usachev

Куда утекает память в read Rust?

У меня есть код:

#[inline(always)]
    pub(crate) fn get(&self, key: &BinKey) -> Option<BinValue>{
        let res = self.get_index_and_file(key); // 1
        if res.is_none() {
            return None;
        }

        let (file, info) = unsafe { res.unwrap_unchecked() };
        let mut buf = vec![0; info.0 as usize];
        file.read().unwrap().read_at(info.1, &mut buf).expect("failed to read");

        return Some(BinValue::new(buf.as_slice()));
    }


Под комментарием 1 он получает Option<(Arc < RwLock< File > >, (u64, u64))>. Дальше создаётся буфер нужного размера и туда записываются данные из файла. read_at читает файл с указанной позиции, заполняя буфер (можно заменить на seek + read, ошибка от этого не изменится). BinValue по сути создаёт что-то вроде вектора, и ошибка не в нём.

Если оставить код таким, то через 10 400 000 вызовов этой функции с заполнение буфера 100-байтовыми значениями утечёт примерно 1 ГБ памяти. Я провёл некоторые наблюдения и обнаружил, что память утекает только с вызовом функции file.read().unwrap().read_at(info.1, &mut buf). expect тут ни разу не сработал. Эти тесты проводились в Docker, если нужна дополнительная информация, спросите в комментариях.

Почему утекает память?
  • Вопрос задан
  • 288 просмотров
Пригласить эксперта
Ответы на вопрос 1
vabka
@vabka Куратор тега Rust
TL;DR; 10400000 * 100 - это как раз почти гигабайт.
В текущем виде это компилироваться не должно, тк as_slice возвращает ссылку, а чтобы структура могла содержать в себе ссылку - нужно добавить лайфтайм в объявление структуры => этот же лайфтайм нужно будет указать в сигнатуре функции.
Сейчас ни того ни другого я не вижу и код не должен компилироваться по причине того что ты пытаешься вернуть ссылку на данные, которые живут только в рамках fn get.
Если же BinValue на самом деле владеет вектором, то это не утечка, а вполне ожидаемое поведение.
Смотри, где должен он дропаться в будущем.

1. Тут можно достаточно легко избавиться от unsafe:
let res = self.get_index_and_file(key);

if res.is_none() {
    return None;
}

let (file, info) = unsafe { res.unwrap_unchecked() };


Превращается в
let Some((file,info)) = self.get_index_and_file(key) else {
  return None;
};


2.
Вообще достаточно странный код.
На этой строке мы создали вектор - это ок. Он требует места в куче. Хотя странно, что info.0 у нас u64, а не usize
let mut buf = vec![0; info.0 as usize];
file.read().unwrap().read_at(info.1, &mut buf).expect("failed to read");

Тут мы файл прочитали. Возможно был смысл сделать BufRead, но не думаю, что он тут бы тут много чего сделал бы.
В остальном тут нет аллокаций.

3. А вот это уже выглядит реально подозрительно:
return Some(BinValue::new(buf.as_slice()));
Это вообще компилируется? Покажи, что из себя представляет BinValue.
По хорошему оно должно брать ownership над вектором.


Если оставить код таким, то через 10 400 000 вызовов этой функции с заполнение буфера 100-байтовыми значениями утечёт примерно 1 ГБ памяти

10 400 000 * 100 = как раз примерно гиг.
Если BinValue в действительности берёт ownership, то это вполне себе ожидаемое поведение, а не утечка.

4. Ещё я бы на всякий случай убрал #[inline(always)]
Ответ написан
Ваш ответ на вопрос

Войдите, чтобы написать ответ

Похожие вопросы