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

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

https://habr.com/ru/articles/857462/?utm_source=habrahabr&utm_medium=rss&utm_campaign=857462

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

Источники

Report Page