Обновить

Комментарии 6

Да, тут вся соль в многопоточности. Как локально кэшировать выделяемые объекты и как затем возвращать свободные - в глобальный пул. Лучше, чем это делают jemalloc и tcmalloc.

Как вариант решения кейса для многопоточности - атомарный индекс для вершины стека.

Когда приходит запрос на выделение сегмента - первым делом резервируется сегмент через инкремент индекса.

Или альтернативный и самый простой вариант - thread_local пул. Но тогда потребление памяти будет не рациональным.

Пул используется более широко: когда создать объект дорого и проще использовать освободившийся. Например, сетевые соединения.

Почему вы взяли для примера критерий экономии памяти не понятно.

Хорошая реализация для базового случая – особенно стек индексов вместо linked list: он лучше дружит с кэшом при одном потоке. Кстати, к разделу про потокобезопасность стоит добавить ещё один сценарий: даже с мьютексом пул может деградировать на SMP-системах из-за ложного разделения кэша (false sharing). Если поток A держит сегмент #5, а поток B – сегмент #6, и оба помещаются на одну кэш-линию (64 байта на x86), каждая запись одного потока инвалидирует строку у другого. Лечится выравниванием сегментов до размера кэш-линии – с небольшим компромиссом по памяти.

пул классно, но с потоками или каналами всё становится интереснее, например, общение через каналы по состояниям с общим владением, тогда кусочки опять откуда брать придётся ), чтобы сгенерировать, а как паттерн да удобно...

я про ситуацию когда этот паттерн на синхро-уровне по владению, тогда в этих красках кусочки приходящии от генерации тоже должны крутиться(наверно из пула тоже) ). Я понимаю что описываю словами, но в коде гемор, получается 2-3 пула одного и тоже, одно на владение между каналами, другое на паралельный запуск генерации, тогда получается на такую систему 2 версии одного и того же)

Скрытый текст
pub struct ChunkTaskResult {
    pub pos: Vec3i,
    pub task_type: TaskType,
    pub chunk_data: ChunkDataTask,
    // pub light_data: Option<Arc<[u8; 4096]>>,
    pub vertices: Option<Vec<VoxelVertex>>,
    pub indices: Option<Vec<u32>>,
    pub revision: u32, // К какой версии данных относится этот меш
    // Делаем Option, так как соседи нужны ТОЛЬКО для TaskType::GenerateMesh
    pub neighbors: Option<MeshNeighbors>,
}
...
#[allow(unused)]
pub struct World {
    pub chunks: HashMap<Vec3i, VoxelChunk>,
    // Приемник результатов мешизации
    cave_noise: Perlin,
    pub loading_queue: HashSet<Vec3i>, // Для GenerateData
    pub meshing_queue: HashSet<Vec3i>, // Для GenerateMesh
    // Канал для получения готовых чанков
    rx_done: Receiver<ChunkTaskResult>,
    // Канал для отправки заданий (если нужно динамически)
    tx_tasks: Sender<ChunkTaskResult>,
    pub allocator: mxg11alc::alloc::Gsa, пул слотов на 258 мебагайт для карточки - синхронизированные куски которыми владеют каналы по отрисовке )
    pub mega_vbo: u32,
    pub mega_ebo: u32,
}
...
и в такой ситуации ворлд каналы в апдейт синхронизируют общее владение
казалось бы ок, но
  fn new() -> Self{
...
        std::thread::spawn(move || {
            while let Ok(task) = rx_tasks.recv() {
                match task.task_type {
                    TaskType::GenerateData =>{
                        //1 (16*4096)
                        let raw_data=generate_c(&task.pos, &seed);
...
                    TaskType::GenerateMesh =>{
                        match task.chunk_data {
                            ChunkDataTask::Full(..)=>{
                                if let Some(nb) = task.neighbors {
                                    //2 а тут либо плохие случаи шихматная доска(либо компромисс 7к каждый на первое время )*16)
                                    let mut vertices = Vec::new();
                                    //3 как выше 7к*16
                                    let mut indices = Vec::new();
                                    build_seamless_mesh(&nb, &mut vertices, &mut indices);
}//fn new Self
...
fn update можно настроить чтобы он брал и чистил, но системе не будет хватать опять тех же пулов чтобы откудато брать пулы на генерацию и создания мешев вообще интересная ситуация )

я пока так оставил(тоесть ЦПУ пула нет просто чищу приходящую в канал память и это не грузит проц на дистанции 16х2х16 чанков(16х16х16 блоков) по горизонтали), 16 потоков держит по синхронизации и не грузит проц)

только что проверил на сколько хватило опыта с зеркалом на ЦПУ, нагрузка возросла и видимо из-за нетривиальности памяти аллоцированных контейнеров Vec - их я заменил зеркалом, я словил рабочую сборку, но с багами, но теперь надо иметь опыт, как в такой ситуации проводить память, утилизировать валидно, потомучто ОС пока что лучше освобождает эти кусочки памяти... Кароче тема интересная, потомучто там реализация интересная, не надо ничего копировать, просто слоты шардируются, но как возращать валидно память и делать так бесконечный мир пока не понятно мне ), потомучто у меня канал делает бесконечный мир, и когда канал перестаёт владеть(он владеет дистанцией) происходит чистка )

Зарегистрируйтесь на Хабре, чтобы оставить комментарий

Публикации