Is Ruby 2.3 Faster? Nested Iterator Performance
http://ruby-performance-book.com/blog/2016/01/is-ruby-2-3-faster-nested-iterator-performance.html Ruby 2.3 đã được phát hành vào cuối năm ngoái với một loạt các cải tiến về hiệu suất. Nhưng nó có thực sự nhanh hơn so với 2.2? Chúng ta hãy cùng xem những khía cạnh sau. Đây là bài viết đầu ...
http://ruby-performance-book.com/blog/2016/01/is-ruby-2-3-faster-nested-iterator-performance.html
Ruby 2.3 đã được phát hành vào cuối năm ngoái với một loạt các cải tiến về hiệu suất. Nhưng nó có thực sự nhanh hơn so với 2.2? Chúng ta hãy cùng xem những khía cạnh sau.
Đây là bài viết đầu tiên trong các loạt bài về hiệu suất của Ruby 2.3. Lần này chúng ta sẽ xem xét về hiệu suất của các vòng lặp lồng nhau (nested iterator).
Điều gì sai với vòng lặp lồng nhau?
Hầu hết mọi người đều không biết về vấn đề này, nhưng một số vòng lặp tạo ra thêm các objects phụ (extra objects). Ví dụ, mỗi lần gọi đến phương thức Array#each_with_index sản xuất thêm hai đối tượng Ruby khác. Điều này nghe có vẻ không phải là một vấn đề lớn cho đến khi bạn sử dụng nó trong một vòng lặp. Hãy nhìn vào ví dụ dưới đây:
GC.disable before = ObjectSpace.count_objects Array.new(10000).each do |i| [0,1].each_with_index do |j, index| end end after = ObjectSpace.count_objects puts "# of arrays: %d" % (after[:T_ARRAY] - before[:T_ARRAY]) puts "# of extra Ruby objects: %d" % (after[:T_NODE] - before[:T_NODE])
Có bao nhiêu objects mà ví dụ trên sẽ tạo ra? Dĩ nhiên, nó cấp phát 10001 arrays. Nhưng, đáng ngạc nhiên bạn cũng sẽ thấy 20000 objects được tạo ra khi bạn chạy file trên với Ruby 2.2 hoặc với các phiên bản về trước.
> ruby array_investigation.rb # of arrays: 10001 # of extra Ruby objects: 20000
Điều trên có nghĩa là vòng lặp each_with_index đã cấp phát thêm hai extra objects mỗi lần mà bạn gọi đến nó. Hãy xem tại sao tại đoạn trích của cuốn sách Ruby Performance Optimization và Ruby Performance Optimization.
Điều này là tồi tệ cho hiệu suất. Extra objects thêm vào nhiều công việc hơn cho bộ thu gom rác thải và sẽ làm chậm ứng dụng của bạn.
Tôi đã nhìn thấy điều này trong mã code của tôi khi vòng lặp lồng nhau tưởng như vô hại lại add thêm 1.5 giây vào thời gian thực thi bởi vì các chu kì GC bổ sung.
Đâu là vòng lặp xấu?
Bạn có thể tìm thấy sự so sánh về hiệu suất của các vòng lặp trong đoạn trích của cuốn sách Ruby Performance Optimization. Nhưng đây là những cái vi phạm thường sử dụng nhiều nhất:
Iterator | Array | Range | Hash |
---|---|---|---|
all? | 3 | 3 | 3 |
any? | 2 | 2 | 2 |
each_with_index | 2 | 2 | 2 |
find | 2 | 2 | 2 |
map | 0 | 1 | 1 |
Vậy Ruby 2.3 nhanh hơn?
Ngay sau khi Ruby 2.3 được phát hành, đọc giả của tôi đã nói với tôi rằng ví dụ về each_with_index như trên đã không tạo ra thêm object nào khi chạy với Ruby 2.3.
Vòng lặp đã được tối ưu? Không, họ đã không làm. Ruby 2.3 trong nội bộ vẫn tạo ra các đối tượng. Nó chỉ là kiểu của đối tượng thay đổi từ T_NODE sang T_IMEMO. Vì vậy mã code dưới đây sẽ phơi bày những hành vi lặp xấu với Ruby 2.3:
GC.disable before = ObjectSpace.count_objects MEMO_OBJECT_TYPE = (RUBY_VERSION >= '2.3.0') ? :T_IMEMO : :T_NODE Array.new(10000).each do |i| [0,1].each_with_index do |j, index| end end after = ObjectSpace.count_objects puts "# of arrays: %d" % (after[:T_ARRAY] - before[:T_ARRAY]) puts "# of extra Ruby objects: %d" % (after[MEMO_OBJECT_TYPE] - before[MEMO_OBJECT_TYPE])
> ruby array_investigation.rb # of arrays: 10001 # of extra Ruby objects: 20000
Nếu bạn quan tâm đến chi tiết, hãy nhìn vào commit trong Ruby source code này
=> Dự đoán: Không nhanh hơn
Một số vòng lặp tiếp tục tạo các đối tượng bổ sung. Việc sử dụng chúng trong các vòng lặp lồng nhau sẽ làm chậm code của bạn.