
Комментарии 4
Да, тут вся соль в многопоточности. Как локально кэшировать выделяемые объекты и как затем возвращать свободные - в глобальный пул. Лучше, чем это делают jemalloc и tcmalloc.
Пул используется более широко: когда создать объект дорого и проще использовать освободившийся. Например, сетевые соединения.
Почему вы взяли для примера критерий экономии памяти не понятно.
Хорошая реализация для базового случая – особенно стек индексов вместо 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 потоков держит по синхронизации и не грузит проц)
Пул объектов: паттерн эффективного управления памятью