Bất cứ ai cũng có thể giải thích hành vi hiệu suất V8 V8 bất ngờ này?


8

Cập nhật (ngày 2 tháng 3 năm 2020)

Hóa ra, mã hóa trong ví dụ của tôi ở đây được cấu trúc theo đúng cách để rơi ra khỏi một vách đá hiệu suất đã biết trong công cụ JavaScript V8 ...

Xem các cuộc thảo luận trên bug.chromium.org để biết chi tiết. Lỗi này hiện đang được xử lý và sẽ được sửa trong tương lai gần.

Cập nhật (ngày 9 tháng 1 năm 2020)

Tôi đã cố gắng cô lập mã hóa hoạt động theo cách được mô tả dưới đây vào một ứng dụng Web duy nhất, nhưng khi làm như vậy, hành vi đó đã biến mất (??). Tuy nhiên, hành vi được mô tả dưới đây vẫn tồn tại trong bối cảnh của ứng dụng đầy đủ.

Điều đó nói rằng, tôi đã tối ưu hóa mã hóa tính toán fractal và vấn đề này không còn là vấn đề trong phiên bản trực tiếp. Nếu bất cứ ai quan tâm, mô-đun JavaScript biểu hiện vấn đề này vẫn có sẵn ở đây

Tổng quat

Tôi vừa hoàn thành một ứng dụng dựa trên Web nhỏ để so sánh hiệu suất của JavaScript dựa trên trình duyệt với Web hội. Ứng dụng này tính toán một hình ảnh Mandelbrot Set, sau đó khi bạn di chuyển con trỏ chuột lên hình ảnh đó, Julia Set tương ứng được tính toán động và thời gian tính toán được hiển thị.

Bạn có thể chuyển đổi giữa sử dụng JavaScript (nhấn 'j') hoặc WebAssugging (nhấn 'w') để thực hiện tính toán và so sánh thời gian chạy.

Nhấn vào đây để xem ứng dụng làm việc

Tuy nhiên, khi viết mã này, tôi phát hiện ra một số hành vi hiệu suất JavaScript lạ bất ngờ ...

Tóm tắt sự cố

  1. Vấn đề này dường như là cụ thể đối với công cụ JavaScript V8 được sử dụng trong Chrome và Brave. Vấn đề này không xuất hiện trong các trình duyệt sử dụng SpiderMonkey (Firefox) hoặc JavaScriptCore (Safari). Tôi chưa thể kiểm tra điều này trong trình duyệt bằng công cụ Chakra

  2. Tất cả mã JavaScript cho ứng dụng Web này đã được viết dưới dạng Mô-đun ES6

  3. Tôi đã thử viết lại tất cả các hàm bằng functioncú pháp truyền thống thay vì cú pháp mũi tên ES6 mới. Thật không may, điều này không làm cho bất kỳ sự khác biệt đáng kể

Vấn đề về hiệu năng dường như liên quan đến phạm vi mà hàm JavaScript được tạo. Trong ứng dụng này, tôi gọi hai hàm một phần, mỗi hàm cung cấp cho tôi một hàm khác. Sau đó tôi chuyển các hàm được tạo này làm đối số cho một hàm khác được gọi bên trong một forvòng lặp lồng nhau .

Liên quan đến chức năng trong đó nó thực thi, có vẻ như một forvòng lặp tạo ra một cái gì đó giống với phạm vi của chính nó (mặc dù không chắc chắn đó là một phạm vi toàn diện). Sau đó, việc truyền các hàm được tạo qua ranh giới phạm vi (?) Này rất tốn kém.

Cấu trúc mã hóa cơ bản

Mỗi hàm một phần nhận giá trị X hoặc Y của vị trí con trỏ chuột trên hình ảnh Bộ Mandelbrot và trả về hàm được lặp khi tính toán bộ Julia tương ứng:

const makeJuliaXStepFn = mandelXCoord => (x, y) => mandelXCoord + diffOfSquares(x, y)
const makeJuliaYStepFn = mandelYCoord => (x, y) => mandelYCoord + (2 * x * y)

Các hàm này được gọi trong logic sau:

  • Người dùng di chuyển con trỏ chuột lên hình ảnh của Bộ Mandelbrot kích hoạt mousemovesự kiện
  • Vị trí hiện tại của con trỏ chuột được dịch sang không gian tọa độ của bộ Mandelbrot và tọa độ (X, Y) được truyền cho hàm juliaCalcJSđể tính toán Bộ Julia tương ứng.

  • Khi tạo bất kỳ Bộ Julia cụ thể nào, hai hàm một phần ở trên được gọi để tạo các hàm được lặp khi tạo Bộ Julia

  • Một forvòng lặp lồng nhau sau đó gọi hàm juliaIterđể tính màu của mỗi pixel trong tập Julia. Mã hóa đầy đủ có thể được nhìn thấy ở đây , nhưng logic cơ bản là như sau:

    const juliaCalcJS =
      (cvs, juliaSpace) => {
        // Snip - initialise canvas and create a new image array
    
        // Generate functions for calculating the current Julia Set
        let juliaXStepFn = makeJuliaXStepFn(juliaSpace.mandelXCoord)
        let juliaYStepFn = makeJuliaYStepFn(juliaSpace.mandelYCoord)
    
        // For each pixel in the canvas...
        for (let iy = 0; iy < cvs.height; ++iy) {
          for (let ix = 0; ix < cvs.width; ++ix) {
            // Translate pixel values to coordinate space of Julia Set
            let x_coord = juliaSpace.xMin + (juliaSpace.xMax - juliaSpace.xMin) * ix / (cvs.width - 1)
            let y_coord = juliaSpace.yMin + (juliaSpace.yMax - juliaSpace.yMin) * iy / (cvs.height - 1)
    
            // Calculate colour of the current pixel
            let thisColour = juliaIter(x_coord, y_coord, juliaXStepFn, juliaYStepFn)
    
            // Snip - Write pixel value to image array
          }
        }
    
        // Snip - write image array to canvas
      }
    
  • Như bạn có thể thấy, các hàm được trả về bằng cách gọi makeJuliaXStepFnmakeJuliaYStepFnbên ngoài forvòng lặp được truyền tới juliaIter, sau đó thực hiện tất cả công việc khó khăn để tính toán màu của pixel hiện tại

Khi tôi nhìn vào cấu trúc mã này, ban đầu tôi nghĩ "Cái này tốt, tất cả đều hoạt động tốt, vì vậy không có gì sai ở đây"

Ngoại trừ có. Hiệu suất chậm hơn nhiều so với dự kiến ​​...

Giải pháp bất ngờ

Nhiều cái đầu gãi và nghịch ngợm theo sau ...

Sau một thời gian, tôi phát hiện ra rằng nếu tôi di chuyển việc tạo các hàm juliaXStepFnjuliaYStepFnbên trong forcác vòng lặp bên ngoài hoặc bên trong , thì hiệu suất sẽ được cải thiện theo hệ số từ 2 đến 3 ...

WHAAAAAAT!?

Vì vậy, mã bây giờ trông như thế này

const juliaCalcJS =
  (cvs, juliaSpace) => {
    // Snip - initialise canvas and create a new image array

    // For each pixel in the canvas...
    for (let iy = 0; iy < cvs.height; ++iy) {
      // Generate functions for calculating the current Julia Set
      let juliaXStepFn = makeJuliaXStepFn(juliaSpace.mandelXCoord)
      let juliaYStepFn = makeJuliaYStepFn(juliaSpace.mandelYCoord)

      for (let ix = 0; ix < cvs.width; ++ix) {
        // Translate pixel values to coordinate space of Julia Set
        let x_coord = juliaSpace.xMin + (juliaSpace.xMax - juliaSpace.xMin) * ix / (cvs.width - 1)
        let y_coord = juliaSpace.yMin + (juliaSpace.yMax - juliaSpace.yMin) * iy / (cvs.height - 1)

        // Calculate colour of the current pixel
        let thisColour = juliaIter(x_coord, y_coord, juliaXStepFn, juliaYStepFn)

        // Snip - Write pixel value to image array
      }
    }

    // Snip - write image array to canvas
  }

Tôi đã dự kiến ​​sự thay đổi dường như không đáng kể này sẽ kém hiệu quả hơn một chút, bởi vì một cặp hàm không cần thay đổi đang được tạo lại mỗi khi chúng ta lặp lại forvòng lặp. Tuy nhiên, bằng cách di chuyển các khai báo hàm bên trong forvòng lặp, mã này thực thi nhanh hơn từ 2 đến 3 lần!

Bất cứ ai có thể giải thích hành vi này?

Cảm ơn


Câu hỏi (có thể giúp tôi không biết): Bạn đang sử dụng trình duyệt nào? Là hiệu suất đạt được đáng chú ý chỉ trong js, hoặc webassugging quá?
Giải tích

1
Cảm ơn @Calculuswhiz, đây có vẻ là sự cố cụ thể của Chrome / Brave. Safari và Firefox dường như không bị ảnh hưởng. Tôi sẽ cập nhật bài viết phù hợp
Chris W

1
Đây là một bản tóm tắt rất chi tiết ... bất kỳ lý do nào bạn đã nộp về cơ bản một vé V8 trên trang web Hỏi & Đáp về lập trình chung, thay vì trên trình theo dõi vấn đề V8 ?
Mike 'Pomax' Kamermans

2
Anh ấy đã đăng một vấn đề trên trình theo dõi V8. Đó là người đầu tiên ở đó
Jeremy Gottfried

4
Tôi đoán là có tất cả mọi thứ trong vòng lặp đơn giản hóa biểu đồ phụ thuộc cho trình tối ưu hóa, sau đó có thể tạo ra mã tốt hơn. Một hồ sơ v8 có thể làm sáng tỏ thêm những gì đang diễn ra.
rustyx

Câu trả lời:


1

Mã của tôi đã xoay sở để rơi ra khỏi một vách đá hiệu suất đã biết trong công cụ JavaScript V8 ...

Các chi tiết của sự cố và cách khắc phục được mô tả trên bug.chromium.org

Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.