Toplam neden enjekte etmekten çok daha hızlı (: +)?


129

Ruby 2.4.0'da bazı karşılaştırmalar çalıştırıyordum ve şunu fark ettim:

(1...1000000000000000000000000000000).sum

hemen hesaplar oysa

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

o kadar uzun sürdü ki ameliyatı iptal ettim. Bunun Range#sumbir takma ad olduğu izlenimi altındaydım Range#inject(:+)ama bu doğru değil gibi görünüyor. Peki nasıl sumçalışır ve neden bundan çok daha hızlıdır inject(:+)?

NB Belgeleri Enumerable#sum(tarafından uygulanmaktadır Range) tembel değerlendirme veya bu doğrultuda herhangi bir şey söylemez.

Yanıtlar:


227

Kısa cevap

Bir tam sayı aralığı için:

  • Enumerable#sum İadeler (range.max-range.min+1)*(range.max+range.min)/2
  • Enumerable#inject(:+) her öğeyi yineler.

teori

1 ile arasındaki tam sayıların toplamına üçgen sayın denir ve buna eşittir .n*(n+1)/2

Arasında tamsayı toplamı nve müçgen sayısı meksi üçgen sayısı n-1eşittir, m*(m+1)/2-n*(n-1)/2ve yazılabilir (m-n+1)*(m+n)/2.

Ruby 2.4'te numaralandırılabilir # toplam

Bu özellik, Enumerable#sumtamsayı aralıklarında kullanılır:

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 buna benzer :

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);

bu şuna eşdeğerdir:

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

söz konusu eşitlik!

karmaşa

Bu bölüm için @k_g ve @ Hynek-Pichi-Vychodil'e çok teşekkürler!

toplam

(1...1000000000000000000000000000000).sum üç toplama, bir çarpma, bir çıkarma ve bir bölme gerektirir.

Bu sabit bir işlem sayısıdır, ancak çarpma O ((log n) ²), dolayısıyla Enumerable#sumbir tamsayı aralığı için O ((log n) ²).

iğne yapmak

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

999999999999999999999999999998 eklemeler gerektirir!

Toplama O (log n), yani Enumerable#injectO (n log n).

İle 1E30, girdi olarak injectasla geri ile. Güneş çok önceden patlayacak!

Ölçek

Ruby Tamsayılarının eklenip eklenmediğini kontrol etmek kolaydır:

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

Nitekim enum.cyorumlardan:

Enumerable#sumyöntem "+" gibi yöntemlerin yeniden tanımlanmasına saygı duymayabilir Integer#+.


17
Bu, gerçekten iyi bir optimizasyondur, çünkü doğru formülü kullanırsanız bir dizi sayının toplamını hesaplamak önemsizdir ve yinelemeli olarak devam ederseniz dayanılmazdır. Bu, çarpmayı bir dizi toplama işlemi olarak uygulamaya çalışmak gibidir.
tadman

Yani performans artışı n+1sadece aralıklar için mi? Yüklü 2.4'üm yok veya kendimi test edecektim, ancak inject(:+)proc için sembolün eksi eksi olacak şekilde temel eklemeyle işlenen diğer Numaralandırılabilir Nesnelerdir .
engineersmnky

8
Okuyucular, toplamı eşit olan n, n+1, n+2, .., mbir aritmetik diziyi oluşturan lise matematiğinizi hatırlayın (m-n+1)*(m+n)/2. Benzer şekilde, bir toplamı geometrik dizi , n, (α^1)n, (α^2)n, (α^3)n, ... , (α^m)n. kapalı formlu bir ifadeden hesaplanabilir.
Cary Swoveland

4
\ begin {nitpick} Numaralandırılabilir # toplamı O ((log n) ^ 2) ve sayılarınızın sınırsız olmasına izin verildiğinde enjekte O (n log n) 'dir. \ end {nitpick}
k_g

6
@EliSadoff: Gerçekten büyük sayılar demek . Mimari kelimeye uymayan sayılar, yani CPU çekirdeğindeki bir komut ve bir işlemle hesaplanamayacağı anlamına gelir. N boyutunun sayısı log_2 N bit ile kodlanabilir, bu nedenle toplama O (logN) işlemidir ve çarpma O ((logN) ^ 2) ancak O ((logN) ^ 1.585) (Karasuba) veya hatta O (logN *) olabilir. günlük (logN) * ​​günlük (günlük (LogN)) (FFT).
Hynek -Pichi- Vychodil
Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.