Hướng dẫn is r slower than python - r chậm hơn python

Làm thế nào dễ dàng để viết mã hiệu quả?

Ảnh của Áo Augusts trên unplash

TL; DR: Chỉ cần nhảy vào phần so sánh tổng thể

Nếu bạn là một nhà khoa học dữ liệu, rất có thể bạn lập trình trong Python hoặc R. nhưng có một đứa trẻ mới trong khối có tên Julia hứa hẹn hiệu suất giống như C mà không ảnh hưởng đến cách các nhà khoa học dữ liệu viết mã và tương tác với dữ liệu.

Trong bài đăng cuối cùng của tôi, tôi đã so sánh R với Julia, cho thấy Julia mang đến một tư duy lập trình làm mới cho cộng đồng khoa học dữ liệu. Điều quan trọng chính là với Julia, bạn không còn cần phải vector hóa để cải thiện hiệu suất. Trong thực tế, việc sử dụng tốt các vòng lặp có thể mang lại hiệu suất tốt nhất.my last post, I have compared R to Julia, showing how Julia brings a refreshening programming mindset to the Data Science community. The main takeaway is that with Julia, you no longer need to vectorize to improve performance. In fact, good use of loops might deliver the best performance.

Trong bài đăng này, tôi đang thêm Python vào hỗn hợp. Ngôn ngữ lựa chọn của các nhà khoa học dữ liệu có một lời để nói. Chúng tôi sẽ giải quyết một vấn đề rất đơn giản trong đó các triển khai tích hợp có sẵn và khi lập trình thuật toán từ đầu là đơn giản. Mục tiêu là để hiểu các lựa chọn của chúng tôi khi chúng tôi cần viết mã hiệu quả.

Kiểm tra thành viên thông qua tìm kiếm tuyến tính

Chúng ta hãy xem xét vấn đề thử nghiệm thành viên trên một vectơ số nguyên chưa được phân loại.

julia> 10 ∈ [71,38,10,65,38]
true
julia> 20 ∈ [71,38,10,65,38]
false

Về nguyên tắc, vấn đề này được giải quyết thông qua tìm kiếm tuyến tính. Thuật toán chạy qua các phần tử của vectơ đầu vào cho đến khi tìm giá trị được tìm kiếm (tìm kiếm thành công) hoặc đạt đến cuối vectơ (tìm kiếm không thành công). Mục tiêu là cho biết nếu một số nguyên nhất định là trong vector.in the vector.

Để đánh giá các triển khai khác nhau trong R, Python và Julia, tôi đã tạo một bộ dữ liệu với 1.000.000 số nguyên duy nhất từ ​​1 đến 2.000.000 và thực hiện 1.000 tìm kiếm với tất cả các số nguyên từ 1 đến 1.000. Xác suất tìm kiếm thành công là ~ 50%, do đó, một nửa số thuật toán sẽ quét vectơ hoàn chỉnh để kết luận rằng tìm kiếm không thành công. Trong các trường hợp còn lại, thuật toán phải yêu cầu (N+1)/2 đánh giá (trung bình) để tìm phần tử, với n là độ dài của vectơ.

Tôi đã đo hiệu suất của mỗi lần thực hiện bằng cách dành thời gian CPU trung bình là 3 lần chạy. Thông tin bổ sung về phần cứng và phần mềm để chạy thử nghiệm có thể được tìm thấy ở đây.

Xin lưu ý rằng mục tiêu của các thí nghiệm này là không tạo ra một điểm chuẩn chính xác của các ngôn ngữ và triển khai khác nhau. Mục tiêu là làm nổi bật các rào cản mà ngôn ngữ gây ra cho các nhà khoa học dữ liệu khi hiệu suất là quan trọng.

C thực hiện

Tôi đã triển khai tìm kiếm tuyến tính trong C để hiểu rõ về hiệu suất trên ngôn ngữ được gõ tĩnh và đặt đường cơ sở. Việc thực thi nhị phân mất 0,26 giây thời gian CPU để thực hiện 1.000 tìm kiếm.

R thực hiện

Tôi đã thử các hương vị khác nhau của thử nghiệm thành viên trong R, từ một nhà điều hành chuyên dụng (in) đến triển khai giống như C bằng cách sử dụng các vòng lặp, đi qua phương pháp vector hóa.

Khi chúng tôi chuyển từ in_search sang for_search, chúng tôi sẽ kiểm soát được thuật toán cao hơn. Tuy nhiên, khi sự kiểm soát tăng lên, hiệu suất làm suy giảm R., đó là một thứ tự nhanh hơn khi sử dụng hoạt động vector hóa, như trong vec_search, thực hiện quét mảng đầy đủ so với lặp qua các phần tử cho đến khi tìm thấy một trận đấu. Giống như trong bài viết trước của tôi, Vectorization đã được đền đáp mặc dù yêu cầu nhiều bộ nhớ và (dự phòng). Đúng như dự đoán, nhà điều hành chuyên dụng in có hiệu suất cao nhất và mã sạch hơn.

Tôi cũng đã thử các hoạt động giảm MAP, nhưng không có đủ kiên nhẫn để đợi cho đến khi họ hoàn thành không phải là một tùy chọn nếu bạn tìm kiếm hiệu suất.

Thực hiện Python

Thành thật mà nói, mục tiêu ban đầu là chỉ sử dụng các hàm gốc và cấu trúc dữ liệu gốc, nhưng toán tử in chậm hơn ~ 10 lần so với R khi sử dụng danh sách gốc của Python. Vì vậy, tôi cũng bao gồm kết quả với các mảng numpy (mang lại các hoạt động vector hóa cho Python). Thời gian CPU đã tăng từ 9,13 đến 0,57 giây, khoảng 2 lần đường cơ sở. Tuy nhiên, khi chuyển sang cách tiếp cận vòng lặp, các danh sách bản địa đã có lợi thế theo một thứ tự cường độ, tôi đã cho Numpy một cơ hội thứ hai bằng cách thêm tổng hợp JIT với gói numba. Có những hạn chế trong numba nhưng nó rất đơn giản để sử dụng: bạn chỉ cần bao gồm gói numba và gắn thẻ các chức năng bạn muốn thấy JIT được biên dịch (và đọc cẩn thận hướng dẫn). Các vòng lặp với Numpy Plus Numba cung cấp hiệu suất tương đương (hoặc tốt hơn) so với các hoạt động vector hóa/chuyên môn, nhưng đến đây không dễ dàng vì có một số bẫy trên đường. Nói về bẫy, thực hiện các vòng lặp trong danh sách bản địa với Numba đã gây thất vọng một lần nữa, tôi đã ngừng thực hiện vì mất hơn 5 phút để hoàn thành.

Julia thực hiện

Ở Julia, tôi đã bao gồm một vài hương vị hơn để thể hiện sự đa dạng và hiệu suất của các chức năng tự nhiên.

Ngoại trừ các hoạt động vector hóa, hiệu suất khá gần với việc thực hiện trong C, với sự xuống cấp từ 20%-50%. Hiệu suất vectorized là khá, khoảng 4 lần thời gian CPU CPU, nhưng cũng có khoảng 2 lần thời gian CPU 2X Numpy trên các hoạt động vectơ. Sự tự do mà bạn nhận được là không thể tin được, vì bạn có thể mã hóa hầu như bất kỳ thuật toán nào trong Julia! Để đạt được hiệu suất hàng đầu trên các vòng lặp, tôi đã sử dụng các gợi ý để nói với trình biên dịch không kiểm tra xem các chỉ mục có nằm trong giới hạn của mảng không (macro ____10) và để nói với trình biên dịch nó có thêm tự do theo thứ tự nó thực thi các lần lặp (

map(line -> parse(Int, line), eachline(f))
1 macro). Trong trường hợp bạn đang tự hỏi, không cung cấp những gợi ý này sẽ mang lại hiệu suất của các vòng lặp gần với in_search.

So sánh tổng thể

.

Tìm kiếm kết quả cạnh nhau cho vấn đề đơn giản này, chúng tôi quan sát rằng:

  • Hiệu suất của Julia, gần với C gần như độc lập về việc thực hiện;
  • Ngoại lệ trong Julia là khi viết mã vector hóa giống R, với hiệu suất xuống cấp khoảng 3 lần;
  • Khi thêm phần tổng hợp JIT (NUMBA) vào Python, việc triển khai dựa trên vòng lặp đã tiến gần đến hiệu suất của Julia; Vẫn còn tê liệt áp đặt các ràng buộc đối với mã Python của bạn, làm cho tùy chọn này trở thành một sự thỏa hiệp;
  • Trong Python, hãy chọn tốt giữa danh sách gốc và mảng numpy và khi nào nên sử dụng numba: đối với ít kinh nghiệm thì không rõ ràng là cấu trúc dữ liệu tốt nhất (thông minh hiệu suất) và không có người chiến thắng rõ ràng (đặc biệt là nếu bạn bao gồm việc sử dụng trường hợp thêm các yếu tố động, không được bảo hiểm ở đây);
  • R không phải là nhanh nhất, nhưng bạn nhận được một hành vi nhất quán so với Python: lần thực hiện chậm nhất trong R chậm hơn ~ 24 lần so với nhanh nhất, trong khi ở Python là ~ 343x (trong Julia là ~ 3x);
  • Bản địa r luôn hoạt động tốt hơn python bản địa;
  • Bất cứ khi nào bạn không thể tránh được vòng lặp trong Python hoặc R, vòng lặp dựa trên phần tử sẽ hiệu quả hơn so với vòng lặp dựa trên chỉ số.

Chi tiết vấn đề…

Tôi có thể dừng bài viết ngay tại đây và viết sự liền mạch như thế nào khi viết mã hiệu quả trong Julia. Tuy nhiên, chi tiết quan trọng, và lập trình viên cần phải chú ý đến nội bộ của Julia. Bạn có thể đoán được dòng mã ảnh hưởng đến hiệu suất nhất là gì không? Đây là một gợi ý: bạn sẽ không tìm thấy nó trong bất kỳ đoạn trích nào được trình bày trước khi

Đây là:

map(line -> parse(Int, line), eachline(f))

Dòng mã này phân tích tệp văn bản đầu vào F, chứa một số trên mỗi dòng (lưu ý rằng đọc tệp không phải là một phần của điểm chuẩn). Vì vậy, điều gì đặc biệt về dòng mã này? Tóm lại, Julia Infers rằng:

  • Loại phần tử được trả về bởi hàm ẩn danh (đối số đầu tiên của
    map(line -> parse(Int, line), eachline(f))
    3) là (luôn luôn) số nguyên;
  • Do đó, đầu ra của ánh xạ là một loạt các số nguyên.

Vì Julia biết rằng đang lưu trữ một loạt các số nguyên, nên nó phân bổ một khối bộ nhớ liên tục trong đó mỗi mục chứa một số nguyên. Điều này cho phép các hoạt động đọc hiệu quả.

Làm thế nào chúng ta có thể gây rối? Đây là một cách:

a = []
for line in eachline(f)
push!(a, parse(Int, line))
end

Có vẻ tương tự, phải không? Tuy nhiên:

> typeof(a)
Array{Any,1}

Câu

map(line -> parse(Int, line), eachline(f))
4, thuận tiện như vẻ ngoài của nó, tạo ra một mảng
map(line -> parse(Int, line), eachline(f))
5, có nghĩa là bạn có thể lưu trữ bất kỳ loại dữ liệu nào trên mỗi phần tử của mảng. Trong nội bộ, Julia lưu trữ một loạt các con trỏ trong bộ nhớ để gắn kết với tính linh hoạt mà
map(line -> parse(Int, line), eachline(f))
5 cung cấp. Do đó, Julia không còn có thể xử lý một khối bộ nhớ liên tục tuần tự khi xử lý mảng. Tác động đến hiệu suất là gì? Chậm hơn khoảng 50 đến 100 lần!bout 50 to 100 times slower!

Khắc phục mã này sẽ khá đơn giản:

map(line -> parse(Int, line), eachline(f))
7 (thay vì
map(line -> parse(Int, line), eachline(f))
4) sẽ thực hiện công việc vì nó chỉ định loại yếu tố.

Điểm mấu chốt

Từ tất cả các ngôn ngữ được đề cập trong bài viết này, Julia rõ ràng là dễ dàng nhất để viết mã hiệu quả. Tuy nhiên, bạn cần biết những gì bạn đang làm. May mắn thay, các mẹo hiệu suất có sẵn có thể đưa bạn đi đúng hướng.

Mã hóa hạnh phúc!

P.S. Tôi sử dụng cả ba ngôn ngữ thường xuyên và tôi yêu tất cả chúng. Mỗi người có vị trí của nó.

Tại sao R rất chậm?

Có rất nhiều chi phí trong quá trình xử lý vì R cần kiểm tra loại biến gần như mỗi khi nó nhìn vào nó.Điều này giúp bạn dễ dàng thay đổi các loại và tái sử dụng tên biến, nhưng làm chậm tính toán cho các tác vụ rất lặp đi lặp lại, như thực hiện một hành động trong một vòng lặp.R needs to check the type of a variable nearly every time it looks at it. This makes it easy to change types and reuse variable names, but slows down computation for very repetitive tasks, like performing an action in a loop.

Ngôn ngữ R có chậm không?

Mặc dù R chậm so với các ngôn ngữ lập trình khác, đối với hầu hết các mục đích, nhưng nó đủ nhanh.R is slow compared to other programming languages, for most purposes, it's fast enough.

R tốt hơn Python ở bất cứ điều gì?

Vì nhiều người sử dụng R để phân tích thống kê và số, nó cung cấp hỗ trợ và thư viện tốt hơn Python.it provides better support and libraries than Python.

Tại sao R khó hơn Python?

Học đường cong Nếu bạn có nền tảng về số liệu thống kê, mặt khác, R có thể dễ dàng hơn một chút.Nhìn chung, cú pháp dễ đọc của Python mang đến cho nó một đường cong học tập mượt mà hơn.R có xu hướng có một đường cong học tập dốc hơn ngay từ đầu, nhưng một khi bạn hiểu cách sử dụng các tính năng của nó, nó sẽ dễ dàng hơn đáng kể.R tends to have a steeper learning curve at the beginning, but once you understand how to use its features, it gets significantly easier.