Như những người khác nói, bạn nên đo hiệu suất của chương trình trước và có thể sẽ không tìm thấy sự khác biệt nào trong thực tế.
Tuy nhiên, từ cấp độ khái niệm, tôi nghĩ rằng tôi sẽ làm sáng tỏ một vài điều bị lẫn lộn trong câu hỏi của bạn. Đầu tiên, bạn hỏi:
Do chi phí cuộc gọi chức năng vẫn còn quan trọng trong trình biên dịch hiện đại?
Lưu ý các từ khóa "chức năng" và "trình biên dịch". Trích dẫn của bạn là khác nhau:
Hãy nhớ rằng chi phí của một cuộc gọi phương thức có thể là đáng kể, tùy thuộc vào ngôn ngữ.
Đây là nói về các phương thức , theo nghĩa hướng đối tượng.
Trong khi "hàm" và "phương thức" thường được sử dụng thay thế cho nhau, có sự khác biệt khi nói về chi phí của chúng (mà bạn đang hỏi về) và khi nói đến việc biên dịch (đó là bối cảnh bạn đưa ra).
Cụ thể, chúng ta cần biết về công văn tĩnh so với công văn động . Tôi sẽ bỏ qua sự tối ưu cho thời điểm này.
Trong một ngôn ngữ như C, chúng ta thường gọi các hàm với công văn tĩnh . Ví dụ:
int foo(int x) {
return x + 1;
}
int bar(int y) {
return foo(y);
}
int main() {
return bar(42);
}
Khi trình biên dịch nhìn thấy cuộc gọi foo(y)
, nó biết foo
tên mà hàm đó đang đề cập đến, vì vậy chương trình đầu ra có thể chuyển thẳng đến foo
hàm, khá rẻ. Đó là những gì công văn tĩnh có nghĩa.
Thay thế là công văn động , trong đó trình biên dịch không biết hàm nào đang được gọi. Ví dụ: đây là một số mã Haskell (vì tương đương C sẽ lộn xộn!):
foo x = x + 1
bar f x = f x
main = print (bar foo 42)
Ở đây bar
hàm đang gọi đối số của nó f
, có thể là bất cứ thứ gì. Do đó, trình biên dịch không thể biên dịch bar
thành một lệnh nhảy nhanh, bởi vì nó không biết phải nhảy tới đâu. Thay vào đó, mã mà chúng ta tạo ra bar
sẽ bổ sung f
để tìm ra chức năng mà nó trỏ đến, sau đó chuyển sang nó. Đó là những gì công văn năng động có nghĩa.
Cả hai ví dụ này đều dành cho các chức năng . Bạn đã đề cập đến các phương thức , có thể được coi là một kiểu đặc biệt của hàm được gửi động. Ví dụ: đây là một số Python:
class A:
def __init__(self, x):
self.x = x
def foo(self):
return self.x + 1
def bar(y):
return y.foo()
z = A(42)
bar(z)
Cuộc y.foo()
gọi sử dụng công văn động, vì nó tìm kiếm giá trị của foo
tài sản trong y
đối tượng và gọi bất cứ thứ gì nó tìm thấy; nó không biết rằng y
sẽ có lớp A
hoặc A
lớp có một foo
phương thức, vì vậy chúng ta không thể nhảy thẳng vào nó.
OK, đó là ý tưởng cơ bản. Lưu ý rằng công văn tĩnh nhanh hơn công văn động bất kể chúng tôi biên dịch hay giải thích; tất cả những thứ khác đều bình đẳng Các cuộc hội thảo phát sinh thêm một chi phí.
Vậy làm thế nào điều này ảnh hưởng đến trình biên dịch hiện đại, tối ưu hóa?
Điều đầu tiên cần lưu ý là công văn tĩnh có thể được tối ưu hóa mạnh mẽ hơn: khi chúng ta biết chức năng nào chúng ta đang nhảy tới, có thể thực hiện những việc như nội tuyến. Với công văn động, chúng tôi không biết chúng tôi sẽ nhảy cho đến khi chạy, vì vậy chúng tôi không thể tối ưu hóa nhiều.
Thứ hai, trong một số ngôn ngữ, có thể suy ra nơi một số công văn động sẽ kết thúc, và do đó tối ưu hóa chúng thành công văn tĩnh. Điều này cho phép chúng tôi thực hiện các tối ưu hóa khác như nội tuyến, v.v.
Trong ví dụ Python ở trên, suy luận như vậy là khá vô vọng, vì Python cho phép các mã khác ghi đè lên các lớp và thuộc tính, do đó, rất khó để suy ra nhiều thứ sẽ giữ trong mọi trường hợp.
Nếu ngôn ngữ của chúng tôi cho phép chúng tôi áp đặt nhiều hạn chế hơn, ví dụ như bằng cách giới hạn y
lớp A
bằng cách sử dụng chú thích, thì chúng tôi có thể sử dụng thông tin đó để suy ra chức năng đích. Trong các ngôn ngữ có phân lớp (gần như tất cả các ngôn ngữ có các lớp!) Thực sự không đủ, vì y
thực sự có thể có một lớp (phụ) khác, vì vậy chúng tôi cần thêm thông tin như final
chú thích của Java để biết chính xác hàm nào sẽ được gọi.
Haskell không phải là ngôn ngữ OO, nhưng chúng ta có thể suy ra giá trị của f
bằng cách đặt nội tuyến bar
( được gửi tĩnh ) vào main
, thay thế foo
cho y
. Vì mục tiêu của foo
in main
được biết đến tĩnh, nên cuộc gọi sẽ được gửi đi tĩnh và có thể sẽ được nội tuyến và tối ưu hóa hoàn toàn (vì các hàm này nhỏ, trình biên dịch có nhiều khả năng nội tuyến chúng hơn, mặc dù chúng ta không thể tin vào điều đó nói chung ).
Do đó, chi phí giảm xuống:
- Liệu ngôn ngữ gửi cuộc gọi của bạn tĩnh hay động?
- Nếu đó là ngôn ngữ thứ hai, liệu ngôn ngữ có cho phép thực hiện suy ra mục tiêu bằng cách sử dụng thông tin khác (ví dụ: các loại, lớp, chú thích, nội tuyến, v.v.) không?
- Làm thế nào tích cực có thể gửi công văn tĩnh (suy ra hoặc cách khác)?
Nếu bạn đang sử dụng ngôn ngữ "rất năng động", với rất nhiều công văn động và một vài đảm bảo có sẵn cho trình biên dịch, thì mọi cuộc gọi sẽ phải chịu một chi phí. Nếu bạn đang sử dụng ngôn ngữ "rất tĩnh", thì trình biên dịch trưởng thành sẽ tạo ra mã rất nhanh. Nếu bạn ở giữa, thì nó có thể phụ thuộc vào phong cách mã hóa của bạn và cách triển khai thông minh.