Đưa `if` lên trên, đẩy `for` xuống dưới
(matklad.github.io)- Việc đưa câu lệnh if bên trong hàm lên chỗ gọi hàm giúp giảm độ phức tạp của mã
- Khi tập trung kiểm tra điều kiện và xử lý phân nhánh vào một chỗ, có thể dễ dàng phát hiện các kiểm tra lặp lại và các nhánh không cần thiết
- Có thể dùng refactor phân rã enum để tránh việc cùng một điều kiện bị rải rác ở nhiều nơi trong mã
- Các vòng lặp
fordựa trên phép toán theo lô rất hiệu quả cho việc tăng hiệu năng và tối ưu các tác vụ lặp - Có thể kết hợp mẫu đưa if lên trên, đẩy for xuống dưới để đồng thời tăng tính dễ đọc và hiệu quả của mã
Ghi chú ngắn về hai quy tắc liên quan
- Khi có câu lệnh if bên trong hàm, nên cân nhắc xem có thể chuyển nó lên chỗ gọi hàm hay không
- Như trong ví dụ, thay vì kiểm tra precondition (điều kiện tiên quyết) bên trong hàm, nên giao phần kiểm tra đó cho nơi gọi hoặc đảm bảo điều kiện tiên quyết bằng kiểu dữ liệu (hoặc
assert) - Cách đưa kiểm tra điều kiện tiên quyết lên trên (Push up) ảnh hưởng đến toàn bộ mã nguồn và nhìn chung giúp giảm số lần kiểm tra điều kiện không cần thiết
Tập trung luồng điều khiển và câu lệnh điều kiện
- Luồng điều khiển và câu lệnh if là nguyên nhân chính gây ra độ phức tạp và lỗi trong mã
- Việc gom các điều kiện lên cấp trên như chỗ gọi hàm để dồn xử lý phân nhánh vào một hàm, còn phần công việc thực tế giao cho các thủ tục con tuyến tính (straight-line), là một mẫu hữu ích
- Khi phân nhánh và luồng điều khiển được gom về một nơi, sẽ dễ nhận ra các nhánh trùng lặp và điều kiện không cần thiết
Ví dụ:
- Khi trong hàm
fcóiflồng nhau, sẽ dễ nhận ra các nhánh chết (Dead Branch) - Nếu phân nhánh bị phân tán qua nhiều hàm (
g,h) thì sẽ khó nhận ra điều đó hơn
Refactor phân rã enum (Dissolving enum Refactor)
- Nếu mã đang gói cùng một nhánh điều kiện trong
enumhoặc cấu trúc tương tự, có thể kéo điều kiện đó lên tầng trên để tách bạch rõ hơn giữa phân nhánh và phần công việc - Cách này giúp tránh việc cùng một điều kiện bị lặp lại nhiều lần trong mã
Ví dụ:
- Khi cùng một điều kiện phân nhánh được biểu diễn trong các hàm
f,gvà trongenum E - Có thể đơn giản hóa toàn bộ mã bằng một nhánh điều kiện ở tầng trên
Tư duy hướng dữ liệu (Data Oriented Thinking) và phép toán theo lô
- Phần lớn chương trình hoạt động trên nhiều đối tượng (entity). Hiệu năng trên đường đi quan trọng (Hot Path) được quyết định bởi việc xử lý số lượng lớn đối tượng
- Nên đưa khái niệm batch vào để xem các phép toán trên tập đối tượng là mặc định, còn phép toán trên một đối tượng đơn lẻ chỉ là trường hợp đặc biệt
Ví dụ:
-
Lấy hàm xử lý theo lô như
frobnicate_batch(walruses)làm mặc định, -
rồi có thể biến việc xử lý từng đối tượng riêng lẻ thành một trường hợp đặc biệt thông qua vòng lặp
for -
Cách làm này rất quan trọng về mặt tối ưu hiệu năng; với khối lượng công việc lớn, nó giúp giảm chi phí khởi tạo và tăng tính linh hoạt về thứ tự xử lý
-
Cũng có thể tận dụng SIMD (
struct-of-arrayv.v.), chẳng hạn xử lý hàng loạt một trường cụ thể trước rồi mới tiếp tục toàn bộ công việc
Các trường hợp thực tiễn và mẫu được khuyến nghị
- Giống như phép nhân đa thức dựa trên FFT, có thể tối đa hóa hiệu năng bằng cách cho phép tính toán đồng thời tại nhiều điểm
- Quy tắc đưa câu lệnh điều kiện lên trên và đẩy vòng lặp xuống dưới có thể áp dụng song song
Ví dụ:
- Thay vì liên tục kiểm tra cùng một điều kiện bên trong vòng lặp, có thể đưa điều kiện ra ngoài vòng lặp để giảm phân nhánh trong thân lặp và giúp tối ưu hóa cũng như vector hóa dễ hơn
- Cách tiếp cận này đảm bảo hiệu quả cao trong data plane của các hệ thống lớn, chẳng hạn thiết kế TigerBeetle
Kết luận
- Bằng cách kết hợp mẫu đưa
if(câu lệnh điều kiện) lên tầng trên (chỗ gọi, phần điều khiển) và đẩyfor(vòng lặp) xuống tầng dưới (phần tính toán, xử lý dữ liệu), có thể cải thiện đồng thời tính dễ đọc, hiệu quả và hiệu năng của mã - Tư duy theo không gian vector trừu tượng (phép toán trên tập hợp) là công cụ giải quyết vấn đề tốt hơn so với xử lý phân nhánh lặp đi lặp lại
- Tóm lại: đưa
iflên trên, đẩyforxuống dưới!
1 bình luận
Ý kiến trên Hacker News
if (weShouldDoThis()) { doThis(); }, và nếu tách từng phần kiểm tra thành hàm riêng thì việc test và quản lý độ phức tạp sẽ dễ hơnif (length % 2 == 1) { ... } else { ... }, tức chỉ dùng điều kiện ở ngoài vòng lặp, thì hiển nhiên đáp án đúng là đưa điều kiện đó lên trên vòng for. Ở phiên bản SIMD thì if biến mất hẳn, và đây là kiểu mẫu code lý tưởng mà tác giả bài viết có lẽ cũng sẽ thích