Chuyển đổi so với hiệu suất if-else C++

Cấu trúc tổng thể của mã của bạn là một trong những yếu tố chính quyết định tốc độ thực thi của mã đó. Có một lượng mã rất nhỏ không nhất thiết có nghĩa là nó sẽ chạy nhanh và có một lượng mã lớn không nhất thiết có nghĩa là nó sẽ chạy chậm. Rất nhiều tác động đến hiệu suất có liên quan trực tiếp đến cách tổ chức mã và cách bạn đang cố gắng giải quyết một vấn đề nhất định

Các kỹ thuật trong chương này không nhất thiết chỉ dành riêng cho JavaScript và thường được dạy để tối ưu hóa hiệu suất cho các ngôn ngữ khác. Tuy nhiên, có một số sai lệch so với lời khuyên dành cho các ngôn ngữ khác, vì có nhiều công cụ JavaScript hơn để xử lý và những điều kỳ quặc của chúng cần được xem xét, nhưng tất cả các kỹ thuật đều dựa trên kiến ​​thức khoa học máy tính phổ biến

Trong hầu hết các ngôn ngữ lập trình, phần lớn thời gian thực thi mã được sử dụng trong các vòng lặp. Vòng lặp qua một loạt các giá trị là một trong những mẫu được sử dụng thường xuyên nhất trong lập trình và do đó cũng là một trong những lĩnh vực cần tập trung nỗ lực cải thiện hiệu suất. Hiểu tác động hiệu suất của các vòng lặp trong JavaScript đặc biệt quan trọng, vì các vòng lặp vô hạn hoặc dài hạn ảnh hưởng nghiêm trọng đến trải nghiệm người dùng tổng thể

ECMA-262, Phiên bản thứ 3, đặc tả xác định hành vi và cú pháp cơ bản của JavaScript, xác định bốn loại vòng lặp. Đầu tiên là vòng lặp

for (var prop in object){
    //loop body
}
0 tiêu chuẩn, chia sẻ cú pháp của nó với các ngôn ngữ giống C khác

for (var i=0; i < 10; i++){
    //loop body
}

Vòng lặp

for (var prop in object){
    //loop body
}
0 có xu hướng là cấu trúc vòng lặp JavaScript được sử dụng phổ biến nhất. Có bốn phần của vòng lặp
for (var prop in object){
    //loop body
}
0. khởi tạo, điều kiện kiểm tra trước, thực thi sau và phần thân vòng lặp. Khi gặp vòng lặp
for (var prop in object){
    //loop body
}
0, mã khởi tạo được thực thi trước, tiếp theo là điều kiện thử nghiệm. Nếu điều kiện thử nghiệm ước tính là
for (var prop in object){
    //loop body
}
4, thì phần thân của vòng lặp được thực thi. Sau khi phần thân được thực thi, mã sau khi thực thi được chạy. Sự đóng gói được nhận thức của vòng lặp
for (var prop in object){
    //loop body
}
0 khiến nó trở thành một yêu thích của các nhà phát triển

Ghi chú

Lưu ý rằng việc đặt câu lệnh

for (var prop in object){
    //loop body
}
6 trong phần khởi tạo của vòng lặp
for (var prop in object){
    //loop body
}
0 sẽ tạo biến cấp hàm, không phải biến cấp vòng lặp. JavaScript chỉ có phạm vi cấp chức năng và do đó, việc xác định một biến mới bên trong vòng lặp
for (var prop in object){
    //loop body
}
0 cũng giống như xác định một biến mới bên ngoài vòng lặp

Loại vòng lặp thứ hai là vòng lặp

for (var prop in object){
    //loop body
}
9. Vòng lặp
for (var prop in object){
    //loop body
}
9 là vòng lặp thử nghiệm đơn giản bao gồm điều kiện thử nghiệm trước và thân vòng lặp

for (var i=0; i < 10; i++){
    //loop body
}
2

Trước khi thân vòng lặp được thực thi, điều kiện thử nghiệm trước được đánh giá. Nếu điều kiện đánh giá là

for (var prop in object){
    //loop body
}
4, thì thân vòng lặp được thực thi; . Bất kỳ vòng lặp
for (var prop in object){
    //loop body
}
0 nào cũng có thể được viết thành vòng lặp
for (var prop in object){
    //loop body
}
9 và ngược lại

Loại vòng lặp thứ ba là vòng lặp

for (var i=0; i < 10; i++){
    //loop body
}
64. Vòng lặp
for (var i=0; i < 10; i++){
    //loop body
}
64 là vòng lặp hậu kiểm duy nhất có sẵn trong JavaScript và được tạo thành từ hai phần, phần thân vòng lặp và điều kiện hậu kiểm

for (var i=0; i < 10; i++){
    //loop body
}
8

Trong vòng lặp

for (var i=0; i < 10; i++){
    //loop body
}
64, thân vòng lặp luôn được thực hiện ít nhất một lần và điều kiện sau kiểm tra xác định xem vòng lặp có nên được thực hiện lại hay không

Vòng lặp thứ tư và vòng lặp cuối cùng là vòng lặp

for (var i=0; i < 10; i++){
    //loop body
}
67. Vòng lặp này có một mục đích rất đặc biệt. nó liệt kê các thuộc tính được đặt tên của bất kỳ đối tượng nào. Định dạng cơ bản như sau

for (var prop in object){
    //loop body
}

Mỗi khi vòng lặp được thực thi, biến

for (var i=0; i < 10; i++){
    //loop body
}
68 được điền tên của một thuộc tính khác (một chuỗi) tồn tại trên
for (var i=0; i < 10; i++){
    //loop body
}
69 cho đến khi tất cả các thuộc tính được trả về. Các thuộc tính được trả về đều là những thuộc tính tồn tại trên đối tượng và những thuộc tính được kế thừa thông qua chuỗi nguyên mẫu của nó

Một nguồn tranh luận liên tục về hiệu suất của vòng lặp là sử dụng vòng lặp nào. Trong số bốn loại vòng lặp do JavaScript cung cấp, chỉ một trong số chúng chậm hơn đáng kể so với các loại khác. vòng lặp

for (var i=0; i < 10; i++){
    //loop body
}
67

Vì mỗi lần lặp qua vòng lặp dẫn đến tra cứu thuộc tính trên phiên bản hoặc trên nguyên mẫu, nên vòng lặp

for (var i=0; i < 10; i++){
    //loop body
}
67 có nhiều chi phí hơn đáng kể cho mỗi lần lặp và do đó chậm hơn các vòng lặp khác. Với cùng số lần lặp lại, một vòng lặp
for (var i=0; i < 10; i++){
    //loop body
}
67 có thể kết thúc chậm hơn bảy lần so với các loại vòng lặp khác. Vì lý do này, bạn nên tránh vòng lặp
for (var i=0; i < 10; i++){
    //loop body
}
67 trừ khi ý định của bạn là lặp qua một số thuộc tính đối tượng không xác định. Nếu bạn có một danh sách các thuộc tính hữu hạn, đã biết để lặp lại, thì việc sử dụng một trong các loại vòng lặp khác và sử dụng một mẫu như thế này sẽ nhanh hơn

for (var i=0; i < 10; i++){
    //loop body
}
6

Mã này tạo một mảng có các thành viên là tên thuộc tính. Vòng lặp

for (var prop in object){
    //loop body
}
9 được sử dụng để lặp lại số lượng nhỏ thuộc tính này và xử lý thành viên thích hợp trên
for (var i=0; i < 10; i++){
    //loop body
}
69. Thay vì tìm kiếm từng và mọi thuộc tính trên
for (var i=0; i < 10; i++){
    //loop body
}
69, mã chỉ tập trung vào các thuộc tính quan tâm, tiết kiệm thời gian và chi phí vòng lặp

Ghi chú

Bạn không bao giờ nên sử dụng

for (var i=0; i < 10; i++){
    //loop body
}
67 để lặp lại các phần tử của một mảng

Ngoài vòng lặp

for (var i=0; i < 10; i++){
    //loop body
}
67, tất cả các loại vòng lặp khác đều có các đặc tính hiệu suất tương đương, do đó không hữu ích khi cố gắng xác định vòng lặp nào nhanh nhất. Việc lựa chọn loại vòng lặp phải dựa trên yêu cầu của bạn hơn là mối quan tâm về hiệu suất

Nếu loại vòng lặp không đóng góp vào hiệu suất của vòng lặp, thì điều gì sẽ xảy ra?

  • Công việc được thực hiện trên mỗi lần lặp

  • Số lần lặp lại

Bằng cách giảm một trong hai hoặc cả hai điều này, bạn có thể tác động tích cực đến hiệu suất tổng thể của vòng lặp

Giảm công việc trên mỗi lần lặp

Lý do là nếu một lần đi qua một vòng lặp mất nhiều thời gian để thực hiện, thì nhiều lần đi qua vòng lặp sẽ còn lâu hơn nữa. Hạn chế số lượng thao tác đắt tiền được thực hiện trong thân vòng lặp là một cách tốt để tăng tốc toàn bộ vòng lặp

Một vòng lặp xử lý mảng điển hình có thể được tạo bằng bất kỳ loại nào trong ba loại vòng lặp nhanh hơn. Mã được viết thường xuyên nhất như sau

for (var prop in object){
    //loop body
}
4

Trong mỗi vòng lặp này, có một số thao tác xảy ra mỗi khi thân vòng lặp được thực thi

  1. Tra cứu một thuộc tính (

    for (var prop in object){
        //loop body
    }
    49) trong điều kiện kiểm soát

  2. Một so sánh (______360) trong điều kiện kiểm soát

  3. Một so sánh để xem liệu điều kiện kiểm soát có đánh giá thành

    for (var prop in object){
        //loop body
    }
    4 (
    for (var prop in object){
        //loop body
    }
    62)

  4. Một hoạt động gia tăng (

    for (var prop in object){
        //loop body
    }
    63)

  5. Tra cứu một mảng (

    for (var prop in object){
        //loop body
    }
    64)

  6. Một cuộc gọi chức năng (

    for (var prop in object){
        //loop body
    }
    65)

Có rất nhiều thứ đang diễn ra trên mỗi vòng lặp đơn giản này, mặc dù không có nhiều mã. Tốc độ mà mã sẽ thực thi phần lớn được xác định bởi những gì

for (var prop in object){
    //loop body
}
66 thực hiện với từng mục, nhưng ngay cả như vậy, việc giảm tổng số thao tác trên mỗi lần lặp có thể cải thiện đáng kể hiệu suất vòng lặp tổng thể

Bước đầu tiên trong việc tối ưu hóa số lượng công việc trong một vòng lặp là giảm thiểu số lần tra cứu thành viên đối tượng và mục mảng. Như đã thảo luận trong Chương 2, các biến này mất nhiều thời gian hơn để truy cập trong hầu hết các trình duyệt so với các biến cục bộ hoặc giá trị bằng chữ. Các ví dụ trước thực hiện tra cứu thuộc tính cho

for (var prop in object){
    //loop body
}
49 mỗi lần qua vòng lặp. Làm như vậy là lãng phí, vì giá trị này sẽ không thay đổi trong quá trình thực hiện vòng lặp và do đó là một cú đánh hiệu suất không cần thiết. Bạn có thể cải thiện hiệu suất vòng lặp một cách dễ dàng bằng cách thực hiện tra cứu thuộc tính một lần, lưu trữ giá trị trong một biến cục bộ, sau đó sử dụng biến đó trong điều kiện kiểm soát

for (var prop in object){
    //loop body
}
6

Mỗi vòng lặp được viết lại này thực hiện tra cứu một thuộc tính duy nhất cho độ dài mảng trước khi thực hiện vòng lặp. Điều này cho phép điều kiện kiểm soát chỉ bao gồm các biến cục bộ và do đó chạy nhanh hơn nhiều. Tùy thuộc vào độ dài của mảng, bạn có thể tiết kiệm khoảng 25% tổng thời gian thực hiện vòng lặp trong hầu hết các trình duyệt (và tới 50% trong Internet Explorer)

Bạn cũng có thể tăng hiệu suất của các vòng lặp bằng cách đảo ngược thứ tự của chúng. Thông thường, thứ tự xử lý các mục mảng không liên quan đến tác vụ và do đó, bắt đầu từ mục cuối cùng và xử lý mục đầu tiên là một giải pháp thay thế có thể chấp nhận được. Đảo ngược thứ tự vòng lặp là một cách tối ưu hóa hiệu suất phổ biến trong các ngôn ngữ lập trình nhưng nhìn chung vẫn chưa được hiểu rõ lắm. Trong JavaScript, việc đảo ngược vòng lặp sẽ dẫn đến cải thiện hiệu suất nhỏ cho các vòng lặp, với điều kiện là bạn loại bỏ các thao tác bổ sung do đó

for (var prop in object){
    //loop body
}
5

Các vòng lặp trong ví dụ này được đảo ngược và kết hợp điều kiện điều khiển với thao tác giảm dần. Mỗi điều kiện kiểm soát bây giờ chỉ đơn giản là so sánh với số không. Các điều kiện kiểm soát được so sánh với giá trị

for (var prop in object){
    //loop body
}
4 và bất kỳ số khác không nào sẽ tự động bị ép buộc thành
for (var prop in object){
    //loop body
}
4, làm cho số 0 tương đương với
for (var prop in object){
    //loop body
}
50. Trên thực tế, điều kiện kiểm soát đã được thay đổi từ hai phép so sánh (bộ lặp có nhỏ hơn tổng và giá trị đó bằng với
for (var prop in object){
    //loop body
}
4 không?) thành một phép so sánh duy nhất (giá trị có phải là
for (var prop in object){
    //loop body
}
4 không?). Cắt giảm từ hai lần so sánh trên mỗi lần lặp xuống còn một lần tăng tốc các vòng lặp hơn nữa. Bằng cách đảo ngược các vòng lặp và giảm thiểu tra cứu thuộc tính, bạn có thể thấy thời gian thực hiện nhanh hơn tới 50%â60% so với ban đầu

Để so sánh với bản gốc, đây là các hoạt động được thực hiện trên mỗi lần lặp cho các vòng lặp này

  1. Một so sánh (

    for (var prop in object){
        //loop body
    }
    53) trong điều kiện kiểm soát

  2. Phép toán giảm một lần (______454)

  3. Tra cứu một mảng (

    for (var prop in object){
        //loop body
    }
    64)

  4. Một cuộc gọi chức năng (

    for (var prop in object){
        //loop body
    }
    65)

Mã vòng lặp mới có ít hơn hai thao tác trên mỗi lần lặp, điều này có thể dẫn đến tăng hiệu suất khi số lần lặp tăng lên

Ghi chú

Giảm công việc được thực hiện trên mỗi lần lặp là hiệu quả nhất khi vòng lặp có độ phức tạp là O(n). Khi vòng lặp phức tạp hơn O(n), bạn nên tập trung chú ý vào việc giảm số lần lặp lại

Giảm số lần lặp

Ngay cả mã nhanh nhất trong thân vòng lặp cũng sẽ cộng lại khi lặp lại hàng nghìn lần. Ngoài ra, có một lượng nhỏ chi phí hoạt động liên quan đến việc thực thi phần thân vòng lặp, điều này chỉ làm tăng thêm thời gian thực hiện tổng thể. Do đó, việc giảm số lần lặp trong suốt vòng lặp có thể dẫn đến hiệu suất tăng cao hơn. Cách tiếp cận nổi tiếng nhất để hạn chế lặp lại vòng lặp là một mẫu có tên là Thiết bị Duffâs

Duffâs Device là một kỹ thuật hủy kiểm soát các thân vòng lặp để mỗi lần lặp thực sự thực hiện công việc của nhiều lần lặp. Jeff Greenberg được ghi nhận là người đầu tiên xuất bản cổng Duffâs Device sang JavaScript từ triển khai ban đầu của nó trong C. Một triển khai điển hình trông như thế này

for (var prop in object){
    //loop body
}
5

Ý tưởng cơ bản đằng sau việc triển khai Thiết bị Duffâs này là mỗi chuyến đi qua vòng lặp được phép thực hiện tối đa tám cuộc gọi tới

for (var prop in object){
    //loop body
}
66. Số lần lặp qua vòng lặp được xác định bằng cách chia tổng số mục cho tám. Bởi vì không phải tất cả các số đều chia hết cho 8, biến
for (var prop in object){
    //loop body
}
58 giữ phần còn lại và cho biết có bao nhiêu cuộc gọi đến
for (var prop in object){
    //loop body
}
66 sẽ xảy ra trong chuyến đi đầu tiên qua vòng lặp. Nếu có 12 mục, thì chuyến đi đầu tiên qua vòng lặp sẽ gọi
for (var prop in object){
    //loop body
}
66 4 lần, sau đó chuyến thứ hai sẽ gọi
for (var prop in object){
    //loop body
}
66 8 lần, tổng cộng có hai chuyến đi qua vòng lặp thay vì 12

Phiên bản nhanh hơn một chút của thuật toán này loại bỏ câu lệnh

for (var prop in object){
    //loop body
}
52 và tách phần xử lý còn lại khỏi phần xử lý chính

for (var prop in object){
    //loop body
}
2

Mặc dù việc triển khai này bây giờ là hai vòng lặp thay vì một, nhưng nó chạy nhanh hơn so với ban đầu bằng cách loại bỏ câu lệnh

for (var prop in object){
    //loop body
}
52 khỏi thân vòng lặp

Việc sử dụng Thiết bị của Duff, phiên bản gốc hay phiên bản sửa đổi, có đáng hay không, phụ thuộc phần lớn vào số lần lặp lại mà bạn đang thực hiện. Trong trường hợp số lần lặp vòng lặp ít hơn 1.000, bạn có thể chỉ thấy mức độ cải thiện hiệu suất không đáng kể so với việc sử dụng cấu trúc vòng lặp thông thường. Tuy nhiên, khi số lần lặp lại tăng lên 1.000 lần, hiệu quả của Thiết bị Duffâs tăng lên đáng kể. Chẳng hạn, ở 500.000 lần lặp, thời gian thực hiện ít hơn tới 70% so với một vòng lặp thông thường

Phiên bản thứ năm của ECMA-262 đã giới thiệu một phương thức mới trên đối tượng gốc

for (var prop in object){
    //loop body
}
54 có tên là
for (var prop in object){
    //loop body
}
55. Phương thức này lặp qua các thành viên của một mảng và chạy một hàm trên mỗi. Hàm được chạy trên mỗi mục được truyền vào
for (var prop in object){
    //loop body
}
55 dưới dạng đối số và sẽ nhận ba đối số khi được gọi, đó là giá trị mục mảng, chỉ mục của mục mảng và chính mảng đó. Sau đây là một ví dụ sử dụng

for (var i=0; i < 10; i++){
    //loop body
}
20

Phương pháp

for (var prop in object){
    //loop body
}
55 được triển khai nguyên bản trong Firefox, Chrome và Safari. Ngoài ra, hầu hết các thư viện JavaScript đều có logic tương đương

for (var i=0; i < 10; i++){
    //loop body
}
21

Mặc dù phép lặp dựa trên chức năng đại diện cho một phương pháp lặp thuận tiện hơn, nhưng nó cũng chậm hơn một chút so với phép lặp dựa trên vòng lặp. Sự chậm lại có thể được giải thích bằng chi phí liên quan đến một phương thức bổ sung được gọi trên mỗi mục mảng. Trong mọi trường hợp, phép lặp dựa trên chức năng mất tới tám lần so với phép lặp dựa trên vòng lặp và do đó không phải là cách tiếp cận phù hợp khi thời gian thực hiện là một mối quan tâm đáng kể

Về bản chất tương tự như các vòng lặp, các điều kiện xác định cách thức thực thi chảy qua JavaScript. Đối số truyền thống về việc sử dụng câu lệnh

for (var prop in object){
    //loop body
}
58 hay câu lệnh
for (var prop in object){
    //loop body
}
52 áp dụng cho JavaScript giống như đối với các ngôn ngữ khác. Vì các trình duyệt khác nhau đã triển khai các tối ưu hóa kiểm soát luồng khác nhau nên không phải lúc nào cũng rõ nên sử dụng kỹ thuật nào

Lý thuyết phổ biến về việc sử dụng

for (var prop in object){
    //loop body
}
58 so với
for (var prop in object){
    //loop body
}
52 dựa trên số lượng điều kiện được thử nghiệm. số lượng điều kiện càng lớn, bạn càng có xu hướng sử dụng
for (var prop in object){
    //loop body
}
52 thay vì
for (var prop in object){
    //loop body
}
58. Điều này thường dẫn đến việc mã nào dễ đọc hơn. Lập luận là
for (var prop in object){
    //loop body
}
58 dễ đọc hơn khi có ít điều kiện hơn và
for (var prop in object){
    //loop body
}
52 dễ đọc hơn khi số lượng điều kiện lớn. Hãy xem xét những điều sau đây

for (var i=0; i < 10; i++){
    //loop body
}
22

Mặc dù cả hai đoạn mã đều thực hiện cùng một nhiệm vụ, nhưng nhiều người sẽ cho rằng câu lệnh

for (var prop in object){
    //loop body
}
58 dễ đọc hơn nhiều so với câu lệnh
for (var prop in object){
    //loop body
}
52. Tuy nhiên, việc tăng số lượng điều kiện thường đảo ngược quan điểm đó

for (var i=0; i < 10; i++){
    //loop body
}
23

Hầu hết sẽ coi câu lệnh

for (var prop in object){
    //loop body
}
52 trong mã này dễ đọc hơn câu lệnh
for (var prop in object){
    //loop body
}
58

Hóa ra, câu lệnh

for (var prop in object){
    //loop body
}
52 nhanh hơn trong hầu hết các trường hợp khi so sánh với
for (var prop in object){
    //loop body
}
58, nhưng chỉ nhanh hơn đáng kể khi số lượng điều kiện lớn. Sự khác biệt chính về hiệu suất giữa hai loại này là chi phí gia tăng của một điều kiện bổ sung đối với
for (var prop in object){
    //loop body
}
58 lớn hơn so với đối với
for (var prop in object){
    //loop body
}
52. Do đó, xu hướng tự nhiên của chúng tôi là sử dụng
for (var prop in object){
    //loop body
}
58 cho một số ít điều kiện và tuyên bố
for (var prop in object){
    //loop body
}
52 cho số lượng điều kiện lớn hơn chính xác là lời khuyên đúng đắn khi xem xét hiệu suất

Nói chung,

for (var prop in object){
    //loop body
}
58 được sử dụng tốt nhất khi có hai giá trị riêng biệt hoặc một vài phạm vi giá trị khác nhau để kiểm tra. Khi có nhiều hơn hai giá trị rời rạc để kiểm tra, câu lệnh
for (var prop in object){
    //loop body
}
52 là lựa chọn tối ưu nhất

Khi tối ưu hóa

for (var prop in object){
    //loop body
}
58, mục tiêu luôn là giảm thiểu số lượng điều kiện cần đánh giá trước khi thực hiện đúng đường dẫn. Do đó, tối ưu hóa đơn giản nhất là đảm bảo rằng các điều kiện phổ biến nhất được ưu tiên. Hãy xem xét những điều sau đây

for (var i=0; i < 10; i++){
    //loop body
}
24

Mã này chỉ tối ưu nếu

for (var i=0; i < 10; i++){
    //loop body
}
209 thường xuyên nhất nhỏ hơn 5. Nếu
for (var i=0; i < 10; i++){
    //loop body
}
209 thường lớn hơn hoặc bằng 10, thì hai điều kiện phải được đánh giá mỗi lần trước khi thực hiện đúng đường dẫn, cuối cùng sẽ tăng lượng thời gian trung bình dành cho câu lệnh này. Các điều kiện trong một
for (var prop in object){
    //loop body
}
58 phải luôn được sắp xếp theo thứ tự từ nhiều khả năng nhất đến ít khả năng nhất để đảm bảo thời gian thực hiện nhanh nhất có thể

Một cách tiếp cận khác để giảm thiểu việc đánh giá điều kiện là tổ chức ________ 558 thành một chuỗi các câu lệnh ________ 558 lồng nhau. Việc sử dụng một

for (var prop in object){
    //loop body
}
58 lớn, duy nhất thường dẫn đến thời gian thực hiện tổng thể chậm hơn khi mỗi điều kiện bổ sung được đánh giá. Ví dụ

for (var i=0; i < 10; i++){
    //loop body
}
25

Với câu lệnh

for (var prop in object){
    //loop body
}
58 này, số lượng điều kiện tối đa để đánh giá là 10. Điều này làm chậm thời gian thực hiện trung bình nếu bạn giả sử rằng các giá trị có thể có cho
for (var i=0; i < 10; i++){
    //loop body
}
209 được phân bổ đều trong khoảng từ 0 đến 10. Để giảm thiểu số lượng điều kiện cần đánh giá, mã có thể được viết lại thành một loạt các câu lệnh
for (var prop in object){
    //loop body
}
58 lồng nhau, chẳng hạn như

for (var i=0; i < 10; i++){
    //loop body
}
26

Câu lệnh

for (var prop in object){
    //loop body
}
58 được viết lại có số lượng tối đa bốn lần đánh giá điều kiện mỗi lần thông qua. Điều này đạt được bằng cách áp dụng cách tiếp cận giống như tìm kiếm nhị phân, chia các giá trị có thể thành một loạt các phạm vi để kiểm tra và sau đó đi sâu hơn vào phần đó. Lượng thời gian trung bình cần thiết để thực thi mã này bằng khoảng một nửa thời gian cần thiết để thực thi câu lệnh
for (var prop in object){
    //loop body
}
58 trước đó khi các giá trị được phân bổ đều trong khoảng từ 0 đến 10. Cách tiếp cận này là tốt nhất khi có các phạm vi giá trị cần kiểm tra (trái ngược với các giá trị rời rạc, trong trường hợp đó, câu lệnh
for (var prop in object){
    //loop body
}
52 thường phù hợp hơn)

Đôi khi, cách tiếp cận tốt nhất đối với câu điều kiện là tránh sử dụng hoàn toàn

for (var prop in object){
    //loop body
}
58 và
for (var prop in object){
    //loop body
}
52. Khi có một số lượng lớn các giá trị rời rạc để kiểm tra, cả
for (var prop in object){
    //loop body
}
58 và
for (var prop in object){
    //loop body
}
52 đều chậm hơn đáng kể so với sử dụng bảng tra cứu. Có thể tạo bảng tra cứu bằng cách sử dụng mảng hoặc đối tượng thông thường trong JavaScript và việc truy cập dữ liệu từ bảng tra cứu nhanh hơn nhiều so với sử dụng
for (var prop in object){
    //loop body
}
58 hoặc
for (var prop in object){
    //loop body
}
52, đặc biệt khi số lượng điều kiện lớn (xem Hình 4-1)

Chuyển đổi so với hiệu suất if-else C++

Hình 4-1.  Tra cứu mục mảng so với sử dụng if-else hoặc switch trong Internet Explorer 7

Các bảng tra cứu không chỉ rất nhanh so với

for (var prop in object){
    //loop body
}
58 và
for (var prop in object){
    //loop body
}
52, mà chúng còn giúp làm cho mã dễ đọc hơn khi có một số lượng lớn các giá trị rời rạc để kiểm tra. Ví dụ: các câu lệnh
for (var prop in object){
    //loop body
}
52 bắt đầu trở nên khó sử dụng khi lớn, chẳng hạn như

for (var i=0; i < 10; i++){
    //loop body
}
27

Lượng không gian mà câu lệnh

for (var prop in object){
    //loop body
}
52 này chiếm trong mã có thể không tỷ lệ thuận với tầm quan trọng của nó. Toàn bộ cấu trúc có thể được thay thế bằng cách sử dụng một mảng làm bảng tra cứu

for (var i=0; i < 10; i++){
    //loop body
}
28

Khi sử dụng bảng tra cứu, bạn đã loại bỏ hoàn toàn mọi đánh giá điều kiện. Hoạt động trở thành tra cứu mục mảng hoặc tra cứu thành viên đối tượng. Đây là một lợi thế lớn cho các bảng tra cứu. vì không có điều kiện để đánh giá nên có rất ít hoặc không có chi phí bổ sung khi số lượng giá trị có thể tăng lên

Bảng tra cứu hữu ích nhất khi có ánh xạ logic giữa một khóa và một giá trị (như trong ví dụ trước). Câu lệnh

for (var prop in object){
    //loop body
}
52 phù hợp hơn khi mỗi phím yêu cầu một hành động duy nhất hoặc tập hợp các hành động diễn ra

Các thuật toán phức tạp thường được thực hiện dễ dàng hơn bằng cách sử dụng đệ quy. Trên thực tế, có một số thuật toán truyền thống coi việc triển khai là đệ quy, chẳng hạn như hàm trả về giai thừa

for (var i=0; i < 10; i++){
    //loop body
}
29

Vấn đề với các hàm đệ quy là điều kiện thiết bị đầu cuối bị thiếu hoặc không được xác định rõ ràng có thể dẫn đến thời gian thực hiện lâu làm đóng băng giao diện người dùng. Hơn nữa, các hàm đệ quy có nhiều khả năng chạy vào giới hạn kích thước ngăn xếp cuộc gọi của trình duyệt

Lượng đệ quy được hỗ trợ bởi các công cụ JavaScript khác nhau và có liên quan trực tiếp đến kích thước của ngăn xếp lệnh gọi JavaScript. Ngoại trừ Internet Explorer, ngăn xếp cuộc gọi có liên quan đến bộ nhớ hệ thống khả dụng, tất cả các trình duyệt khác đều có giới hạn ngăn xếp cuộc gọi tĩnh. Kích thước ngăn xếp cuộc gọi cho các phiên bản trình duyệt gần đây nhất tương đối cao so với các trình duyệt cũ hơn (ví dụ: Safari 2 có kích thước ngăn xếp cuộc gọi là 100). Hình 4-2 hiển thị kích thước ngăn xếp cuộc gọi trên các trình duyệt chính

Chuyển đổi so với hiệu suất if-else C++

Hình 4-2.  Kích thước ngăn xếp cuộc gọi JavaScript trong trình duyệt

Khi bạn vượt quá kích thước ngăn xếp cuộc gọi tối đa bằng cách giới thiệu quá nhiều đệ quy, trình duyệt sẽ báo lỗi với một trong các thông báo sau

  • trình duyệt web IE. âTràn ngăn xếp tại dòng xâ

  • firefox. Quá nhiều đệ quyâ

  • Cuộc đi săn. âĐã vượt quá kích thước ngăn xếp cuộc gọi tối đaâ

  • Ô-pê-ra. âHủy bỏ (điều khiển tràn ngăn xếp)â

Chrome là trình duyệt duy nhất không hiển thị thông báo cho người dùng khi vượt quá kích thước ngăn xếp cuộc gọi

Có lẽ phần thú vị nhất của lỗi tràn ngăn xếp là chúng là lỗi JavaScript thực tế trong một số trình duyệt và do đó có thể bị bẫy bằng cách sử dụng câu lệnh

for (var i=0; i < 10; i++){
    //loop body
}
232. Loại ngoại lệ khác nhau dựa trên trình duyệt đang được sử dụng. Trong Firefox, đó là một
for (var i=0; i < 10; i++){
    //loop body
}
233; . (Opera không gây ra lỗi; nó chỉ dừng công cụ JavaScript. ) Điều này cho phép xử lý các lỗi như vậy ngay từ JavaScript

for (var i=0; i < 10; i++){
    //loop body
}
80

Nếu không được xử lý, các lỗi này sẽ nổi lên giống như bất kỳ lỗi nào khác (trong Firefox, nó xuất hiện trong Firebug và bảng điều khiển lỗi; trong Safari/Chrome, lỗi này xuất hiện trong bảng điều khiển JavaScript), ngoại trừ trong Internet Explorer. IE sẽ không chỉ hiển thị lỗi JavaScript mà còn hiển thị hộp thoại trông giống như một cảnh báo với thông báo tràn ngăn xếp

Ghi chú

Mặc dù có thể bẫy các lỗi này trong JavaScript nhưng không nên. Không nên triển khai tập lệnh nào có khả năng vượt quá kích thước ngăn xếp cuộc gọi tối đa

Khi bạn gặp phải giới hạn kích thước ngăn xếp cuộc gọi, bước đầu tiên của bạn là xác định bất kỳ trường hợp đệ quy nào trong mã. Cuối cùng, có hai mẫu đệ quy cần lưu ý. Đầu tiên là mẫu đệ quy đơn giản được trình bày trong hàm

for (var i=0; i < 10; i++){
    //loop body
}
236 được hiển thị trước đó, khi một hàm gọi chính nó. Mô hình chung là như sau

for (var i=0; i < 10; i++){
    //loop body
}
81

Mẫu này thường dễ xác định khi xảy ra lỗi. Mẫu thứ hai, tinh tế hơn bao gồm hai chức năng

for (var i=0; i < 10; i++){
    //loop body
}
82

Trong mẫu đệ quy này, hai hàm mỗi hàm gọi hàm kia, sao cho một vòng lặp vô hạn được hình thành. Đây là mẫu rắc rối hơn và khó xác định hơn nhiều trong các cơ sở mã lớn

Hầu hết các lỗi ngăn xếp cuộc gọi đều liên quan đến một trong hai mẫu đệ quy này. Nguyên nhân thường gặp của lỗi tràn ngăn xếp là do điều kiện đầu cuối không chính xác, vì vậy, bước đầu tiên sau khi xác định mẫu là xác thực điều kiện đầu cuối. Nếu điều kiện đầu cuối là chính xác, thì thuật toán chứa quá nhiều đệ quy để có thể chạy an toàn trong trình duyệt và nên thay đổi để sử dụng phép lặp, ghi nhớ hoặc cả hai

Bất kỳ thuật toán nào có thể được thực hiện bằng cách sử dụng đệ quy cũng có thể được thực hiện bằng cách sử dụng phép lặp. Các thuật toán lặp thường bao gồm một số vòng lặp khác nhau thực hiện các khía cạnh khác nhau của quy trình và do đó đưa ra các vấn đề về hiệu suất của chính chúng. Tuy nhiên, việc sử dụng các vòng lặp được tối ưu hóa thay cho các hàm đệ quy chạy dài có thể giúp cải thiện hiệu suất do chi phí vòng lặp thấp hơn so với khi thực thi một hàm

Ví dụ, thuật toán sắp xếp hợp nhất được triển khai thường xuyên nhất bằng cách sử dụng đệ quy. Một triển khai JavaScript đơn giản của sắp xếp hợp nhất như sau

for (var i=0; i < 10; i++){
    //loop body
}
83

Mã cho cách sắp xếp hợp nhất này khá đơn giản và dễ hiểu, nhưng bản thân hàm

for (var i=0; i < 10; i++){
    //loop body
}
237 lại được gọi rất thường xuyên. Hàm đệ quy là nguyên nhân rất phổ biến gây ra lỗi tràn ngăn xếp trên trình duyệt

Gặp phải lỗi tràn ngăn xếp không nhất thiết có nghĩa là toàn bộ thuật toán phải thay đổi; . Thuật toán sắp xếp hợp nhất cũng có thể được thực hiện bằng cách sử dụng phép lặp, chẳng hạn như

for (var i=0; i < 10; i++){
    //loop body
}
84

Việc triển khai

for (var i=0; i < 10; i++){
    //loop body
}
237 này thực hiện công việc giống như lần trước mà không sử dụng đệ quy. Mặc dù phiên bản lặp lại của sắp xếp hợp nhất có thể chậm hơn một chút so với tùy chọn đệ quy, nhưng nó không có cùng tác động ngăn xếp lệnh gọi như phiên bản đệ quy. Chuyển thuật toán đệ quy sang thuật toán lặp chỉ là một trong các tùy chọn để tránh lỗi tràn ngăn xếp

Tránh làm việc là kỹ thuật tối ưu hóa hiệu suất tốt nhất. Mã của bạn phải làm càng ít công việc thì nó thực thi càng nhanh. Dọc theo những dòng đó, nó cũng có ý nghĩa để tránh lặp lại công việc. Thực hiện cùng một nhiệm vụ nhiều lần là một sự lãng phí thời gian thực hiện. Ghi nhớ là một cách tiếp cận để tránh lặp lại công việc bằng cách lưu vào bộ đệm các phép tính trước đó để sử dụng lại sau này, điều này làm cho việc ghi nhớ trở thành một kỹ thuật hữu ích cho các thuật toán đệ quy

Khi các hàm đệ quy được gọi nhiều lần trong quá trình thực thi mã, sẽ có nhiều công việc trùng lặp. Hàm

for (var i=0; i < 10; i++){
    //loop body
}
236, được giới thiệu trước đó trong Đệ quy, là một ví dụ tuyệt vời về cách có thể lặp lại công việc nhiều lần bằng các hàm đệ quy. Hãy xem xét đoạn mã sau

for (var i=0; i < 10; i++){
    //loop body
}
85

Mã này tạo ra ba giai thừa và kết quả là hàm

for (var i=0; i < 10; i++){
    //loop body
}
236 được gọi tổng cộng 18 lần. Phần tồi tệ nhất của mã này là tất cả các công việc cần thiết được hoàn thành trên dòng đầu tiên. Vì giai thừa của 6 bằng 6 nhân với giai thừa của 5, nên giai thừa của 5 được tính hai lần. Tệ hơn nữa, giai thừa của 4 được tính ba lần. Sẽ hợp lý hơn nhiều nếu lưu các phép tính đó và sử dụng lại chúng thay vì bắt đầu lại từ đầu với mỗi lệnh gọi hàm

Bạn có thể viết lại hàm

for (var i=0; i < 10; i++){
    //loop body
}
236 để sử dụng tính năng ghi nhớ theo cách sau

for (var i=0; i < 10; i++){
    //loop body
}
86

Chìa khóa cho phiên bản ghi nhớ này của hàm giai thừa là việc tạo một đối tượng bộ đệm. Đối tượng này được lưu trữ trên chính hàm đó và được điền sẵn hai giai thừa đơn giản nhất. 0 và 1. Trước khi tính giai thừa, bộ đệm này được kiểm tra để xem phép tính đã được thực hiện chưa. Không có giá trị bộ đệm có nghĩa là phép tính phải được thực hiện lần đầu tiên và kết quả được lưu trong bộ đệm để sử dụng sau. Hàm này được sử dụng theo cách tương tự như hàm

for (var i=0; i < 10; i++){
    //loop body
}
236 ban đầu

for (var i=0; i < 10; i++){
    //loop body
}
87

Mã này trả về ba giai thừa khác nhau nhưng thực hiện tổng cộng tám lệnh gọi tới

for (var i=0; i < 10; i++){
    //loop body
}
243. Vì tất cả các tính toán cần thiết được hoàn thành trên dòng đầu tiên, nên hai dòng tiếp theo không cần thực hiện bất kỳ phép đệ quy nào vì các giá trị được lưu trong bộ nhớ cache được trả về

Quá trình ghi nhớ có thể hơi khác nhau đối với từng chức năng đệ quy, nhưng nhìn chung áp dụng cùng một mẫu. Để ghi nhớ một chức năng dễ dàng hơn, bạn có thể xác định một hàm

for (var i=0; i < 10; i++){
    //loop body
}
244 gói gọn chức năng cơ bản. Ví dụ

for (var i=0; i < 10; i++){
    //loop body
}
88

Hàm

for (var i=0; i < 10; i++){
    //loop body
}
244 này chấp nhận hai đối số. một chức năng để ghi nhớ và một đối tượng bộ đệm tùy chọn. Đối tượng bộ đệm có thể được chuyển vào nếu bạn muốn điền trước một số giá trị; . Sau đó, một hàm trình bao được tạo để bao bọc kết quả gốc (
for (var i=0; i < 10; i++){
    //loop body
}
246) và đảm bảo rằng một kết quả mới chỉ được tính nếu nó chưa từng được tính trước đó. Hàm shell này được trả về để bạn có thể gọi nó trực tiếp, chẳng hạn như

for (var i=0; i < 10; i++){
    //loop body
}
89

Ghi nhớ chung của loại này kém tối ưu hơn so với việc cập nhật thủ công thuật toán cho một hàm nhất định vì hàm

for (var i=0; i < 10; i++){
    //loop body
}
244 lưu trữ kết quả của lệnh gọi hàm với các đối số cụ thể. Do đó, các cuộc gọi đệ quy chỉ được lưu khi hàm shell được gọi nhiều lần với cùng các đối số. Vì lý do này, tốt hơn hết là bạn nên triển khai ghi nhớ theo cách thủ công trong những chức năng có vấn đề nghiêm trọng về hiệu suất thay vì áp dụng một giải pháp ghi nhớ chung chung.

Cũng giống như các ngôn ngữ lập trình khác, cách bạn tính mã và thuật toán bạn chọn ảnh hưởng đến thời gian thực thi của JavaScript. Không giống như các ngôn ngữ lập trình khác, JavaScript có một bộ tài nguyên hạn chế để rút ra, vì vậy các kỹ thuật tối ưu hóa thậm chí còn quan trọng hơn

  • Các vòng lặp

    for (var prop in object){
        //loop body
    }
    0,
    for (var prop in object){
        //loop body
    }
    9 và
    for (var i=0; i < 10; i++){
        //loop body
    }
    64 đều có các đặc điểm hiệu suất tương tự nhau và do đó không có loại vòng lặp nào nhanh hơn hoặc chậm hơn đáng kể so với các vòng lặp khác

  • Tránh vòng lặp

    for (var i=0; i < 10; i++){
        //loop body
    }
    67 trừ khi bạn cần lặp lại một số thuộc tính đối tượng không xác định

  • Cách tốt nhất để cải thiện hiệu suất của vòng lặp là giảm số lượng công việc được thực hiện trên mỗi lần lặp và giảm số lần lặp lại vòng lặp

  • Nói chung,

    for (var prop in object){
        //loop body
    }
    52 luôn nhanh hơn
    for (var prop in object){
        //loop body
    }
    58, nhưng không phải lúc nào cũng là giải pháp tốt nhất

  • Bảng tra cứu là giải pháp thay thế nhanh hơn để đánh giá nhiều điều kiện bằng cách sử dụng

    for (var prop in object){
        //loop body
    }
    58 hoặc
    for (var prop in object){
        //loop body
    }
    52

  • Kích thước ngăn xếp cuộc gọi trình duyệt giới hạn số lượng đệ quy mà JavaScript được phép thực hiện;

  • Nếu bạn gặp phải lỗi tràn ngăn xếp, hãy thay đổi phương thức thành thuật toán lặp hoặc sử dụng tính năng ghi nhớ để tránh lặp lại công việc

Số lượng mã được thực thi càng lớn thì hiệu suất thu được từ việc sử dụng các chiến lược này càng lớn

Chuyển đổi nhanh hơn nếu

Các hằng số trường hợp trong câu lệnh switch tạo một bảng nhảy tại thời điểm biên dịch. Bảng nhảy này chọn đường dẫn thực hiện dựa trên giá trị của biểu thức. Nếu chúng ta có nhiều lựa chọn thì việc thực thi câu lệnh switch sẽ nhanh hơn nhiều so với logic tương đương của câu lệnh 'if-else' .

Chuyển đổi tốt hơn nếu

Hóa ra, câu lệnh switch nhanh hơn trong hầu hết các trường hợp so với câu lệnh if-else , nhưng chỉ nhanh hơn đáng kể khi số . Sự khác biệt chính về hiệu suất giữa hai loại này là chi phí gia tăng của một điều kiện bổ sung đối với if-else lớn hơn so với đối với switch.

Công tắc có hiệu quả hơn câu lệnh if không?

Câu lệnh chuyển đổi hoạt động nhanh hơn nhiều so với bậc thang if-else tương đương . Đó là do trình biên dịch tạo một bảng nhảy cho một công tắc trong quá trình biên dịch. Kết quả là trong quá trình thực hiện, thay vì kiểm tra trường hợp nào thỏa mãn, nó chỉ quyết định trường hợp nào phải thực hiện.

Chuyển đổi tốt hơn nếu

Lưu ý, chuyển chậm hơn . Thật hấp dẫn khi nghĩ rằng một công tắc luôn nhanh hơn một câu lệnh if tương đương. Tuy nhiên, điều này là không đúng sự thật. Và một tình huống mà công tắc chậm hơn là khi thời gian chạy thực tế của chương trình có sự phân bổ đầu vào rất sai lệch.