Буферный пул для максимальной скорости: квест по победе над Nginx

от автора

Вы когда-нибудь ловили себя на том, что пытаетесь выжать каждую миллисекунду из своего 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);     } }

🔍 Что здесь происходит?

  1. Мы используем Arc и Mutex для совместного использования и защиты буферов параллельным потокобезопасным способом.

  2. BufferPool создает пул буферов при запуске, каждый с фиксированной емкостью.

  3. 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; // Возвращаем его в пул     }); }

🤓 Что происходит?

  1. Мы используем семафор для управления максимальным количеством одновременных подключений. В конце концов, мы не собираемся расплавлять наши серверы. 🥵

  2. tokio::spawn создает легкие задачи, и каждая из них получает буфер из нашего пула

  3. Буферы очищаются и перерабатываются эффективно. Потому что мы заботимся о наших буферах, а память на свалке — это прошлый год. 🌱

📈 Почему это так быстро?

Избегая постоянного выделения и освобождения памяти, мы сокращаем накладные расходы и задержку. Наши буферы всегда готовы, как и ваш лучший друг, который всегда готов потусоваться. 🥳

Итак, если вы создаете HTTP-сервер и хотите превзойти Nginx, попробуйте Buffer Pools. Ваши пользователи (и ваши серверы) будут вам благодарны! 🙌

Есть вопросы или вы хотите обсудить другие безумные оптимизации? Оставьте комментарий ниже! Или просто расскажите мне, как продвигается ваш последний проект Rust. Я весь внимание!

Источники: https://github.com/evgenyigumnov/cblt


ссылка на оригинал статьи https://habr.com/ru/articles/857462/