Tail Call Optimization là gì? Và tại sao bạn không cần quan tâm đến nó?
Chỉ nếu như bạn sử dụng JavaScript...
Tail call là từ được cấu thành từ hai chữ, chữ tail và chữ call.
tail /teɪl/
call /kɔːl/ (in UK) or /kɑl/ (in US)
Chữ tail Hài hước tí, nếu các bạn không thấy zui thì nên tự xem lại óc hài hước của mình. À không, ý mình là của các bạn. có nghĩa là đuôi, còn chữ call có nghĩa là gọi, hợp lại ta có chữ gọi đuôi, hay gọi đằng đuôi. Trong môn khoa học máy tính, gọi đằng đuôi có nghĩa là lời gọi tới một hàm con tại vị trí cuối cùng trong một hàm cha nào đó, đằng sau lời gọi này, hàm cha không còn làm bất cứ một việc gì khác nữa.
Kiểu như này:
function sum(a, b) { return a + b;
} // Ví dụ 1
function add(a, b) { return sum(10, 20); // đây là tail call
} // Ví dụ 2
function add(a, b) { return 10 + sum(10, 20); // đây không phải là tail call
} // Ví dụ 3
function add(a, b) { let result = sum(10, 20); // đây không phải là tail call return result;
}
Như trên ta thấy, chỉ có lời gọi hàm sum()
ở ví dụ 1 là tail call, còn hai ví dụ còn lại thì không.
Trước khi nói tiếp về tail call optimization, chúng ta hãy nói qua một chút về call stack.
Call stack là một cấu trúc dữ liệu kiểu ngăn xếp (stack) được sử dụng để lưu vị trí hoạt động của một chương trình, khi có một lời gọi hàm (call) xảy ra, thì stack sẽ được đẩy thêm vào (push) một phần tử, sau khi hàm đó xử lý xong thì nó sẽ bị gỡ ra (pop). Việc này giúp cho máy tính biết được nó đang đứng ở đâu trước khi một lời gọi hàm xảy ra, và có thể quay về sau khi hàm đó kết thúc.
Ở ví dụ 3, đầu tiên, khi chương trình thực hiện, thì call stack của chúng ta sẽ biến đổi như sau:
Đầu tiên, hàm add(a, b)
sẽ được push vào call stack, vì đây là hàm vừa được gọi:
program counter
, chạy từ đầu tới cuối của chương trình đó, đụng lệnh nào thì thực thi lệnh đó (giống khi ta đọc sách mà lấy tay chỉ vào từng dòng để đánh vần vậy).
Và để tìm hiểu về kĩ thuật tail call optimization của compiler, chúng ta chỉ cần biết 2 lệnh, đó là:
-
call
: lệnh điều khiển choprogram counter
tạm thời nhảy đến một vị trí nào đó trong chương trình để xử lý, sau khi xử lý xong thì nó sẽ quay về, và tiếp tục xử lý lệnh kế tiếp, nghe như ném boomerang nhỉ. Ví dụ đoạn code sau có lệnhcall
, sẽ được thực thi theo thứ tự từ trên xuống, và nhảy tứa lưa như mấy mũi tên trong hình:
OK, vậy tại sao đầu bài lại nói là không cần quan tâm đến tail call optimization khi dùng JavaScript?
Đơn giản, là vì JavaScript không có tail call optimization!
Mặc dù trong đặc tả ES6 có mô tả về nó, hầu như không có một JavaScript engine nào implement nó cả, trừ Safari .
Đọc thêm
- Tail Calls, Optimization, and ES6, Gustavo Duarte
https://manybutfinite.com/post/tail-calls-optimization-es6/ - Tail call optimization in ECMAScript 6, Axel Rauschmayer
http://2ality.com/2015/06/tail-call-optimization.html - Tail Call Optimization, Ward Cunningham
http://wiki.c2.com/?TailCallOptimization - Call vs Jmp: The Stack Connection, Dr. O. Lawlor
https://www.cs.uaf.edu/2012/fall/cs301/lecture/09_24_call_and_ret.html - https://www.chromestatus.com/feature/5516876633341952
- Tail Calls, Optimization, and ES6, Gustavo Duarte