Dữ liệu & CPU/Tổng kết Course 1 — Dữ liệu & CPU
23/23
Bài 23 / 23~15 phútCPU hiện đạiMiễn phí lượt xem

Tổng kết Course 1 — Dữ liệu & CPU

Recap toàn Course 1: từ bit tới pipeline. Cheat sheet xuyên 3 module, glossary, các quyết định tối ưu rút ra, self-assessment, và lộ trình sang Course 2 Bộ nhớ.

TL;DR: Course 1 "Dữ liệu & CPU" đi từ bit — đơn vị nhỏ nhất — đến pipeline, cơ chế giúp CPU hiện đại chạy nhiều lệnh song song. Ba module lần lượt trả lời: dữ liệu lưu như thế nào (Module 1), CPU đọc và chạy lệnh như thế nào (Module 2), rồi CPU hiện đại tối ưu tốc độ bằng cách nào (Module 3). Đây là trang để bookmark.

Hành trình Course 1

Mỗi khi bạn chạy ./program, ba lớp cơ chế diễn ra đồng thời — và cả ba đều nằm trong Course 1 này.

Module 1 — Biểu diễn dữ liệu trả lời câu hỏi đầu tiên: dữ liệu trông như thế nào trong máy? Máy tính không biết "số 42" hay "chữ A" — nó chỉ biết bit. Module 1 giải thích quy ước mã hoá: hai's complement biến bit thành số nguyên có dấu; IEEE 754 biến bit thành số thực với dấu phẩy động; UTF-8 biến bit thành văn bản Unicode. Endianness quyết định thứ tự byte khi lưu. Thao tác bit-level (mask, shift) cho phép thao túng trực tiếp từng cờ trạng thái.

Module 2 — Máy chạy thế nào trả lời: CPU lấy đống bit đó và làm gì với nó? Kiến trúc von Neumann chia máy tính thành bộ nhớ + CPU + bus; vòng lặp fetch-decode-execute là nhịp tim của mọi chương trình. Thanh ghi và ALU là nơi tính toán thực sự diễn ra. Assembly nhập môn cho thấy code bậc cao (Java, Python, C) biến thành lệnh máy qua compile pipeline như thế nào — mỗi dòng int x = a + b; tương ứng một chuỗi lệnh mov/add/store cụ thể.

Module 3 — CPU hiện đại trả lời: làm sao CPU chạy nhanh hơn mà không tăng clock mãi được? Clock tăng có giới hạn vật lý (power wall, heat). Câu trả lời là pipeline — chia lệnh thành nhiều giai đoạn chạy chồng lên nhau. Branch prediction đoán nhánh điều kiện trước khi biết kết quả. Out-of-order execution sắp xếp lại lệnh để tận dụng tài nguyên. SIMD chạy một lệnh trên nhiều phần tử dữ liệu cùng lúc. Và Spectre/Meltdown cho thấy mọi tối ưu hoá đều có tradeoff bảo mật.

Toàn bộ hành trình từ bit đến pipeline giải thích một điều thực tế: cách bạn viết code ảnh hưởng trực tiếp đến tốc độ chạy — không phải vì "compiler thích" mà vì phần cứng có cấu trúc xác định.

Toan canh Course 1: tu bit toi pipeline.

flowchart LR
  M1["Module 1<br/>Bieu dien du lieu<br/>bit -> byte -> UTF-8"] --> M2["Module 2<br/>May chay the nao<br/>von Neumann -> assembly"]
  M2 --> M3["Module 3<br/>CPU hien dai<br/>pipeline -> branch -> SIMD"]

Cheat sheet xuyên course

TầngKhái niệm chốtÁp dụng tối ưu
Bit & byte8 bit = 1 byte = 256 giá trị; 2^n giá trị cho n bitChọn kiểu dữ liệu theo miền giá trị; byte/short khi cần tiết kiệm bộ nhớ array lớn
Số nguyên (two's complement)Bit cao nhất là bit dấu; tràn số wrap-around lặng lẽDùng Math.addExact() khi cần phát hiện tràn; đổi sang long trước khi nhân số lớn
Số thực (IEEE 754)Phần thập phân nhị phân không chính xác; 0.1 + 0.2 không bằng 0.3Không dùng float/double cho tiền; dùng BigDecimal hoặc lưu cent (số nguyên)
Văn bản (UTF-8)Unicode code point 1–4 byte; String.length() đếm char, không phải byteLuôn chỉ rõ charset UTF-8 khi đọc/ghi file; so sánh chuỗi bằng .equals() không phải ==
Bitmask & flagvalue & mask đọc bit; value | flag bật bit; value & ~flag tắt bitGom nhiều boolean flag vào 1 int; tiết kiệm bộ nhớ và cache-friendly cho cấu trúc lớn
Endiannessx86/ARM little-endian; network byte order big-endianDùng ByteBuffer.order() hoặc DataInputStream khi đọc binary protocol; kiểm tra endianness khi debug dữ liệu nhị phân
Von Neumann & busCPU và RAM cùng dùng chung bus; truy cập RAM chậm hơn thanh ghi hàng trăm lầnTái dùng biến trong vòng lặp thay vì truy cập field object lặp lại; để compiler tối ưu register allocation
Fetch-decode-executeMỗi lệnh cần ít nhất 3 bước: nạp, giải mã, thực thiGiảm số lệnh bằng cách chọn thuật toán đúng — không tối ưu micro trước khi chọn đúng big-O
Assembly & compile-O2/-O3 cho phép compiler xoá dead code, hoist invariant, vectorizeBật -O2 hoặc tương đương; viết code rõ ý định để compiler tối ưu được
Clock & IPCTần số (GHz) nhân IPC = throughput thực; cùng GHz mà IPC cao hơn thì nhanh hơnĐo IPC với perf stat trên Linux; IPC thấp thường do cache miss hoặc branch misprediction
Pipeline & hazardPipeline chia lệnh thành nhiều giai đoạn chồng nhau; hazard gây stallTránh chuỗi phụ thuộc dài (data hazard); đặt lệnh không phụ thuộc nhau xen kẽ
Branch predictionCPU đoán nhánh if trước khi biết điều kiện; đoán sai phải flush pipelineSắp xếp dữ liệu để nhánh phổ biến nhất được dự đoán đúng; tránh if phụ thuộc dữ liệu ngẫu nhiên trong vòng lặp nóng
Đo lường & AmdahlAmdahl: speedup toàn hệ bị giới hạn bởi phần không song song hoá đượcĐo trước khi tối ưu; profiler chỉ chỗ thực sự chậm, không đoán
Out-of-order & SIMDCPU tự sắp xếp lại lệnh để tránh stall; SIMD chạy 1 lệnh trên nhiều phần tửViết vòng lặp đơn giản, không có alias pointer; compiler vectorize được khi không có dependency phức tạp

Glossary

Thuật ngữĐịnh nghĩa 1 câu
bitĐơn vị thông tin nhỏ nhất, nhận giá trị 0 hoặc 1.
byte8 bit gộp lại, biểu diễn được 256 giá trị khác nhau.
two's complementQuy ước mã hoá số nguyên có dấu: đảo toàn bộ bit rồi cộng 1 để ra số âm, cho phép cộng/trừ dùng chung một mạch.
overflowKết quả phép toán vượt dải giá trị của kiểu, dẫn đến wrap-around lặng lẽ không báo lỗi.
IEEE 754Chuẩn biểu diễn số thực dấu phẩy động: 1 bit dấu + trường số mũ + trường định trị (mantissa).
mantissaPhần định trị trong IEEE 754, quyết định độ chính xác của số thực.
UnicodeBảng mã gán code point duy nhất cho mọi ký tự trên thế giới.
UTF-8Encoding biến độ dài mã hoá code point Unicode thành 1–4 byte, tương thích ngược với ASCII.
endiannessThứ tự byte khi lưu số nguyên nhiều byte: big-endian lưu byte cao trước, little-endian lưu byte thấp trước.
bitmaskGiá trị nhị phân dùng để chọn ra hoặc bật/tắt một tập bit cụ thể trong số nguyên bằng AND/OR/XOR.
von NeumannKiến trúc máy tính gồm CPU + bộ nhớ chia sẻ + bus, nơi cả lệnh lẫn dữ liệu cùng lưu trong một bộ nhớ duy nhất.
fetch-decode-executeVòng lặp cơ bản của CPU: nạp lệnh từ bộ nhớ, giải mã xem lệnh làm gì, thực thi và ghi kết quả.
registerÔ nhớ cực nhỏ nằm trong CPU, truy cập nhanh nhất (1 cycle), số lượng hạn chế (vài chục).
ALUArithmetic Logic Unit — mạch thực hiện phép toán số học và logic bên trong CPU.
IPCInstructions Per Cycle — số lệnh trung bình CPU hoàn thành mỗi chu kỳ clock; cùng GHz mà IPC cao hơn thì nhanh hơn.
pipelineKỹ thuật chia lệnh thành nhiều giai đoạn (fetch, decode, execute, writeback...) chạy chồng lên nhau như dây chuyền sản xuất.
hazardXung đột trong pipeline khi lệnh sau phụ thuộc vào kết quả của lệnh trước chưa hoàn tất, gây stall.
branch predictionCơ chế CPU đoán nhánh điều kiện trước khi biết kết quả; đoán sai phải xả pipeline và làm lại.
Amdahl's LawĐịnh luật giới hạn speedup tối đa khi song song hoá: S = 1 / (1 - p + p/n), với p là phần có thể song song.
SIMDSingle Instruction Multiple Data — một lệnh tác động lên nhiều phần tử dữ liệu cùng lúc (AVX, SSE, NEON).

Những quyết định tối ưu bạn đã học

Mỗi module trong Course 1 đóng bằng một quyết định kỹ thuật cụ thể. Tổng hợp lại:

Chọn kiểu dữ liệu theo miền giá trị:

  • Số nguyên không âm dưới 255 → byte (không phải int) để giảm bộ nhớ khi có array lớn.
  • Số đếm vượt 2 tỷ → long, không phải int.
  • Không dùng float/double cho tiền tệ — dùng BigDecimal (Java) hoặc lưu đơn vị nhỏ nhất (cent, xu) dưới dạng số nguyên.

Encoding và văn bản:

  • Luôn set encoding UTF-8 tường minh khi đọc/ghi file (StandardCharsets.UTF_8), không dựa vào platform default.
  • Đọc độ dài byte của String bằng .getBytes(UTF_8).length, không phải .length() — quan trọng khi tính giới hạn HTTP header, database column size.

Bitmask thay nhiều boolean:

  • Gom n cờ boolean vào 1 int/long bằng bitmask: ít bộ nhớ hơn, cache-friendly hơn khi duyệt array lớn.
  • Ví dụ: quyền truy cập (READ=1, WRITE=2, EXEC=4) → permissions & READ để kiểm tra.

Bật compiler optimization:

  • Dùng -O2 (GCC/Clang) hoặc javac + JIT warming: dead code bị xoá, invariant bị hoist ra khỏi vòng lặp, loop được vectorize.
  • Viết code rõ ý định (không alias pointer, không side-effect ẩn) để compiler tối ưu được.

Đo trước khi tối ưu:

  • Không đoán chỗ chậm — profiler (perf, JProfiler, async-profiler) chỉ đúng bottleneck.
  • Benchmark đúng cách: JVM warm-up ít nhất vài nghìn iteration trước khi đo; dùng JMH cho Java.
  • Amdahl's Law: nếu 80% code đã nhanh rồi, tối ưu 20% còn lại chỉ cải thiện tối đa 25% tổng thể.

Viết vòng lặp branch-friendly:

  • Sắp xếp dữ liệu đầu vào để nhánh phổ biến nhất xuất hiện liên tiếp → branch predictor đạt tỉ lệ đúng cao hơn.
  • Tránh if phụ thuộc giá trị ngẫu nhiên trong vòng lặp nóng — xem xét branchless alternative (min/max intrinsic, ternary đơn giản).

Viết vòng lặp vectorize-friendly:

  • Vòng lặp đơn giản, không có dependency giữa iteration, không có pointer alias → compiler tự SIMD hoá.
  • Ưu tiên array of structs → struct of arrays khi cần vectorize nhiều field cùng lúc.

Self-assessment

Bạn đã đạt Course 1 nếu trả lời được:

  • Giải thích được số nguyên, số thực (IEEE 754) và văn bản (UTF-8) được biểu diễn nhị phân thế nào; dự đoán được khi nào tràn số và khi nào float bị sai — nếu chưa chắc, đọc lại Module 1 bài 02 (two's complement) và bài 03 (IEEE 754).
  • Trace được chu kỳ fetch-decode-execute và vai trò của register, ALU, bus trong kiến trúc von Neumann — nếu chưa chắc, đọc lại Module 2 bài 01 (von Neumann) và bài 02 (fetch-decode-execute).
  • Đọc được assembly cơ bản và ánh xạ được một đoạn code bậc cao xuống lệnh máy — nếu chưa chắc, đọc lại Module 2 bài 04 (assembly nhập môn) và bài 05 (từ code bậc cao tới lệnh máy).
  • Giải thích được pipeline hoạt động thế nào và vì sao branch prediction ảnh hưởng đến tốc độ — nếu chưa chắc, đọc lại Module 3 bài 02 (pipeline & hazard) và phần branch prediction.
  • Áp dụng được: viết vòng lặp branch-friendly, đo cải thiện bằng benchmark, và giải thích kết quả theo Amdahl's Law — nếu chưa chắc, đọc lại Module 3 bài đo lường và bài capstone.

Ôn lại từ đầu: Module 1 — Biểu diễn dữ liệu · Module 2 — Máy chạy thế nào · Module 3 — CPU hiện đại

Tiếp theo: Course 2 — Bộ nhớ

Bạn vừa hiểu CPU tính toán thế nào. Nhưng có một nút thắt lớn mà Module 2 đã nhắc qua: von Neumann bottleneck — CPU và RAM chia sẻ cùng một bus, và RAM chậm hơn CPU hàng trăm lần. Đây là lý do tại sao nhiều chương trình bị giới hạn không phải bởi tốc độ tính toán, mà bởi tốc độ di chuyển dữ liệu. Course 2 "Bộ nhớ" sẽ đào sâu vào stack và heap (bộ nhớ được cấp phát thế nào), hệ thống cache nhiều tầng (L1/L2/L3 là giải pháp phần cứng cho von Neumann bottleneck), và virtual memory (ảo hoá địa chỉ cho phép chạy nhiều process an toàn). Hiểu bộ nhớ là bước tiếp theo bắt buộc để giải thích vì sao cùng một thuật toán O(n) mà cache-friendly lại nhanh hơn cache-unfriendly vài chục lần. Course 2 sắp ra mắt.

Tài liệu mở rộng

📚 Sách nền tảng cho Course 1
  • Code: The Hidden Language of Computer Hardware and Software — Charles Petzold. Xây từng tầng từ công tắc điện đến máy tính hoàn chỉnh; giải thích bit/byte/logic gate/CPU theo cách trực quan nhất, không cần toán nền.
  • Computer Systems: A Programmer's Perspective (CS:APP) — Bryant & O'Hallaron. Cuốn giáo trình chuẩn cho CS systems: data representation, assembly x86-64, processor architecture, memory hierarchy, linking, concurrency. Bài lab bomb/buffer overflow nổi tiếng. Đọc chương 1-5 để củng cố toàn bộ Course 1.
  • Computer Organization and Design — Patterson & Hennessy. Giải thích kiến trúc máy tính từ góc độ phần cứng + phần mềm; bao gồm pipeline, hazard, branch prediction theo chuẩn RISC-V. Phù hợp khi muốn đi sâu hơn phần CPU hiện đại.
  • Compiler Explorer (godbolt.org) — Công cụ online: paste code C/C++/Rust/Go/Java → xem assembly được sinh ra ngay lập tức. Thay đổi -O0/-O2/-O3 để thấy compiler tối ưu hoá code của bạn thế nào. Không thể thiếu khi học Module 2 và 3.

Bài này có giúp bạn hiểu bản chất không?

Hỏi đáp về bài này

Chưa có câu hỏi

Đặt câu hỏi

Có gì chưa rõ trong bài? Đặt câu hỏi đầu tiên — câu trả lời từ cộng đồng giúp bạn (và người sau).

Đặt câu hỏi đầu tiên