Вы когда-нибудь ловили себя на том, что пытаетесь выжать каждую миллисекунду из своего HTTP-сервера? Возможно, вы слышали, что «Nginx — король скорости», и думали: «Вызов принят!» Что ж, давайте поговорим об обработке небольшого контента (менее 100 КБ) в десять раз быстрее обычного.
Секретный соус? Эффективное управление памятью с помощью буферных пулов. 👇
🧐 Проблема
Каждому HTTP-запросу нужен буфер для обработки контента. Начнем с простого:
let mut buf = Vec::with_capacity(8192);
Звучит достаточно невинно, не так ли? Но для высокопроизводительного сервера выделение и освобождение этих буферов тысячи раз в секунду является серьезным узким местом. Нам нужно что-то более быстрое, более эффективное — что-то, что заставит попотеть даже Nginx. 💦
🦸♂️ Решение: буферный пул!
Я создал BufferPool, который предварительно выделяет буферы и повторно использует их, все в великолепном асинхронном Rust:
use std::sync::Arc; use tokio::sync::Mutex; pub type SmartVector = Arc<Mutex<Vec<u8>>>; pub struct BufferPool { pool: Arc<Mutex<Vec<SmartVector>>>, } impl BufferPool { pub fn new(buffer_count: usize, buffer_size: usize) -> Self { let pool = (0..buffer_count) .map(|_| Arc::new(Mutex::new(Vec::with_capacity(buffer_size)))) .collect(); BufferPool { pool: Arc::new(Mutex::new(pool)), } } pub async fn get_buffer(&self) -> Option<SmartVector> { let mut pool = self.pool.lock().await; pool.pop() } pub async fn return_buffer(&self, buffer: SmartVector) { let mut pool = self.pool.lock().await; pool.push(buffer); } }
🔍 Что здесь происходит?
-
Мы используем Arc и Mutex для совместного использования и защиты буферов параллельным потокобезопасным способом.
-
BufferPool создает пул буферов при запуске, каждый с фиксированной емкостью.
-
get_buffer извлекает буфер из пула, а return_buffer возвращает его обратно. Просто и мило!
🛠️ Профессиональное использование BufferPool
Посмотрите на основной цикл, где происходит магия:
let max_connections = 5000; let BUF_SIZE = 8192; let semaphore = Arc::new(Semaphore::new(max_connections)); let buffer_pool = Arc::new(BufferPool::new(max_connections, BUF_SIZE)); loop { let semaphore = semaphore.clone(); let permit = semaphore.acquire_owned().await?; let buffer_pool_arc = buffer_pool.clone(); tokio::spawn(async move { let _permit = permit; // Сохраняем разрешение, пока не закончим обработку // Получаем буфер из пула let buffer = buffer_pool_arc.get_buffer().await.unwrap(); // 🚀 Делаем что-то быстрое и удивительное с буфером здесь buffer.lock().await.clear(); // Очищаем буфер для повторного использования buffer_pool_arc.return_buffer(buffer).await; // Возвращаем его в пул }); }
🤓 Что происходит?
-
Мы используем семафор для управления максимальным количеством одновременных подключений. В конце концов, мы не собираемся расплавлять наши серверы. 🥵
-
tokio::spawn создает легкие задачи, и каждая из них получает буфер из нашего пула
-
Буферы очищаются и перерабатываются эффективно. Потому что мы заботимся о наших буферах, а память на свалке — это прошлый год. 🌱
📈 Почему это так быстро?
Избегая постоянного выделения и освобождения памяти, мы сокращаем накладные расходы и задержку. Наши буферы всегда готовы, как и ваш лучший друг, который всегда готов потусоваться. 🥳
Итак, если вы создаете HTTP-сервер и хотите превзойти Nginx, попробуйте Buffer Pools. Ваши пользователи (и ваши серверы) будут вам благодарны! 🙌
Есть вопросы или вы хотите обсудить другие безумные оптимизации? Оставьте комментарий ниже! Или просто расскажите мне, как продвигается ваш последний проект Rust. Я весь внимание!
Источники: https://github.com/evgenyigumnov/cblt
ссылка на оригинал статьи https://habr.com/ru/articles/857462/
Добавить комментарий