2010-08-28 7 views
6

Tôi đã nghiên cứu hiệu suất JavaScript. Tôi đã học được rằng khi truy cập nhiều hơn một lần, tốt nhất là sao chép các biến đóng và các thành viên lớp vào phạm vi cục bộ để tăng tốc mọi thứ. Ví dụ:Khi hiệu suất tinh chỉnh, cách tốt nhất để gọi các phương thức JavaScript nhiều lần là gì?

var i = 100; 
var doSomething = function() { 
    var localI = i; 
    // do something with localI a bunch of times 

    var obj = { 
     a: 100 
    }; 
    var objA = obj.a; 
    // do something with objA a bunch of times 
}; 

Tôi hiểu điều này; nó thêm một phím tắt cho trình thông dịch tìm kiếm giá trị theo tên. Khái niệm này trở nên rất không rõ ràng khi giao dịch với các phương thức. Lúc đầu, tôi nghĩ rằng nó sẽ làm việc theo cùng một cách. Ví dụ:

var obj = { 
    fn: function() { 
     // Do something 
     return this.value; 
    }, 
    value: 100 
}; 
var objFn = obj.fn 
objFn(); 
// call objFn a bunch of times 

Vì vậy, điều này sẽ không hoạt động chút nào. Việc truy cập phương thức như thế này sẽ loại bỏ nó khỏi phạm vi của nó. Khi nó đạt đến dòng this.value, điều này đề cập đến đối tượng cửa sổ và this.value có thể sẽ không được xác định. Thay vì trực tiếp gọi objFn và mất phạm vi, tôi có thể vượt qua phạm vi của nó trở lại vào nó với objFn.call (obj) nhưng điều này thực hiện bất kỳ tốt hơn hoặc tồi tệ hơn sau đó obj.fn gốc()?

Tôi quyết định viết một kịch bản để kiểm tra điều này và tôi nhận được kết quả rất khó hiểu. Kịch bản lệnh này làm cho các lần lặp qua một số phép thử lặp lại thông qua hàm trên gọi nhiều lần. Thời gian trung bình cho mỗi bài kiểm tra là đầu ra cho cơ thể.

Một đối tượng được tạo bằng nhiều phương pháp đơn giản trên đó. Các phương pháp bổ sung có để xác định xem thông dịch viên có làm việc chăm chỉ hơn để định vị một phương pháp cụ thể hay không.

Kiểm tra 1 chỉ cần gọi this.a();
Kiểm tra 2 tạo biến cục bộ a = this.a rồi gọi a.call (this);
Kiểm tra 3 tạo biến cục bộ sử dụng chức năng liên kết của YUI để bảo toàn phạm vi. Tôi đã nhận xét điều này. Các cuộc gọi chức năng bổ sung được tạo bởi YUI làm theo cách này chậm hơn.

Các thử nghiệm 4, 5 và 6 là các bản sao 1, 2, 3 ngoại trừ việc sử dụng z thay vì a.

Chức năng sau của YUI được sử dụng để ngăn chặn các lỗi tập lệnh chạy trốn. Thời gian được thực hiện trong các phương pháp thử nghiệm thực tế để setTimeouts không ảnh hưởng đến kết quả. Mỗi hàm được gọi là tổng số 10000000 lần. (Dễ dàng cấu hình nếu bạn muốn chạy thử nghiệm.)

Đây là toàn bộ tài liệu XHTML tôi đã sử dụng để kiểm tra.

<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd"> 
<html lang="en" xmlns="http://www.w3.org/1999/xhtml" xmlns:v="urn:schemas-microsoft-com:vml" xml:lang="en" dir="ltr"> 
    <head> 
     <script type="text/javascript" src="http://yui.yahooapis.com/combo?3.1.2/build/yui/yui-min.js"></script> 
     <script> 
      YUI().use('node', function (Y) { 
       var o = { 
        value: '', 
        a: function() { 
         this.value += 'a'; 
        }, 
        b: function() { 
         this.value += 'b'; 
        }, 
        c: function() { 
         this.value += 'c'; 
        }, 
        d: function() { 
         this.value += 'd'; 
        }, 
        e: function() { 
         this.value += 'e'; 
        }, 
        f: function() { 
         this.value += 'f'; 
        }, 
        g: function() { 
         this.value += 'g'; 
        }, 
        h: function() { 
         this.value += 'h'; 
        }, 
        i: function() { 
         this.value += 'i'; 
        }, 
        j: function() { 
         this.value += 'j'; 
        }, 
        k: function() { 
         this.value += 'k'; 
        }, 
        l: function() { 
         this.value += 'l'; 
        }, 
        m: function() { 
         this.value += 'm'; 
        }, 
        n: function() { 
         this.value += 'n'; 
        }, 
        o: function() { 
         this.value += 'o'; 
        }, 
        p: function() { 
         this.value += 'p'; 
        }, 
        q: function() { 
         this.value += 'q'; 
        }, 
        r: function() { 
         this.value += 'r'; 
        }, 
        s: function() { 
         this.value += 's'; 
        }, 
        t: function() { 
         this.value += 't'; 
        }, 
        u: function() { 
         this.value += 'u'; 
        }, 
        v: function() { 
         this.value += 'v'; 
        }, 
        w: function() { 
         this.value += 'w'; 
        }, 
        x: function() { 
         this.value += 'x'; 
        }, 
        y: function() { 
         this.value += 'y'; 
        }, 
        z: function() { 
         this.value += 'z'; 
        }, 
        reset: function() { 
         this.value = ''; 
        }, 
        test1: function (length) { 
         var time = new Date().getTime(); 

         while ((length -= 1)) { 
          this.a(); 
         } 
         return new Date().getTime() - time; 
        }, 
        test2: function (length) { 
         var a = this.a, 
         time = new Date().getTime(); 

         while ((length -= 1)) { 
          a.call(this); 
         } 
         return new Date().getTime() - time; 
        }, 
        test3: function (length) { 
         var a = Y.bind(this.a, this), 
         time = new Date().getTime(); 

         while ((length -= 1)) { 
          a(); 
         } 
         return new Date().getTime() - time; 
        }, 
        test4: function (length) { 
         var time = new Date().getTime(); 

         while ((length -= 1)) { 
          this.z(); 
         } 
         return new Date().getTime() - time; 
        }, 
        test5: function (length) { 
         var z = this.z, 
         time = new Date().getTime(); 

         while ((length -= 1)) { 
          z.call(this); 
         } 
         return new Date().getTime() - time; 
        }, 
        test6: function (length) { 
         var z = Y.bind(this.z, this), 
         time = new Date().getTime(); 

         while ((length -= 1)) { 
          z(); 
         } 
         return new Date().getTime() - time; 
        } 
       }, 
       iterations = 100, iteration = iterations, length = 100000, 
       t1 = 0, t2 = 0, t3 = 0, t4 = 0, t5 = 0, t6 = 0, body = Y.one('body'); 

       body.set('innerHTML', '<span>Running ' + iterations + ' Iterations&hellip;</span>'); 
       while ((iteration -= 1)) { 
        Y.later(1, null, function (iteration) { 
         Y.later(1, null, function() { 
          o.reset(); 
          t1 += o.test1(length); 
         }); 
         Y.later(1, null, function() { 
          o.reset(); 
          t2 += o.test2(length); 
         }); 
         /*Y.later(1, null, function() { 
          o.reset(); 
          t3 += o.test3(length); 
         });*/ 
         Y.later(1, null, function() { 
          o.reset(); 
          t4 += o.test4(length); 
         }); 
         Y.later(1, null, function() { 
          o.reset(); 
          t5 += o.test5(length); 
         }); 
         /*Y.later(1, null, function() { 
          o.reset(); 
          t6 += o.test6(length); 
         });*/ 
         if (iteration === 1) { 
          Y.later(10, null, function() { 
           t1 /= iterations; 
           t2 /= iterations; 
           //t3 /= iterations; 
           t4 /= iterations; 
           t5 /= iterations; 
           //t6 /= iterations; 

           //body.set('innerHTML', '<dl><dt>Test 1: this.a();</dt><dd>' + t1 + '</dd><dt>Test 2: a.call(this);</dt><dd>' + t2 + '</dd><dt>Test 3: a();</dt><dd>' + t3 + '</dd><dt>Test 4: this.z();</dt><dd>' + t4 + '</dd><dt>Test 5: z.call(this);</dt><dd>' + t5 + '</dd><dt>Test 6: z();</dt><dd>' + t6 + '</dd></dl>'); 
           body.set('innerHTML', '<dl><dt>Test 1: this.a();</dt><dd>' + t1 + '</dd><dt>Test 2: a.call(this);</dt><dd>' + t2 + '</dd><dt>Test 4: this.z();</dt><dd>' + t4 + '</dd><dt>Test 5: z.call(this);</dt><dd>' + t5 + '</dd></dl>'); 
          }); 
         } 
        }, iteration); 
       } 
      }); 
     </script> 
    </head> 
    <body> 
    </body> 
</html> 

Tôi đã chạy tính năng này bằng Windows 7 trong ba trình duyệt khác nhau. Các kết quả này tính bằng mili giây.

Firefox 3.6.8

Test 1: this.a(); 
    9.23 
Test 2: a.call(this); 
    9.67 
Test 4: this.z(); 
    9.2 
Test 5: z.call(this); 
    9.61 

Chrome 7.0.503.0

Test 1: this.a(); 
    5.25 
Test 2: a.call(this); 
    4.66 
Test 4: this.z(); 
    3.71 
Test 5: z.call(this); 
    4.15 

Internet Explorer 8

Test 1: this.a(); 
    168.2 
Test 2: a.call(this); 
    197.94 
Test 4: this.z(); 
    169.6 
Test 5: z.call(this); 
    199.02 

Firefox và Internet Explorer được sản xuất kết quả về cách tôi mong đợi. Kiểm tra 1 và Test 4 tương đối gần, Test 2 và Test 5 tương đối gần, và Test 2 và Test 5 mất nhiều thời gian hơn Test 1 và Test 4 vì có thêm một cuộc gọi hàm để xử lý.

Chrome Tôi không hiểu chút nào, nhưng nó nhanh hơn rất nhiều, có lẽ việc tinh chỉnh hiệu suất phụ phần nghìn giây là không cần thiết.

Có ai có giải thích tốt về kết quả không? Cách tốt nhất để gọi các phương thức JavaScript nhiều lần là gì?

+2

bạn có thể thêm liên kết này trong câu hỏi của bạn, vì vậy những người dùng khác có thể chạy các bài kiểm tra bản thân? - http://jsfiddle.net/Lbbx5/ – Anurag

+1

Đọc liên quan: [JavaScript Widget Không có "này"] (http://michaux.ca/articles/javascript-widgets-without-this) –

+1

FYI, sử dụng Chromium (Linux) trên hệ thống của tôi (tương đối chậm), sau một vài thử nghiệm có vẻ như kết quả giống như trong Firefox (chỉ nhanh hơn): kết hợp 1/4 và 2/5 gần bằng hoặc ít hơn và 2 và 5 chậm hơn 1 và 4. –

Trả lời

1

Vâng, miễn là trang web của bạn có người dùng IE8 là khách truy cập, điều này hoàn toàn không liên quan. Sử dụng 1 hoặc 3 (người dùng sẽ không thấy sự khác biệt).

Có thể không có câu trả lời hay cho câu hỏi "tại sao". Khi nói đến tối ưu hóa, các công cụ tập lệnh này có khả năng tập trung vào việc tối ưu hóa các kịch bản mà chúng thấy xảy ra rất nhiều trong cuộc sống thực, nơi tối ưu hóa có thể được chứng minh để hoạt động chính xác và nơi nó tạo sự khác biệt, và theo cách nó làm mất hiệu lực số lượng thử nghiệm ít nhất.

+0

Trong bất kỳ ứng dụng thực nào phải lặp lại hàng triệu lần, nó có thể sẽ gọi các hàm phức tạp hơn giá trị + = 'a' và nó có thể sẽ gọi nhiều hơn một hàm cho mỗi lần lặp. Trong thử nghiệm này, tối ưu hóa khoảng ba mươi mili giây trên Internet Explorer có thể không liên quan nhưng khi ứng dụng mất nhiều thời gian hơn để thực sự làm điều gì đó và ba mươi mili giây được nhân với một vài chục cuộc gọi hàm, nó bắt đầu thêm vào độ trễ rất đáng chú ý. – Killthesand

2

Chỉ cần lý thuyết, vì vậy thực hiện việc này với một hạt muối ...

động cơ Javascript của Chrome, V8, sử dụng một kỹ thuật tối ưu hóa được gọi là lớp ẩn. Về cơ bản, nó xây dựng các đối tượng tĩnh che các đối tượng Javascript động, trong đó mỗi thuộc tính/phương thức được ánh xạ tới một địa chỉ bộ nhớ cố định có thể được tham chiếu bất thường với nhu cầu tìm kiếm bảng tra cứu tốn kém. Mỗi khi một đối tượng Javascript có một thuộc tính được thêm vào/gỡ bỏ, một lớp ẩn mới được tạo ra.

Lý thuyết của tôi cho kết quả thử nghiệm của bạn với Chrome, là việc tham chiếu hàm trong biến cục bộ miễn phí sẽ phá vỡ mối quan hệ lớp ẩn. Trong khi tham chiếu các biến cục bộ có thể cũng không yêu cầu tra cứu bảng, một bước bổ sung bây giờ phải được thực hiện trong việc gán lại biến 'this'. Đối với một phương thức trên một lớp ẩn, 'this' là một giá trị cố định, vì vậy nó có thể được gọi mà không cần bước này.

Một lần nữa chỉ cần đưa ra lý thuyết. Nó có thể đáng để kiểm tra sự khác biệt giữa tham chiếu biến cục bộ so với tham chiếu object.member trong Chrome, để xem hiệu suất truy cập cho sau này có kém hiệu quả hơn trong các trình duyệt khác, có lẽ là do Lớp ẩn.

+0

Khái niệm về các lớp ẩn trong động cơ V8 làm cho điều này trở nên xa lạ hơn. Tại sao gọi this.a() đắt hơn nhiều so với gọi this.z()? Gọi a.call (điều này) là ít tốn kém hơn so với gọi this.a() nhưng gọi z.call (này) là đắt hơn so với gọi this.z(). Họ có đảo ngược lặp lại thông qua các thành viên thay vì con trỏ bộ nhớ dereferencing? Tôi tự hỏi nếu những kết quả này là đáng tin cậy.Chúng tôi đang ở quy mô 3-5 mili giây; Tôi có thể di chuyển chuột quanh màn hình và bóp méo các kết quả này. Tôi sẽ thử một số lượng lớn các lần lặp trên chrome và xem nó như thế nào. – Killthesand

+0

Tôi đã chạy một thử nghiệm khác trên Chrome, 1000 lần lặp với một triệu cuộc gọi mỗi lần. Phải mất một lúc để chạy nhưng cung cấp nhiều kết quả nhất quán hơn. Kiểm tra 1: this.a(); 168.475 Kiểm tra 2: a.call (điều này); 172.069 Kiểm tra 4: this.z(); 168.936 Kiểm tra 5: z.call (điều này); 173.012 – Killthesand