2012-04-12 31 views
10

Vì vậy, tôi đang lập trình một mô phỏng vật lý Javascript 2d. Hiệu suất là tốt, nhưng tôi sẽ thực hiện tối ưu hóa để làm cho nó tốt hơn. Vì vậy, bởi vì chương trình làm việc với rất nhiều hình học vật lý, tôi thực hiện một số tính toán Định lý Pythagore trong chương trình. Trong tất cả, khoảng năm phép tính; cùng nhau, chúng chạy khoảng một triệu lần mỗi giây. Vì vậy, tôi đoán nó sẽ tăng hiệu suất nếu tôi đặt mã định lý Pythagore đơn giản đó vào một hàm mới và gọi nó là; sau khi tất cả, theo cách đó trình duyệt có ít biên dịch để làm. Vì vậy, tôi đã chạy mã trong Firefox và nhận được .... tăng 4000000% trong thời gian thực hiện phép tính đó.JS: Mất bao lâu để gọi một hàm?

Làm cách nào? Đó là cùng một mã: Math.sqrt (x * x + y * y), do đó, làm thế nào để thêm nó như là một chức năng làm chậm nó xuống? Tôi cho rằng lý do là một hàm cần có thời gian chỉ để được gọi, mà không thực thi mã, và thêm rằng một triệu trong số những sự chậm trễ này trên một giây làm chậm nó xuống?

Điều đó dường như khá đáng báo động đối với tôi. Điều này cũng áp dụng cho các hàm js được xác định trước? Có vẻ như không, và nếu như vậy, làm thế nào để họ tránh được nó?

Các mã được sử dụng để đi như thế này:

function x() 
{ 
    dx=nx-mx; 
    dy=ny-my; 
    d=Math.sqrt(dx*dx+dy*dy); 
    doStuff(... 
} 

Những gì tôi cố gắng là thế này:

function x() 
{ 
    dx=nx-mx; 
    dy=ny-my; 
    d=hypo(dx,dy); 
    doStuff(... 
} 
function hypo(x,y) 
{ 
    return Math.sqrt(x*x+y*y); 
} 

Cảm ơn!

+3

Chức năng của bạn được xác định nằm ngoài phạm vi đang chạy một triệu lần một giây? – alex

+0

Và nó không đúng khi trình duyệt có "ít biên dịch để làm" bởi vì bạn đặt nó vào một hàm ... nó phải giống nhau, thực sự, đặc biệt là khi biên dịch là một điều khởi động. Nhưng @alex có lẽ đã có lý do cho sự suy giảm 400% của bạn :) – Ryan

+0

@alex Có, nó được định nghĩa trong cửa sổ chính. – mindoftea

Trả lời

7

Cuộc gọi chức năng không đáng kể hoặc thậm chí tối ưu hóa bằng các ngôn ngữ được biên dịch trước mà JS chưa bao giờ có. Ngoài ra, rất nhiều tùy thuộc vào trình duyệt.

Chúng là cái chết của tất cả hiệu suất trong các ngôn ngữ thông dịch mà JS đã chủ yếu cho đến gần đây. Hầu hết các trình duyệt hiện đại đều có trình biên dịch JIT (Just In Time), nhưng tôi tin rằng các cuộc gọi hàm tới một phạm vi khác vẫn tốn một số chi phí vì đối tượng gọi của JS phải xác định cái đang thực sự được gọi và điều đó có nghĩa là di chuyển lên và xuống các chuỗi phạm vi khác nhau.

Vì vậy, như một quy tắc chung: nếu bạn quan tâm đến IE8 và các phiên bản cũ hơn và cũ hơn của Chrome và Firefox, hãy tránh thời gian gọi hàm. Đặc biệt là các vòng trong. Đối với các trình duyệt JIT, tôi hy vọng rằng một hàm được định nghĩa bên trong hàm kia sẽ mang lại lợi ích chung (nhưng tôi vẫn thử nghiệm vì đây là công nghệ mới cho IE9 và tương đối mới đối với mọi người khác).

Một điều khác cần phải cảnh giác. Nếu một hàm đặc biệt phức tạp, thì JIT có thể không làm gì để tối ưu hóa chúng.

https://groups.google.com/forum/#!msg/closure-compiler-discuss/4B4IcUJ4SUA/OqYWpSklTE4J

Nhưng điều quan trọng là phải hiểu là khi một cái gì đó bị khóa và chỉ gọi là bên trong một bối cảnh, giống như một chức năng trong một hàm, nó phải là dễ dàng cho một JIT để tối ưu hóa. Được định nghĩa bên ngoài một hàm, nó phải xác định định nghĩa của hàm đó đang được gọi chính xác. Nó có thể ở trong một hàm bên ngoài. Nó có thể là toàn cầu. Nó có thể là một thuộc tính của prototype của constructor window, vv .. Trong một ngôn ngữ mà hàm là lớp đầu tiên, có nghĩa là tham chiếu của chúng có thể được truyền xung quanh như args giống như cách bạn truyền dữ liệu xung quanh, bạn không thể thực sự tránh được bước đó bên ngoài ngữ cảnh hiện tại của bạn.

Vì vậy, hãy thử xác định hypo bên trong X để xem điều gì xảy ra.

Một vài lời khuyên chung từ tuổi giải thích rằng vẫn có thể có giá trị trong JITs: ''

  • Các toán tử như trong someObject.property, là một quá trình đáng lưu vào bộ nhớ đệm. Nó chi phí trên cao vì có một quá trình tra cứu đối tượng cuộc gọi liên quan mỗi khi bạn sử dụng nó. Tôi tưởng tượng Chrome sẽ không giữ lại kết quả của quá trình này vì các thay đổi đối với các đối tượng cha mẹ hoặc các nguyên mẫu có thể thay đổi những gì nó thực sự tham chiếu bên ngoài một ngữ cảnh cụ thể. Trong ví dụ của bạn nếu x đang được sử dụng bởi một vòng lặp (có khả năng không sao hoặc thậm chí hữu ích nếu x được định nghĩa trong cùng hàm với vòng lặp trong JITs - giết người trong một trình thông dịch), tôi sẽ thử gán Math.sqrt cho var trước khi sử dụng nó trong hypo. Có quá nhiều tham chiếu đến nội dung bên ngoài ngữ cảnh của chức năng hiện tại của bạn có thể khiến một số JIT quyết định không đáng để tối ưu hóa nhưng đó là suy đoán thuần túy về phía tôi.

  • Sau đây có lẽ là cách nhanh nhất để lặp một mảng:

//assume a giant array called someArray 
var i = someArray.length; //note the property lookup process being cached here 
//'someArray.reverse()' if original order isimportant 
while(i--){ 
    //now do stuff with someArray[i]; 
} 

lưu ý: Mã khối không làm việc ở đây đối với một số lý do.

Làm theo cách này có thể hữu ích bởi vì về cơ bản biến đổi bước inc/decrement và so sánh logic thành chỉ giảm, hoàn toàn loại bỏ sự cần thiết của toán tử so sánh trái/phải. Lưu ý rằng trong JS toán tử giảm dần bên phải có nghĩa là tôi được chuyển qua để được đánh giá và sau đó giảm đi trước khi nó được sử dụng bên trong khối. while(0) đánh giá sai.

+0

Cảm ơn; một lời giải thích rất hay! Việc xác định hàm hypo bên trong hàm lớn hơn mất quá nhiều thời gian và tôi sẽ cần nó trong các hàm khác, vì vậy nó không đáng cho tôi. Trong sự tò mò, mặc dù, tôi đã cố gắng xác định nó bên trong. Điều đó giải quyết được 97% các vấn đề về hiệu suất! Nó vẫn còn tăng cao, nhưng tốt hơn nhiều. Bạn có biết làm thế nào các chức năng được xác định trước tránh điều này? Cảm ơn một lần nữa! – mindoftea

+0

Các đối tượng và phương thức JS chính đang hoạt động ở mức thấp hơn nhiều. Nếu bạn cố gắng cảnh báo Math.sqrt, ví dụ (không có parens), bạn có thể sẽ thấy một hàm với [MÃ NATIVE] hoặc một cái gì đó ở bên trong. Đó thực sự là một cuộc gọi kéo từ môi trường thời gian chạy được biên dịch trước của trình duyệt. Nó vẫn có thể hữu ích để cache những tra cứu mặc dù bởi vì bạn có thể thay thế các tham chiếu đến các phương thức nguyên gốc được xác định với bất cứ điều gì bạn muốn trong JS (không phải rất thông minh nhưng nó cho phép bạn làm điều đó). Điều đó có nghĩa là JIT vẫn phải tính đến quá trình tra cứu. –

+0

Ngoài ra, nếu bạn vẫn có tất cả các thiết lập, hãy thử Hypo định nghĩa nội tuyến với 'var sqrt = Math.sqrt' ngay trước nó và sau đó gọi sqrt thay vì bên trong hypo. Tôi tò mò. –

1

Trước sự ngạc nhiên của tôi, bộ nhớ đệm tra cứu theo đề nghị của Erik không làm được gì nhiều để cải thiện hiệu suất trên trình duyệt của tôi (Chromium, Linux) nhưng dường như đau hiệu suất thay vì: http://jsperf.com/inline-metric-distance

var optimizedDistance = (function() { 
    var sqrt = Math.sqrt; 
    return function (x, y) { return sqrt(x * x + y * y); } 
})(); 

chậm hơn

var unoptimizedDistance = function(x, y) { 
    return Math.sqrt(x * x + y * y); 
} 

Thậm chí gọi một bí danh là chậm hơn

var _sqrt = Math.sqrt; // _sqrt is slower than Math.sqrt! 

Nhưng sau đó một lần nữa, đây không phải là một phép đo khoa học và thực tế chính xác có thể vẫn thay đổi. Tuy nhiên, tôi muốn sử dụng Math.sqrt.

+0

Thú vị. Tôi sẽ làm bài kiểm tra của riêng tôi trong một giây. – mindoftea

+0

Được rồi. Tôi đã thử nghiệm tất cả các mã có và không có bộ nhớ đệm Math. Tôi đã định nghĩa chúng là các hình cầu để các hàm khác nhau của tôi có thể truy cập chúng và sau đó chạy nó. Dường như ngoại trừ một vài trường hợp, bộ nhớ đệm bằng cách nào đó chậm hơn. – mindoftea

Các vấn đề liên quan