Parallel versus sequential assignment is microbenchmark (not representative) and scales with number of inputs

Đang mở
#208 7 bình luận 1 reaction 0 người được giao Xem trên GitHub

Chưa có ai nhận issue này.

Đánh giá

Độ khó
4/5
Thời gian dự kiến
3-5 ngày
Mức phù hợp với người mới
42/100
Loại issue
Tài liệu
Độ rõ ràng
Khá rõ ràng
Mức độ hoạt động
Đình trệ
Công nghệ
ruby
Lĩnh vực
documentation, performance

Hướng nghiên cứu

Xem xét code/general/assignment.rb và phần trình bày README liên quan, sau đó so sánh các nhận định về benchmark với các ví dụ và phép đo trong issue này. Xác định xem benchmark có nên bị loại bỏ hoặc được chú thích chi tiết hay không, và xác minh rằng tài liệu sau cùng không còn trình bày phép so sánh này như một đại diện rộng rãi.

Do mô hình lập chỉ mục viết ra từ nội dung của issue.

Mô tả

Update: Parallel assignment doesn't allocate unless it's the last statement in a method/proc. There's a benchmark provided below. I'm

I think this benchmark https://github.com/fastruby/fast-ruby/blob/main/code/general/assignment.rb needs to be removed or at least heavily annotated on the readme. It is not representative of real code. In my experience, removing parallel assignments into sequential ones speeds up apps.

One reason is that parallel assignments [can] allocate an intermediate array [if it is the last statement in a method for example], the other is that the benchmark uses MANY assignments and the performance difference seems to scale with assignment numbers. In the wild the most common number is 2.

Also worth noting that by using integers, we're isolating to only the assignment time (mostly) but this distorts the possible impact of the code. If you switch to strings that allocate then the pressure on the GC actually causes sequential assignment to be faster (at least sometimes).

Benchmarks below.

Reasoning

In https://github.com/fastruby/fast-ruby/blob/main/code/general/assignment.rb it states that parallel assignment is much faster than sequential assignment, but if you have only 2 assignments, the difference is negligible:

require 'benchmark/ips'

def fast
  _a, _b= 1, 2
  nil
end

def slow
  _a = 1
  _b = 2
  nil
end

Benchmark.ips do |x|
  x.report('Parallel Assignment')   { fast }
  x.report('Sequential Assignment') { slow }
  x.compare!
end
Warming up --------------------------------------
 Parallel Assignment     1.494M i/100ms
Sequential Assignment
                         1.344M i/100ms
Calculating -------------------------------------
 Parallel Assignment     14.659M (± 4.9%) i/s -     73.229M in   5.007646s
Sequential Assignment
                         15.020M (± 4.0%) i/s -     75.261M in   5.019081s

Comparison:
Sequential Assignment: 15019526.2 i/s
 Parallel Assignment: 14658684.7 i/s - same-ish: difference falls within error

Note that sequential assignment is faster here (but within error). While the parallel assignment is fewer operations, it also introduces an intermediate array that requires an allocation:

$ cat scratch.rb
require 'memory_profiler'
report = MemoryProfiler.report do
  _a, _b = 1, 2
end

report.pretty_print
⛄️ 3.1.2 🚀 /tmp
$ ruby scratch.rb
Total allocated: 40 bytes (1 objects)
Total retained:  40 bytes (1 objects)

allocated memory by gem
-----------------------------------
        40  other

allocated memory by file
-----------------------------------
        40  scratch.rb

allocated memory by location
-----------------------------------
        40  scratch.rb:3

allocated memory by class
-----------------------------------
        40  Array
# ...

[Note that the above only works because the parallel assignment is returned _a, _b = 1, 2; nil does not allocate]

Even in the case of MANY sequential versus parallel assignments, if the function is doing anything non-trivial (such as allocating strings as opposed to using integers which are all singletons, then the effect of the "faster" parallel assignment is pretty much negligible. This run actually has sequential being faster:

require 'benchmark/ips'

def fast
  _a, _b, _c, _d, _e, _f, _g, _h = String.new("1"), String.new("2"), String.new("3"), String.new("4"), String.new("5"), String.new("6"), String.new("7"), String.new("8")
  nil
end

def slow
  _a = String.new("1")
  _b = String.new("2")
  _c = String.new("3")
  _d = String.new("4")
  _e = String.new("5")
  _f = String.new("6")
  _g = String.new("7")
  _h = String.new("8")
  nil
end

Benchmark.ips do |x|
  x.report('Parallel Assignment')   { fast }
  x.report('Sequential Assignment') { slow }
  x.compare!
end
Warming up --------------------------------------
 Parallel Assignment    80.318k i/100ms
Sequential Assignment
                        77.943k i/100ms
Calculating -------------------------------------
 Parallel Assignment    796.245k (± 3.7%) i/s -      4.016M in   5.050973s
Sequential Assignment
                        767.116k (± 5.9%) i/s -      3.897M in   5.099053s

Comparison:
 Parallel Assignment:   796245.3 i/s
Sequential Assignment:   767115.8 i/s - same-ish: difference falls within error

The sequential assignment is also faster here (but within error).

Ngôn ngữ chính
Ruby
Star
5.7k
Fork
370
Chỉ số merge pull request
Không có pull request nào được merge trong 30 ngày

Hướng dẫn đóng góp

Mở hướng dẫn đóng góp

Bắt đầu từ đâu

  1. Đọc hết issue, rồi đọc hướng dẫn đóng góp của dự án.
  2. Bình luận trên issue rằng bạn sẽ nhận — tránh hai người làm cùng một việc.
  3. Fork repository và làm thay đổi trên một nhánh.
  4. Mở pull request có tham chiếu số hiệu của issue.

Issue khác của fastruby/fast-ruby

Tất cả issue của fastruby/fast-ruby

Issue tương tự

Thêm issue về Ruby

Nhận issue mới trong hộp thư của bạn

Bản tóm tắt ngắn những issue GitHub phù hợp với người mới.