Tại sao tổng lại nhanh hơn nhiều so với tiêm (: +)?


129

Vì vậy, tôi đã chạy một số điểm chuẩn trong Ruby 2.4.0 và nhận ra rằng

(1...1000000000000000000000000000000).sum

tính toán ngay lập tức trong khi

(1...1000000000000000000000000000000).inject(:+)

mất nhiều thời gian đến nỗi tôi mới hủy bỏ hoạt động. Tôi đã có ấn tượng đó Range#sumlà một bí danh Range#inject(:+)nhưng có vẻ như điều đó không đúng. Vậy làm thế nào để sumlàm việc, và tại sao nó nhanh hơn nhiều inject(:+)?

NB Tài liệu cho Enumerable#sum(được thực hiện bởi Range) không nói gì về đánh giá lười biếng hoặc bất cứ điều gì dọc theo những dòng đó.

Câu trả lời:


227

Câu trả lời ngắn

Đối với một phạm vi số nguyên:

  • Enumerable#sum trả lại (range.max-range.min+1)*(range.max+range.min)/2
  • Enumerable#inject(:+) lặp đi lặp lại trên mọi yếu tố.

Học thuyết

Tổng các số nguyên giữa 1 và nđược gọi là số tam giác , và bằng n*(n+1)/2.

Tổng các số nguyên giữa nmlà số tam giác mtrừ đi số tam giác của n-1, bằng m*(m+1)/2-n*(n-1)/2và có thể được viết (m-n+1)*(m+n)/2.

Vô số # tổng trong Ruby 2.4

Thuộc tính này được sử dụng trong Enumerable#sumphạm vi số nguyên:

if (RTEST(rb_range_values(obj, &beg, &end, &excl))) {
    if (!memo.block_given && !memo.float_value &&
            (FIXNUM_P(beg) || RB_TYPE_P(beg, T_BIGNUM)) &&
            (FIXNUM_P(end) || RB_TYPE_P(end, T_BIGNUM))) { 
        return int_range_sum(beg, end, excl, memo.v);
    } 
}

int_range_sum trông như thế này:

VALUE a;
a = rb_int_plus(rb_int_minus(end, beg), LONG2FIX(1));
a = rb_int_mul(a, rb_int_plus(end, beg));
a = rb_int_idiv(a, LONG2FIX(2));
return rb_int_plus(init, a);

tương đương với:

(range.max-range.min+1)*(range.max+range.min)/2

sự bình đẳng nói trên!

Phức tạp

Cảm ơn rất nhiều đến @k_g và @ Hynek-Pichi-Vychodil cho phần này!

Tổng

(1...1000000000000000000000000000000).sum đòi hỏi ba bổ sung, một phép nhân, một phép con và phép chia.

Đó là một số lượng hoạt động không đổi, nhưng phép nhân là O ((log n) ²), Enumerable#sumO ((log n) ²) cho một phạm vi số nguyên.

tiêm

(1...1000000000000000000000000000000).inject(:+)

yêu cầu bổ sung 999999999999999999999999999998!

Ngoài ra là O (log n), Enumerable#injectO (n log n) cũng vậy.

Với 1E30đầu vào, injectkhông bao giờ trở lại. Mặt trời sẽ nổ tung từ lâu!

Kiểm tra

Thật dễ dàng để kiểm tra nếu Ruby Integers đang được thêm vào:

module AdditionInspector
  def +(b)
    puts "Calculating #{self}+#{b}"
    super
  end
end

class Integer
  prepend AdditionInspector
end

puts (1..5).sum
#=> 15

puts (1..5).inject(:+)
# Calculating 1+2
# Calculating 3+3
# Calculating 6+4
# Calculating 10+5
#=> 15

Thật vậy, từ enum.cý kiến:

Enumerable#sumphương pháp có thể không tôn trọng định nghĩa lại "+" phương thức của các phương thức như Integer#+.


17
Đây là một tối ưu hóa thực sự tốt vì tính toán tổng của một dãy số là không đáng kể nếu bạn sử dụng đúng công thức và không thể tin được nếu bạn lặp đi lặp lại. Nó giống như cố gắng thực hiện phép nhân như một chuỗi các hoạt động bổ sung.
tadman

Vì vậy, việc tăng hiệu suất chỉ dành cho n+1phạm vi? Tôi chưa cài đặt 2.4 hoặc tôi sẽ tự kiểm tra nhưng các Đối tượng Vô số khác được xử lý bằng phép cộng cơ bản vì chúng sẽ bị inject(:+)trừ đi chi phí biểu tượng cho Proc.
kỹ sư

8
Độc giả, nhớ lại từ toán học trung học của bạn n, n+1, n+2, .., mtạo thành một chuỗi số học có tổng bằng (m-n+1)*(m+n)/2. Tương tự như vậy, tổng của một chuỗi hình học , n, (α^1)n, (α^2)n, (α^3)n, ... , (α^m)n. có thể được tính từ một biểu thức dạng đóng.
Cary Swoveland

4
\ started {nitpick} Số tổng # là O ((log n) ^ 2) và tiêm là O (n log n) khi số của bạn được phép không bị ràng buộc. \ end {nitpick}
k_g

6
@EliSadoff: Nó có nghĩa là những con số thực sự lớn. Nó có nghĩa là những con số không phù hợp với từ kiến ​​trúc tức là không thể được tính bằng một lệnh và một thao tác trong lõi CPU. Số lượng kích thước N có thể được mã hóa bằng các bit log_2 N, do đó, phép cộng O (logN) và phép nhân là O ((logN) ^ 2) nhưng có thể là O ((logN) ^ 1.585) (Karasuba) hoặc thậm chí O (logN * log (logN) * ​​log (log (LogN)) (FFT).
Hynek -Pichi- Vychodil
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.