Trình biên dịch LISP / Scheme chất lượng để cạnh tranh với C / C ++


8

Về mặt lý thuyết, có thể có trình biên dịch Lisp / Scheme có thể tạo mã có thể cạnh tranh với C được biên dịch không, giả sử trong phạm vi 15-25%?

Trong thử nghiệm của tôi, tôi đã phát hiện ra rằng cây trồng hiện tại của trình biên dịch (Bigloo, SBCL, Gambit, gà, vv) là chậm hơn so với mã C tương đương với 20-50 lần .

Ngoại lệ duy nhất là trình biên dịch Stalin . Đối với các chương trình đơn giản, nó tạo ra các nhị phân tương đương với C. Tuy nhiên, điều tôi nghi ngờ là không có dự án nào khác (Bigloo, Chicken, Clozure, v.v.) đã cố gắng thực hiện bất kỳ thủ thuật nào mà Stalin sử dụng ("tối ưu hóa toàn bộ chương trình", Vân vân).

Tôi là một fan hâm mộ lớn của LISP từ giữa những năm 90 và rất thích mang nó lên máy bay để nhóm của tôi có thể hoàn thành các dự án trong một nửa thời gian thường sử dụng C / C ++ /. NET / etc, nhưng ... hiệu suất vấn đề là một rào cản lớn.

Tôi tự hỏi nếu thiếu trình biên dịch LISP chất lượng là do thực tế là không có thời gian và tiền bạc nghiêm trọng nào được đầu tư vào chủ đề HOẶC nếu điều này đơn giản không phải là một nhiệm vụ khả thi với tình trạng công nghệ trình biên dịch hiện nay ??


1
Tôi tin rằng bạn đã thử nghiệm các trình biên dịch với Common Lisp (declare (optimize ...)), (declare (<type> <var))(the <type> <expr>)trong các chức năng của bạn? Nếu không, đó không phải là một so sánh công bằng :)
Jakub Lédl

1
Tôi nghĩ cs.stackexchange.com/questions/842/ Câu trả lời cho câu hỏi này.
Kyle Jones

@KyleJones Có phải không? Tôi đoán là với tối ưu hóa tối đa, Common Lisp có thể nhận được trong phạm vi được chỉ định bởi OP, nếu không gần hơn.
Jakub Lédl

Chỉ cần thay đổi ngôn ngữ lập trình sẽ không bao giờ khiến nhóm của bạn tạo ra mã chính xác gấp bốn lần so với cùng một lúc. Những nghiên cứu đã chỉ ra rằng các lập trình viên có kinh nghiệm trong ngôn ngữ hóa ra số lượng dòng mã tương đương trên mỗi đơn vị thời gian cho độ phức tạp của vấn đề cố định, không phụ thuộc vào ngôn ngữ. Vì vậy, bạn sẽ không đạt được bất cứ điều gì trừ khi trong khu vực có vấn đề của bạn, các chương trình LISP ngắn hơn nhiều. Một điều khác cần xem xét là bạn phải có được những người có kinh nghiệm trong LISP để phát triển và bảo trì. Và những người ở xa ở giữa.
vonbrand

1
Dường như với tôi rằng câu hỏi này đòi hỏi nhiều kinh nghiệm lập trình hơn là câu trả lời khoa học. Do đó, đây có thể là trang web sai cho câu hỏi.
Raphael

Câu trả lời:


7

Như đã lưu ý trong các nhận xét, không chính xác khi tuyên bố "vụ mùa hiện tại của trình biên dịch (Bigloo, SBCL, Gambit, Chicken, v.v.) chậm hơn 20-50 lần so với mã C tương đương", mà không đủ điều kiện bạn đã kiểm tra và những gì bạn đã kiểm tra bạn đã thử nghiệm

Đối với việc sử dụng của tôi , tôi thấy rằng đối với nhiều thứ, Gambit và Chicken Scheme khá gần với mã 'C' tương đương về tốc độ, với phiên bản hiện tại của Gambit (4.7.3) thường nhanh hơn Gà (4.9.0.1) nhưng hơn trước tối ưu hóamã 'C' đầu ra (đưa ra các giả định về số lượng thanh ghi có sẵn - giả sử x686 - và buộc sử dụng bộ nhớ ngăn xếp cho bất kỳ yêu cầu bộ nhớ bổ sung nào, mà các quyết định nên để lại cho trình biên dịch 'C' như Chicken, thường loại bỏ yêu cầu về các thanh ghi bổ sung và kết hợp các bước xử lý) để ngăn trình biên dịch 'C' tự tối ưu hóa dẫn đến các vòng lặp rất nhỏ bị chậm đến khoảng hai lần so với các vòng lặp nhỏ chặt chẽ đó trong 'C' (hoặc Gà ); Chicken chỉ định nghĩa nhiều biến cục bộ cho một hàm nhất định khi nó thấy phù hợp (chủ yếu được sử dụng một cách bất biến) và sau đó phụ thuộc vào trình biên dịch để tối ưu hóa hầu hết các biến đó. Gà không '

EDIT_ADD: Tôi đã thực hiện một số nghiên cứu thêm về các phiên bản Lược đồ Gà và Gambit-C và đã tìm thấy như sau:

  1. Gà nhanh hơn Gambit cho các vòng lặp nhỏ hẹp vì lý do trên (phụ thuộc nhiều hơn vào trình biên dịch 'C' để tối ưu hóa mà không cần thực hiện nhiều và do đó tận dụng tốt hơn các thanh ghi phụ x86-64), nhưng cũng vì nó không bao gồm kiểm tra bảo trì ngăn xếp "POLL" bên trong các vòng lặp, trong khi Gambit bao gồm kiểm tra "POLL" bên trong vòng lặp. Ngay cả khi điều này không được kích hoạt (trường hợp thông thường), sẽ mất vài chu kỳ xung nhịp CPU để xác định rằng không có gì là bắt buộc (khoảng 6 chu kỳ). Một trình biên dịch thông minh hơn trong tương lai có thể thấy rằng không cần thực hiện kiểm tra ngăn xếp khi bên trong một vòng lặp chặt chẽ và không thực hiện các hoạt động xây dựng ngăn xếp, thực hiện ngay trước hoặc sau vòng lặp và tiết kiệm thời gian này.

  2. Macro 'G' của Gambit làm quá nhiều việc, như đã nói, trong việc xác định chính xác cách thức các hoạt động nên được thực hiện, đặc biệt là bao gồm các hoạt động kích thước ngăn xếp cố định và những trình biên dịch 'C' khó có thể tối ưu hóa hơn; sử dụng các thanh ghi hiệu quả hơn có thể giảm thời gian vòng lặp chặt chẽ có lẽ bằng 4 chu kỳ, kết hợp với những điều trên sẽ đưa nó vào sân bóng gà.

  3. Không tối ưu hóa "đọc / sửa đổi / ghi" đầu ra cho các hoạt động vectơ được sửa đổi tại chỗ và không xuất mã để trình biên dịch thực hiện. Một phụ trợ thông minh hơn như LLVM khi được sử dụng với Haskell thực hiện loại điều này. Điều này sẽ làm giảm việc sử dụng một thanh ghi và thời gian thực hiện chỉ bằng một lệnh duy nhất thay vì đọc riêng, tính toán sửa đổi và ghi vào cùng một vị trí; điều này sẽ trở thành một phép tính sửa đổi (nói một chút hoặc), và sau đó đọc một sửa đổi (| =) viết một lệnh. Điều này có thể làm cho tốc độ nhanh hơn theo chu kỳ hoặc hơn

  4. Cả hai đều được gõ động và xử lý các bit "thẻ" dữ liệu như là một phần của dữ liệu của chúng. Không đủ thông minh cho các vòng lặp chặt chẽ để giảm các thẻ, thực hiện vòng lặp "không có thẻ", sau đó thêm các thẻ trở lại cho bất kỳ kết quả nào từ vòng lặp, cũng không tạo ra mã mà trình biên dịch 'C' có thể thấy để làm điều này mặc dù nó kết hợp các hoạt động này cho một số trường hợp. Tối ưu hóa ở đây có thể giảm thời gian vòng lặp bằng một vài chu kỳ CPU hoặc hơn, tùy thuộc vào vòng lặp.

  5. Các vòng lặp 'C' rất chặt chẽ có thể mất khoảng 3,5 chu kỳ xung nhịp CPU trên mỗi vòng lặp trên CPU nhanh không phải là bộ nhớ tốc độ truy cập bộ nhớ cache (như AMD Bulldozer, tốc độ chậm gấp đôi); vòng lặp tương tự trong Gà hiện mất khoảng 6 chu kỳ và Gambit mất khoảng 16,9 chu kỳ. Với tất cả các tối ưu hóa theo quy định ở trên, không có lý do gì mà các triển khai Đề án này không thể làm được điều đó, tuy nhiên một số công việc được yêu cầu:

  6. Trong trường hợp của Gambit, công việc khó hơn có thể là cải thiện phân tích dòng chảy để nhận ra khi không cần kiểm tra "POLL" (nghĩa là điều này có thể bị gián đoạn, mặc dù trình biên dịch không cho phép ngắt để sử dụng? ); cải tiến dễ dàng hơn là chỉ thực hiện việc sử dụng thanh ghi tốt hơn (ví dụ: nhận ra các thanh ghi x86-64 tốt hơn là kiến ​​trúc x686 mặc định). Đối với cả hai, phân tích luồng tốt hơn để nhận ra rằng họ có thể "gỡ" dữ liệu, đặc biệt là "fixnum", "flonum" và vector, dữ liệu để các thao tác này không cần thiết trong các vòng lặp chặt chẽ và kết hợp các hướng dẫn đọc / sửa đổi / ghi. Cả hai kết thúc này có thể được thực hiện bằng cách sử dụng một phụ trợ tốt hơn như LLVM (không phải là một lượng công việc không đáng kể, nhưng cả hai đều đã có một phần ở đó).

Kết luận: Gà hiện chậm hơn khoảng 50% so với 'C' trên CPU nhanh nhất (không phải là Bulldozer của tôi, với tốc độ tương đương do điều chỉnh bộ đệm của mã 'C') và Gambit chậm hơn khoảng 400% (chỉ khoảng Chậm hơn 125% trên chiếc Bulldozer chậm của tôi). Tuy nhiên, các cải tiến trong tương lai của trình biên dịch có thể làm giảm điều này để một trong hai mã không chậm hơn 'C' hoặc trong phạm vi mà OP chỉ định.

Một ngôn ngữ phức tạp hơn như Haskell, khi sử dụng phụ trợ LLVM, chú ý cẩn thận đến việc sử dụng nghiêm ngặt (không phải là vấn đề với lược đồ luôn luôn háo hức theo mặc định) và sử dụng các cấu trúc dữ liệu phù hợp (mảng ST không được đóng hộp thay vì liệt kê các vòng lặp chặt chẽ; phần nào có thể áp dụng cho Lược đồ sử dụng vectơ), chạy ở tốc độ tương đương với 'C' nếu phần phụ trợ LLVM được sử dụng với tối ưu hóa đầy đủ. Nếu nó có thể làm điều này, thì Scheme cũng có thể cải tiến trình biên dịch ở trên.

LẠI, không có cách nào mà một trong hai điều này chậm hơn 20 đến 50 lần khi được sử dụng với các cờ tối ưu hóa phù hợp. END_EDIT_ADD

Tất nhiên, tất cả các điểm chuẩn đều không hợp lệ nếu một người không sử dụng các cài đặt tối ưu hóa phù hợp cho từng người trong môi trường sản xuất ...

Tôi nghĩ rằng trình biên dịch thương mại Chez Scheme sẽ ở trong sân bóng để tạo ra hiệu suất cao như Gambit và Chicken, vì là thương mại, nó chắc chắn có một số "thời gian và tiền bạc nghiêm túc đầu tư vào nó".

Cách duy nhất tôi có thể khiến Gambit hoặc Chicken chạy chậm như "chậm hơn 20 đến 50 lần so với 'C'" là không sử dụng bất kỳ cài đặt tối ưu hóa nào, trong trường hợp chúng thường không chạy nhanh hơn nhiều so với giải thích trong REPL của chúng - Chậm hơn 10 lần so với sử dụng đúng các cài đặt đó.

Có thể OP đã không kiểm tra bằng cách sử dụng các cài đặt này đúng cách?

Nếu OP quan tâm làm rõ các quy trình thử nghiệm của mình, tôi sẽ sẵn sàng chỉnh sửa câu trả lời này để cho thấy rằng ít nhất Gambit và Gà không cần phải chậm như vậy.


Đây có phải là kiểm tra loại thời gian chạy bị vô hiệu hóa? Tôi sẽ không muốn làm điều đó trong sản xuất, vì nó làm cho các lỗi có thể khai thác mà trước đây không có.
Demi

@Demetri, với hầu hết các trình biên dịch Scheme như Gambit-C, Chicken hoặc Bigloo, người ta tăng tốc độ gấp ba lần cho nhiều điểm chuẩn bằng cách vô hiệu hóa tất cả các kiểm tra thời gian chạy, nhưng mã vẫn không "chậm hơn 20 đến 50 lần" như đã nêu của câu hỏi OP. Trên thực tế, nhiều kiểm tra trong số này có thể bị vô hiệu hóa một cách an toàn trong mã sản xuất sau khi kiểm tra các vấn đề trong việc gỡ lỗi mà không gặp rủi ro bằng cách viết mã để các kiểm tra đó chỉ được tích hợp vào mã khi cần thiết.
GordonBood

@Demetri Tôi có thể chứng thực sơ đồ gà đang ở trong sân bóng chậm hơn 1,5-2,5 lần so với C đối với mã điểm chuẩn, nếu được biên dịch với tối ưu hóa. Nhưng vâng, nó chậm khủng khiếp nếu được biên dịch mà không tối ưu hóa. FWIW Tôi nhận được kết quả tốt nhất từ ​​việc sử dụng fixnum-op, các đối tượng không có hộp và cho phép biên dịch khối, tức là phân tích tĩnh tốt hơn dẫn đến tối ưu hóa tốt hơn.
Morten Jensen

Tôi lo lắng hơn về kiểm tra an toàn thời gian chạy - Tôi sẽ không muốn một lỗi không xuất hiện trong thử nghiệm là lỗi tràn bộ đệm có thể khai thác. Rõ ràng người ta sẽ bật tối ưu hóa.
Demi

@Demetri có thể hiểu được. Kinh nghiệm của tôi là chi phí kiểm tra thời gian chạy phụ thuộc rất nhiều vào loại mã bạn viết. Đôi khi chi phí hoạt động cao hơn gấp 10 lần so với việc chạy mà không kiểm tra trong thử nghiệm của tôi.
Morten Jensen
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.