12/08/2018, 13:45

Tối ưu hiệu suất Ruby

1. Mở đầu Không phải bàn cãi nhiều, ai trong chúng ta cũng biết rằng Ruby là một ngôn ngữ tuyệt vời, nó giúp cho việc xây dựng nên một ứng dụng web trở nên đơn giản và nhanh chóng hơn bao giờ hết. Nhưng song song với điều đó, luôn có ý kiến cho rằng các ứng dụng viết bằng ngôn ngữ Ruby (hay các ...

1. Mở đầu

Không phải bàn cãi nhiều, ai trong chúng ta cũng biết rằng Ruby là một ngôn ngữ tuyệt vời, nó giúp cho việc xây dựng nên một ứng dụng web trở nên đơn giản và nhanh chóng hơn bao giờ hết. Nhưng song song với điều đó, luôn có ý kiến cho rằng các ứng dụng viết bằng ngôn ngữ Ruby (hay các Framwork có nền tảng Ruby) thường chạy chậm hơn các ứng dụng mà sử dụng các ngôn ngữ lập trình khác. Bài viết này sẽ giúp các bạn hiểu được vì sao Ruby lại chậm, và làm thế nào để cải thiện điều đó.

2. Vì sao codes viết bằng Ruby lại chậm ?

Chắc hẳn việc đầu tiên chúng ta nghĩ tới việc ứng dụng chạy chậm chạp đó là do việc xây dựng các thuật toán phức tạp, sử dụng nhiều vòng lặp lồng nhau ... Thông thường giải pháp được đưa ra trong trường hợp này sẽ là cấu trúc lại code, xác định khoanh vùng những phần bị chậm, nhận định nguyên nhân và viết lại đoạn code đó giống như việc tránh các nút thắt cổ chai vậy. Và chúng ta cứ lặp đi lặp lại điều đó cho đến khi ứng dụng trở nên nhanh hơn.

Giải pháp trên dường như khá là hợp lý, tuy nhiên đối với Ruby code đôi lúc lại không áp dụng được. Thuật toán phức tạp có thể là nguyên nhân chính dẫn đến các vấn đề về performance, nhưng có một nguyên nhân nữa mà các developer thường bỏ qua. Hãy xem ví dụ dưới đây:

require 'benchmark'

rows = 30000
cols = 10

data = Array.new(rows){Array.new(cols){"test" * 1000 }}

time = Benchmark.realtime do
csv = data.map{ |row| row.join(",")}.join("
")
end
puts time.round(2)

Nguyên nhân đầu tiên mà tôi muốn nói đến là việc lựa chọn phiên bản ruby sử dụng trong ứng dụng cũng ảnh hưởng đến performance. Cụ thể là mỗi phiên bản ruby sẽ thực thi đoạn code trên nhanh chậm khác nhau.

1.9.3 2.1.0 2.1.5 2.3.0
18.69 10.12 8.22 6.23

Bạn có thể thấy ruby version cũ thực thi mất nhiều thời gian nhất, các version mới hơn có vẻ khả quan, tuy nhiên thử xét xem một chương trình khá đơn giản - tạo ra 1 file csv với 30000 và 10 cột mất tận hơn 10s, với ruby 2.3.0 là hơn 6s tuy nhiên vẫn quá chậm. Vậy tại sao thực thi chương trình trên lại tốn thời gian đến vậy ?

Hãy cùng nhìn lại đoạn code thêm một lần nữa, nó chỉ là 2 vòng lặp lồng nhau, không có gì đặc biệt cả. Làm sao để chương trình trên nhanh hơn đây? Hãy cùng thử chạy chương trình trên và disable cơ chế garbage collection

require 'benchmark'

rows = 30000
cols = 10

data = Array.new(rows){Array.new(cols){"test" * 1000 }}
GC.disable  # Insert this line

time = Benchmark.realtime do
csv = data.map{ |row| row.join(",")}.join("
")
end
puts time.round(2)

Và đây là kết quả thu được

1.9.3 2.1.0 2.1.5 2.3.0
GC Enabled 18.69 10.12 8.22 6.23
GC Disabled 6.58 5.0 4.04 3.43
Thời gian để GC 65% 51% 51% 45%

Giờ thì bạn đã thấy lí do khiến cho code chở nên chậm chạp là gì rồi chứ? Chương trình của chúng ta dành phần lớn thời gian để thực hiện thu gom "rác". Thật đáng ngạc nhiên đúng không, đặc biệt là với những ai trước đó đã làm việc với C# hay java, cũng là những ngôn ngữ có cơ chế GC thì có lẽ sẽ cảm thấy khá choáng với điều này, đúng là khó tin nhưng ... thật.

Vậy tóm lại thì nguyên nhân chính là do code của chúng ta sử dụng quá nhiều tài nguyên bộ nhớ, hay là cơ chế GC của Ruby quá chậm ? Câu trả lời là cả 2 điều trên.

Tốn tài nguyên bộ nhớ thì vốn là đặc trưng của Ruby rồi, cái này là do đặc thù của ngôn ngữ. "Mọi thứ đều là Object" nghĩa là chương trình cần thêm bộ nhớ để cấp cho các đối tượng Ruby này. Ngoài ra thì cái cơ chế garbage collection trên Ruby chậm, cũng là vấn đề thiên cổ của các version ruby từ trước đến nay (dĩ nhiên từ 2.2.2 trở đi thì cũng có cải thiện hơn thật, không phủ nhận điều đó). Không những cái thuật toán đánh dấu, quét, ngăn chặn GC nó chậm, mà nó còn dừng luôn cả ứng dụng trong thời gian GC chạy             </div>
            
            <div class=

0