Categories
Tags
algorithms APIT arm assembly asynchronous base64 Blogging box c clang-format cmake compiler concurrency const_fn contravariant cos covariant cpp Customization cybersecurity DataStructure db Demo deserialization discrete doc DP Dynamic Example FFI flat_map FP Functional functions futures Fuwari GATs gccrs generics gitignore GUI hacking hashmap haskell heap interop invariant iterator justfile kernel LaTeX LFU linux MachineLearning Markdown math ML OnceLock optimization OS parallels perf physics pin postgresql release RPIT rust science Science serialization shift sin SmallProjects std String surrealdb swisstable synchronous tan traits triangulation utf16 utf8 Video x86_64 xilem zig
777 words
4 minutes
240703_Array_vs_Vec_memory_diagrams
link
극한의 상황에서는 vector를 못 쓸지도.. OS같이 온실같은 환경에서는 고민하지 않겠지만 embedded는 없는게 더 많다.
레딧글
비교해서 공부(Array vs Vec)
Access cost 어느정도 cost를 지불해야하는지 깊게 파 보자
Cache behavior캐쉬 관점에서 비교해 보자
- I’ll explain Array vs Vec memory layout, where data lives, what’s on stack vs heap, and why performance differs, using clear CLI diagrams you can see with your eyes.
🧠 Big picture (pin this first)
Array ([T; N]) → size known at compile time
Vec<T> → size known at runtime- Memory-wise:
Array → inline storage
Vec → pointer to heap storageSummary table (pin this)|🔝|
| Property | Array [T; N] | Vec |
|---|---|---|
| Size known | Compile time | Runtime |
| Storage | Inline | Heap |
| Grows | ❌ No | ✅ Yes |
| Reallocation | ❌ No | ✅ Yes |
| Indirection | ❌ No | ✅ Yes |
| Cache-friendly | Excellent | Excellent |
| Stack usage | High if large | Small |
Final mental model (one sentence)|🔝|
Array owns the bytes directly.
Vec owns a pointer to bytes.
- 배열이 바이트를 직접 소유합니다.
- Vec는 바이트 포인터를 소유하고 있습니다.
Or visually:
Array = [ DATA ]
Vec = [ POINTER ] → [ DATA ]1️⃣ Fixed-size Array [T; N] memory diagram|🔝|
- Rust code
let a: [i32; 4] = [10, 20, 30, 40];- Memory layout (typically on stack)
Stack
┌───────────────────────────────┐
│ a[0] │ a[1] │ a[2] │ a[3] │
│ 10 │ 20 │ 30 │ 40 │
└───────────────────────────────┘- Key properties:
✔ One contiguous block
✔ No pointer indirection
✔ Size = 4 * size_of(i32)
✔ Known at compile time- The array is the data.
2️⃣ Array inside a struct (still inline)|🔝|
struct S {
a: [i32; 4],
b: i32,
}- Memory:
Stack (or heap if S is heap-allocated)
┌─────────────────────────────────────┐
│ a[0] a[1] a[2] a[3] │ b │
└─────────────────────────────────────┘- 🔥 Arrays are always inline, never indirect.
3️⃣ Vec<T> memory diagram (three-part structure)|🔝|
- Rust code
let v: Vec<i32> = vec![10, 20, 30, 40];- What
Vec<T>really is
Vec<T> = (ptr, len, capacity)- Stack vs Heap
Stack Heap
┌─────────────────────┐ ┌───────────────────────────────┐
│ ptr ────────────────┼────▶ │ v[0] │ v[1] │ v[2] │ v[3] │
│ len = 4 │ │ 10 │ 20 │ 30 │ 40 │
│ cap = 4 │ └───────────────────────────────┘
└─────────────────────┘- Key properties:
✔ Vec header is small (3 machine words)
✔ Data lives on the heap
✔ Size can grow/shrink- The vector points to the data, it is not the data.
4️⃣ Side-by-side: Array vs Vec (visual)|🔝|
Array [i32; 4]:
┌───────────────┐
│ 10 20 30 40 │ ← inline, fixed
└───────────────┘
Vec<i32>:
┌───────────────┐ ┌───────────────┐
│ ptr len cap │ ────▶ │ 10 20 30 40 │
└───────────────┘ └───────────────┘5️⃣ Where they live (important)|🔝|
- Local variable
let a = [1, 2, 3, 4];
let v = vec![1, 2, 3, 4];Stack:
- a → full array
- v → only (ptr, len, cap)Heap:
- v’s elements6️⃣ Access cost (what the CPU does)|🔝|
Array access
a[i]- CPU:
base_address + i * sizeof(T)- ✔ Single address computation
- ✔ No pointer chasing
Vec access
v[i]- CPU:
load ptr
ptr + i * sizeof(T)- ❌ One extra load (usually cached)
- ✔ Still fast
7️⃣ Cache behavior (important difference)|🔝|
Array
┌────────────────────────────────┐
│ a0 a1 a2 a3 a4 a5 a6 a7 ... │
└────────────────────────────────┘- Always contiguous
- Perfect for cache + SIMD
Vec
Stack: [ptr len cap]
Heap: ┌────────────────────────┐
│ v0 v1 v2 v3 v4 ... │
└────────────────────────┘- Still contiguous
- But one level of indirection
- Heap placement may affect locality
8️⃣ Growing a Vec (reallocation diagram)|🔝|
v.push(50);- If capacity is exceeded:
Old heap:
┌───────────────┐
│ 10 20 30 40 │
└───────────────┘
Allocate new heap:
┌───────────────────────────┐
│ 10 20 30 40 50 │
└───────────────────────────┘
Update ptr, len, cap
Free old heap- 🔥 This cannot happen with arrays.
9️⃣ Why arrays can be faster (sometimes)|🔝|
✔ No heap allocation
✔ No reallocation
✔ Better cache predictability
✔ Better for SIMD- But:
❌ Fixed size
❌ Large arrays may overflow stack🔟 Why Vec is used everywhere|🔝|
✔ Runtime-sized
✔ Can grow/shrink
✔ Easy to pass around
✔ Standard collection- Rust APIs prefer:
&[T] // slice- Because both arrays and Vec can produce slices.
1️⃣1️⃣ Slice unifies both worlds|🔝|
- 슬라이스는 두 세계를 통합합니다
fn foo(x: &[i32]) {}Array → &[i32]
Vec → &[i32]- Memory:
Slice = (ptr, len)┌───────────────┐
│ ptr len │
└───────────────┘
- No ownership, just a view.
240703_Array_vs_Vec_memory_diagrams
https://younghakim7.github.io/blog/posts/240703_array_vs_vec_memory_diagrams/