๐ง _์ฌ์ธต_ํ๊ตฌ_Memory_Management_Performance
Source: Dev.to
(๋ฒ์ญํ ํ ์คํธ๋ฅผ ์ ๊ณตํด ์ฃผ์๋ฉด ํ๊ตญ์ด๋ก ๋ฒ์ญํด ๋๋ฆฌ๊ฒ ์ต๋๋ค.)
๐ก ๋ฉ๋ชจ๋ฆฌ ๊ด๋ฆฌ์ ํต์ฌ ๊ณผ์
ํ๋ ์น ์ ํ๋ฆฌ์ผ์ด์ ์ ์ ๊ธฐ์ ์ผ๋ก ์ธ ๊ฐ์ง ๊ทผ๋ณธ์ ์ธ ๋ฌธ์ ์ ์ง๋ฉดํฉ๋๋ค:
| ๋์ ๊ณผ์ | ์ค์ํ ์ด์ |
|---|---|
| ๐จ ๋ฉ๋ชจ๋ฆฌ ๋์ | ํด์ ๋์ง ์์ ๊ฐ์ฒด๊ฐ ๊ฒฐ๊ตญ ํ์ ๊ณ ๊ฐ์์ผ ์ถฉ๋์ด๋ OOM ์ค๋ฅ๋ฅผ ์ผ์ผํต๋๋ค. |
| โฐ GC ์ผ์์ ์ง | Stopโtheโworld ์ผ์์ ์ง๋ก ์ธํด ์์ฒญ ์ง์ฐ ์๊ฐ์ด ์ฆ๊ฐํ๋ฉฐ, ์ด๋ ์ง์ฐ์ ๋ฏผ๊ฐํ ์๋น์ค์์๋ ์ฉ๋ฉ๋ ์ ์์ต๋๋ค. |
| ๐ ๋ฉ๋ชจ๋ฆฌ ๋จํธํ | ๋ฐ๋ณต์ ์ธ ํ ๋น/ํด์ ๋ก ๋ฉ๋ชจ๋ฆฌ๊ฐ ๋จํธํ๋์ด ์บ์ ํจ์จ์ด ๊ฐ์ํ๊ณ ์ ์ฒด ์ฒ๋ฆฌ๋์ด ์ ํ๋ฉ๋๋ค. |
๐ ๋ฉ๋ชจ๋ฆฌ ๊ด๋ฆฌ ์ฑ๋ฅ ๋น๊ต
๐ฌ ๋ฉ๋ชจ๋ฆฌโ์ฌ์ฉ ํจ์จ์ฑ ํ ์คํธ
์๋๋ฆฌ์ค: 1โฏ๋ฐฑ๋ง ๋์ ์ฐ๊ฒฐ, ํ๋ ์์ํฌ ๊ฐ ๋์ผ ์ํฌ๋ก๋.
| Framework | ๋ฉ๋ชจ๋ฆฌ ์ฌ์ฉ๋ | GC ์ผ์์ ์ง ์๊ฐ | ํ ๋น ํ์ | ํด์ ํ์ |
|---|---|---|---|---|
| Hyperlane Framework | 96โฏMB | 0โฏms | 12โฏ543 | 12โฏ543 |
| Rust Standard Library | 84โฏMB | 0โฏms | 15โฏ672 | 15โฏ672 |
| Go Standard Library | 98โฏMB | 15โฏms | 45โฏ234 | 45โฏ234 |
| Tokio | 128โฏMB | 0โฏms | 18โฏ456 | 18โฏ456 |
| Gin Framework | 112โฏMB | 23โฏms | 52โฏ789 | 52โฏ789 |
| Rocket Framework | 156โฏMB | 0โฏms | 21โฏ234 | 21โฏ234 |
| Node Standard Library | 186โฏMB | 125โฏms | 89โฏ456 | 89โฏ456 |
๐ ๋ฉ๋ชจ๋ฆฌโํ ๋น ์ง์ฐ ๋น๊ต
| Framework | ํ๊ท ํ ๋น ์๊ฐ | P99 ํ ๋น ์๊ฐ | ์ต๋ ํ ๋น ์๊ฐ | ํ ๋น ์คํจ์จ |
|---|---|---|---|---|
| Hyperlane Framework | 0.12โฏยตs | 0.45โฏยตs | 2.34โฏยตs | 0โฏ% |
| Rust Standard Library | 0.15โฏยตs | 0.52โฏยตs | 2.78โฏยตs | 0โฏ% |
| Tokio | 0.18โฏยตs | 0.67โฏยตs | 3.45โฏยตs | 0โฏ% |
| Rocket Framework | 0.21โฏยตs | 0.78โฏยตs | 4.12โฏยตs | 0โฏ% |
| Go Standard Library | 0.89โฏยตs | 3.45โฏยตs | 15.67โฏยตs | 0.01โฏ% |
| Gin Framework | 1.23โฏยตs | 4.56โฏยตs | 23.89โฏยตs | 0.02โฏ% |
| Node Standard Library | 2.45โฏยตs | 8.92โฏยตs | 45.67โฏยตs | 0.05โฏ% |
๐ฏ ํต์ฌ ๋ฉ๋ชจ๋ฆฌโ๊ด๋ฆฌ ๊ธฐ์ ๋ถ์
๐ ์ ๋กโ๊ฐ๋น์ง ์ค๊ณ
Hyperlane ํ๋ ์์ํฌ๋ ์ธ ๊ฐ์ง ์๋ณด์ ์ธ ๊ธฐ๋ฒ์ ํตํด ๊ฑฐ์ ์ ๋ก์ ๊ฐ๊น์ด ๊ฐ๋น์ง ์์ฑ์ ๋ฌ์ฑํฉ๋๋ค.
1๏ธโฃ ๊ฐ์ฒดโํ ๊ธฐ์
// Hyperlane framework's objectโpool implementation
struct MemoryPool<T> {
objects: Vec<T>,
free_list: Vec<usize>,
capacity: usize,
}
impl<T> MemoryPool<T> {
fn new(capacity: usize) -> Self {
let objects = Vec::with_capacity(capacity);
let mut free_list = Vec::with_capacity(capacity);
for i in 0..capacity {
free_list.push(i);
}
Self { objects, free_list, capacity }
}
fn allocate(&mut self, value: T) -> Option<usize> {
if let Some(index) = self.free_list.pop() {
if index >= self.objects.len() {
self.objects.push(value);
} else {
self.objects[index] = value;
}
Some(index)
} else {
None
}
}
fn deallocate(&mut self, index: usize) {
// Return the slot to the free list
self.free_list.push(index);
}
}
2๏ธโฃ ์ฐ๊ฒฐโํธ๋ค๋ฌ ๋ฒํผ (์์)
use std::collections::HashMap;
struct ConnectionHandler {
// Preโallocated read buffer
read_buffer: Vec<u8>,
// Preโallocated write buffer
write_buffer: Vec<u8>,
// Preโallocated header storage
headers: HashMap<String, String>,
}
impl ConnectionHandler {
fn new() -> Self {
Self {
read_buffer: Vec::with_capacity(8_192), // 8โฏKB
write_buffer: Vec::with_capacity(8_192), // 8โฏKB
headers: HashMap::with_capacity(16), // space for 16 headers
}
}
}
4๏ธโฃ ๋ฉ๋ชจ๋ฆฌโ๋ ์ด์์ ์ต์ ํ
// Struct layout tuned for cache friendliness
#[repr(C)]
struct OptimizedStruct {
// Highโfrequency fields grouped together
id: u64, // 8โbyte aligned
status: u32, // 4โbyte
flags: u16, // 2โbyte
version: u16, // 2โbyte
// Lowโfrequency fields placed at the end
metadata: Vec<u8>, // heapโallocated pointer
}
์ธ ๊ฐ์ง ๊ธฐ๋ฒโ๊ฐ์ฒด ํ๋ง, ์ฌ์ ํ ๋น ๋ฒํผ, ๊ทธ๋ฆฌ๊ณ ์บ์ ์นํ์ ์ธ ๋ ์ด์์โ์ด ํจ๊ป ์๋ํ์ฌ ํ ๋น์ ์์ธก ๊ฐ๋ฅํ๊ฒ ๋ง๋ค๊ณ ๋ฐํ์ ๊ฐ๋น์ง ์ปฌ๋ ์ ์ ์ต์ํํฉ๋๋ค.
๐ป ๋ฉ๋ชจ๋ฆฌโ๊ด๋ฆฌ ๊ตฌํ ๋ถ์
๐ข Node.js ๋ฉ๋ชจ๋ฆฌโ๊ด๋ฆฌ ๋ฌธ์
// Example: perโrequest allocations in a naive Node.js server
const http = require('http');
const server = http.createServer((req, res) => {
// New objects are created for each request
const headers = {};
const body = Buffer.alloc(1024); // heap allocation
// V8 GC pauses become noticeable under load
res.writeHead(200, { 'Content-Type': 'text/plain' });
res.end('Hello');
});
server.listen(60000);
๋ฌธ์ ๋ถ์
| ์ฆ์ | ๊ทผ๋ณธ ์์ธ |
|---|---|
| ๋น๋ฒํ ๊ฐ์ฒด ์์ฑ | ๊ฐ ์์ฒญ๋ง๋ค ์๋ก์ด headers์ body ๊ฐ์ฒด๊ฐ ํ ๋น๋์ด V8์ ์ธ๋๋ณ GC์ ์๋ ฅ์ด ๊ฐํด์ง๋๋ค. |
| GCโ์ ๋ฐ ์ง์ฐ ์คํ์ดํฌ | ๋์ ๋์์ฑ ํ์์ GC๊ฐ stopโtheโworld ์ผ์ ์ ์ง๋ฅผ ์ผ์ผ์ผ ์์ฒญ ์ง์ฐ์ด ์ฆ๊ฐํฉ๋๋ค. |
| ๋ฉ๋ชจ๋ฆฌ ๋จํธํ | Buffer์ ๋ฐ๋ณต์ ์ธ ํ ๋นยทํด์ ๊ฐ ํ์ ๋จํธํ์์ผ ํ ๋น ์ฒ๋ฆฌ๋์ ๊ฐ์์ํต๋๋ค. |
์ํ ๋ฐฉ์์ผ๋ก๋ ์ผ๋ฐ์ ์ผ๋ก ๊ฐ์ฒด ํ๋ง, ๋ฒํผ ์ฌ์ฌ์ฉ, ๋๋ ์ค์ํ ๊ฒฝ๋ก๋ฅผ ๋ค์ดํฐ๋ธ ํ์ฅ์ด๋ ๋์ฒด ๋ฐํ์์ผ๋ก ์ด๋ํ๋ ๊ฒ์ด ํฌํจ๋ฉ๋๋ค.
๐น Go์ ๋ฉ๋ชจ๋ฆฌ ๊ด๋ฆฌ ๊ธฐ๋ฅ
Go์ ๋ฉ๋ชจ๋ฆฌ ๊ด๋ฆฌ๋ ๋น๊ต์ ํจ์จ์ ์ด์ง๋ง, ์์ง ๊ฐ์ ์ฌ์ง๊ฐ ์์ต๋๋ค.
package main
import (
"fmt"
"net/http"
"sync"
)
var bufferPool = sync.Pool{
New: func() interface{} {
return make([]byte, 1024)
},
}
func handler(w http.ResponseWriter, r *http.Request) {
// Use sync.Pool to reduce memory allocation
buffer := bufferPool.Get().([]byte)
defer bufferPool.Put(buffer)
fmt.Fprintf(w, "Hello")
}
func main() {
http.HandleFunc("/", handler)
http.ListenAndServe(":60000", nil)
}
์ฅ์ ๋ถ์
- sync.Pool โ ๊ฐ๋จํ ๊ฐ์ฒด ํ ๋ฉ์ปค๋์ฆ.
- ๋์์ฑ ์์ ์ฑ โ ๊ฐ๋น์ง ์ปฌ๋ ํฐ๊ฐ ๋์์ ์คํ๋์ด ์ผ์ ์ ์ง ์๊ฐ์ด ์งง์์ง๋๋ค.
- ๋ฉ๋ชจ๋ฆฌ ์์ถ์ฑ โ Go์ ํ ๋น์๋ ๋น๊ต์ ํจ์จ์ ์ ๋๋ค.
๋จ์ ๋ถ์
- GC ์ผ์ ์ ์ง โ ์ฌ์ ํ ์ง์ฐ ์๊ฐ์ ๋ฏผ๊ฐํ ์ ํ๋ฆฌ์ผ์ด์ ์ ์ํฅ์ ์ค๋๋ค.
- ๋ฉ๋ชจ๋ฆฌ ์ฌ์ฉ๋ โ Go ๋ฐํ์์ด ์ถ๊ฐ ์ค๋ฒํค๋๋ฅผ ๋ฐ์์ํต๋๋ค.
- ํ ๋น ์ ๋ต โ ์์ ๊ฐ์ฒด ํ ๋น์ด ์์ ํ ์ต์ ํ๋์ง ์์ ์ ์์ต๋๋ค.
๐ Rust์ ๋ฉ๋ชจ๋ฆฌโ๊ด๋ฆฌ ์ฅ์
use std::io::prelude::*;
use std::net::{TcpListener, TcpStream};
fn handle_client(mut stream: TcpStream) {
// Zeroโcost abstraction โ memory layout determined at compile time
let mut buffer = [0u8; 1024]; // Stack allocation
// Ownership system ensures memory safety
let response = b"HTTP/1.1 200 OK\r\n\r\nHello";
stream.write_all(response).unwrap();
stream.flush().unwrap();
// Memory automatically released when the function ends
}
fn main() {
let listener = TcpListener::bind("127.0.0.1:60000").unwrap();
for stream in listener.incoming() {
let stream = stream.unwrap();
handle_client(stream);
}
}
์ฅ์ ๋ถ์
- Zeroโcost abstractions โ ์ปดํ์ผ ์ ์ต์ ํ๊ฐ ์ด๋ฃจ์ด์ง๋ฉฐ ๋ฐํ์ ์ค๋ฒํค๋๊ฐ ์์ต๋๋ค.
- No GC pauses โ ๊ฐ๋น์ง ์ปฌ๋ ์ ์ผ๋ก ์ธํ ์ง์ฐ์ด ์ฌ๋ผ์ง๋๋ค.
- Memory safety โ ์์ ๊ถ ์์คํ ์ด ์ปดํ์ผ ํ์์ ์์ ์ฑ์ ๋ณด์ฅํฉ๋๋ค.
- Precise control โ ๊ฐ๋ฐ์๊ฐ ๋ฉ๋ชจ๋ฆฌ๊ฐ ์ธ์ ํ ๋น๋๊ณ ํด์ ๋๋์ง๋ฅผ ์ ํํ ๊ฒฐ์ ํ ์ ์์ต๋๋ค.
๋์ ๊ณผ์ ๋ถ์
- Learning curve โ ์์ ๊ถ๊ณผ ๋์ฌ ๊ท์น์ ์ตํ๋ ๋ฐ ์๊ฐ์ด ํ์ํฉ๋๋ค.
- Compilation time โ ์๋ช (lifetime) ๋ถ์์ผ๋ก ์ธํด ๋น๋ ์๊ฐ์ด ๋์ด๋ ์ ์์ต๋๋ค.
- Development efficiency โ ๊ฐ๋น์ง ์ปฌ๋ ์ ๊ธฐ๋ฐ ์ธ์ด์ ๋นํด ๊ฐ๋ฐ ํจ์จ์ฑ์ด ๋ฎ์ ์ ์์ต๋๋ค.
Source:
๐ฏ Production Environment Memory Optimization Practice
๐ช ์ ์์๊ฑฐ๋ ์์คํ ๋ฉ๋ชจ๋ฆฌ ์ต์ ํ
๊ฐ์ฒด ํ ํ์ฉ
// Product information object pool
struct ProductPool {
pool: MemoryPool,
}
impl ProductPool {
fn get_product(&mut self) -> Option<ProductHandle> {
self.pool.allocate(Product::new())
}
fn return_product(&mut self, handle: ProductHandle) {
self.pool.deallocate(handle.index());
}
}
๋ฉ๋ชจ๋ฆฌ ์ฌ์ ํ ๋น
// Shoppingโcart memory preโallocation
struct ShoppingCart {
items: Vec<Product>, // Preโallocated capacity
total: f64,
discount: f64,
}
impl ShoppingCart {
fn new() -> Self {
Self {
items: Vec::with_capacity(20), // Reserve space for 20 products
total: 0.0,
discount: 0.0,
}
}
}
๐ณ ๊ฒฐ์ ์์คํ ๋ฉ๋ชจ๋ฆฌ ์ต์ ํ
์ ๋กโ๋ณต์ฌ ์ค๊ณ
use tokio::io::AsyncReadExt;
use std::net::TcpStream;
// A static buffer that lives for the whole program lifetime
static mut PAYMENT_BUFFER: [u8; 4096] = [0; 4096];
async fn process_payment(stream: &mut TcpStream) -> Result<(), std::io::Error> {
// SAFETY: The buffer is only accessed by this async task.
let buffer = unsafe { &mut PAYMENT_BUFFER };
stream.read_exact(buffer).await?;
// Direct processing, no extra copying
let payment = parse_payment(buffer)?;
process_payment_internal(payment).await?;
Ok(())
}
๋ฉ๋ชจ๋ฆฌ ํ ๊ด๋ฆฌ
use once_cell::sync::Lazy;
// A pool that holds up to 10โฏ000 preโallocated paymentโtransaction objects
static PAYMENT_POOL: Lazy<MemoryPool<PaymentTransaction>> = Lazy::new(|| {
MemoryPool::new(10_000)
});
๐ฎ ๋ฏธ๋ ๋ฉ๋ชจ๋ฆฌ ๊ด๋ฆฌ ํธ๋ ๋
๐ ํ๋์จ์ด ์ง์ ๋ฉ๋ชจ๋ฆฌ ๊ด๋ฆฌ
NUMAโ์ธ์ ํ ๋น
// NUMAโaware memory allocation (requires a crate that wraps libnuma)
fn numa_aware_allocate(size: usize) -> *mut u8 {
let node = get_current_numa_node();
unsafe { numa_alloc_onnode(size, node) }
}
์ง์ ๋ฉ๋ชจ๋ฆฌ
// Simple wrapper for persistentโmemoryโmapped files
struct PersistentMemory {
ptr: *mut u8,
size: usize,
}
impl PersistentMemory {
fn new(size: usize) -> Self {
// `pmem_map_file` is a thin wrapper around libpmem
let ptr = unsafe { pmem_map_file(size) };
Self { ptr, size }
}
}
๐ง ์ง๋ฅํ ๋ฉ๋ชจ๋ฆฌ ๊ด๋ฆฌ
๋จธ์ ๋ฌ๋ ๊ธฐ๋ฐ ํ ๋น
// A โsmartโ allocator that uses a trained model to pick the best strategy
struct SmartAllocator {
model: AllocationModel,
history: Vec<AllocationRecord>,
}
impl SmartAllocator {
fn predict_allocation(&self, size: usize) -> AllocationStrategy {
self.model.predict(size, &self.history)
}
}
์ ์ฝ๋ ์ค๋ํซ์ ํ์ฌ ๊ถ์ฅ๋๋ ํจํด(๊ฐ์ฒด ํ, ์ฌ์ ํ ๋น, ์ ๋กโ๋ณต์ฌ I/O)์ ๋ณด์ฌ์ฃผ๋ฉฐ, ๋ฉ๋ชจ๋ฆฌ ๊ด๋ฆฌ๊ฐ ํ๋์จ์ด ์ง์ ๊ธฐ์ ๊ณผ AI ๊ธฐ๋ฐ ํ ๋น ๊ฒฐ์ ์ผ๋ก ํฅํ๊ณ ์์์ ์ฟ๋ณผ ์ ์์ต๋๋ค.
๐ฏ ์์ฝ
- Go๋ ํธ๋ฆฌํ ํ๋ง๊ณผ ๋์ ๊ฐ๋น์ง ์ปฌ๋ ์ ์ ์ ๊ณตํ์ง๋ง, ์ฌ์ ํ ์ผ์ ์ ์ง ์๊ฐ๊ณผ ๋ฐํ์ ์ค๋ฒํค๋๊ฐ ๋ฐ์ํฉ๋๋ค.
- Rust๋ GC ์ผ์ ์ ์ง๋ฅผ ์์ ๊ณ ์ธ๋ฐํ ์ ์ด๋ฅผ ์ ๊ณตํ์ง๋ง, ํ์ต ๊ณก์ ์ด ๊ฐํ๋ฅด๊ณ ์ปดํ์ผ ์๊ฐ์ด ๊ธธ์ด์ง๋ ๋น์ฉ์ด ์์ต๋๋ค.
- ์ค์ ์์คํ
(์ ์์๊ฑฐ๋, ๊ฒฐ์ )์ ๋ค์์ผ๋ก ์ด์ ์ ์ป์ต๋๋ค:
- ๊ฐ์ฒด ํ
- ์ฌ์ ํ ๋น
- ์ ๋ก ์นดํผ ์ค๊ณ
- ํ๋์จ์ด ์ธ์ ์ ๋ต(NUMA, ์ง์ ๋ฉ๋ชจ๋ฆฌ)
- ์๋ก์ด ํธ๋ ๋๋ ํ๋์จ์ด ์ง์ ํ ๋น ๋ฐ ์ํฌ๋ก๋ ํจํด์ ์ ์ํ๋ AI ๊ธฐ๋ฐ ํ ๋น์๋ฅผ ํฅํ๊ณ ์์ต๋๋ค.
๋ฉ๋ชจ๋ฆฌ ๊ด๋ฆฌ ์ฌ์ธต ๋ถ์
์ด ๋ถ์์ ํตํด ํ๋ ์์ํฌ๋ง๋ค ๋ฉ๋ชจ๋ฆฌ ๊ด๋ฆฌ ๋ฐฉ์์ ํฐ ์ฐจ์ด๊ฐ ์์์ ๋ฐ๊ฒฌํ์ต๋๋ค. ํนํ Hyperlane ํ๋ ์์ํฌ์ ์ ๋กโ๊ฐ๋น์ง ์ค๊ณ๋ ์ธ์์ ์ ๋๋คโ๊ฐ์ฒด ํ๊ณผ ๋ฉ๋ชจ๋ฆฌ ์ฌ์ ํ ๋น์ ํ์ฉํ์ฌ ๊ฐ๋น์ง ์ปฌ๋ ์ ๋ฌธ์ ๋ฅผ ๊ฑฐ์ ์์ ํ ํํผํฉ๋๋ค.
- Rust โ ์์ ๊ถ ์์คํ ์ ํตํด ๊ฐ๋ ฅํ ๋ฉ๋ชจ๋ฆฌ ์์ ๋ณด์ฅ์ ์ ๊ณตํฉ๋๋ค.
- Go โ ๊ฐ๋น์ง ์ปฌ๋ ํฐ๊ฐ ํธ๋ฆฌํ์ง๋ง, ์ง์ฐ ์๊ฐ์ ๋ฏผ๊ฐํ ์ ํ๋ฆฌ์ผ์ด์ ์์๋ ๊ฐ์ ์ฌ์ง๊ฐ ๋จ์ ์์ต๋๋ค.
๋ฉ๋ชจ๋ฆฌ ๊ด๋ฆฌ๋ ์น ์ ํ๋ฆฌ์ผ์ด์ ์ฑ๋ฅ ์ต์ ํ์ ํต์ฌ์ ๋๋ค. ์ฌ๋ฐ๋ฅธ ํ๋ ์์ํฌ์ ์ต์ ํ ์ ๋ต์ ์ ํํ๋ ๊ฒ์ด ์์คํ ์ฑ๋ฅ์ ๊ฒฐ์ ์ ์ธ ์ํฅ์ ๋ฏธ์นฉ๋๋ค. ์ด ๋ถ์์ด ๋ ๋์ ๊ฒฐ์ ์ ๋ด๋ฆฌ๋ ๋ฐ ๋์์ด ๋๊ธธ ๋ฐ๋๋๋ค.