Anasayfa / FAQ / Ruby İçin Daha İyi Performans Rehberi

Ruby İçin Daha İyi Performans Rehberi

rubyRuby Programlama Dilinin sevdiğim özelliklerinden biri de standart kütüphane ile gelen benchmark modülü. Bu sayede yaptığımız kod optimizasyonlarının etkinliğini gözlemlemek bir hayli kolaylaşıyor. Eğer optimizasyon konusu üzerine odaklanırsak beş başlık altında toplamamız mümkün:

  • Algoritma Optimizasyonu (Daha iyi lagoritma daha iyi performans)
  • Kaynak kod (kod yazarken daha performanslı çözümü tercih etmek)
  • İnşa (Kodumuzun build flaglarını ./condigure vb yöntemlerle yapılandırmak)
  • Derleme (Bu aşama mRuby, Jruby ve Rubinius kullanmadıkça Ruby için olanaksız maalesef)
  • Runtime (Bu noktadaki iyileştirmeler Matz ve ekibinin insiyatifinde)

Ruby ile kodumuzun performansını analız etmek için önce Benchmark kütüphanesini çağıracağız:

require ‘benchmark’

Ancak bu noktada da daha performanslı ve pratik analiz için benim önerim benchmark/ips isimli gem ile çalışmak olcaktır.

Örnek:

require 'benchmark/ips'
Benchmark.ips do |n|
n.report( fast )  { fast }
n.report( slow ) { slow }
end

Böylece kaç tane iterasyon istediğimizi de ayrıca belirtmemize gerek kalmamış oluyor. Hedeflerimiz kodumuzu kaynak kodunda optimize ederek ortalama en az %10 üzerinde bir artış sağlamak olmalı ki zaman maliyetine değsin. Ayrıca kodumuzun okunaklılığını da koruyarak kaliteli ve standartlara uygun kod yazmaya özen göstermeliyiz. Mesela bir kod blokunu Proc fonksiyona alıp çağırmak yerine yield daha performanslı ve okunaklı kod sunacaktır bize.

Proc mu yield mi?

Örnek:


def yavas(&block)
block.call
end


def hizli
yield
end

Aynı sonucu daha az yazarak daha iyi bir performansla almak mümkün gördüğünüz gibi. Tabii ki ilk yöntem daha güvenli olsa da ikinci çözümümüz tam 5 kat daha hızlı olacaktır.

Blok mu :sembol.to_proc mu?

Örnek:

(1..100).map { |i| i.to_s }

(1..100).map(&:to_s)

# İkinci yöntem %25 daha hızlı ki Rails bu teknikle baştan opitimze edilmiştir.

Bu durumda daha iyi prformans için blok yerine Symbol#to_proc tercih edeceğiz ve daha az kod yazmış olacağız.

Enumerable#map ve Array#flatten mi Enumerable#flat_map mi?

Örnek:


enum.map do
# bir şeyi daha yavaş yap
end.flatten(1)


enum.flat_map do
# bir şeyi daha hızlı yap
end

ve elbette ikinci yöntemimiz 4 kat daha hızlı neden olmasın ki? Özellikle rails projelerinizde bu method’a ihtiyacınız olacaktır. Rubinius commit etiği için sferik (github) teşekkürü borç biliriz.

Hash#merge mi Hash#merge! mi?

Bilginiz üzere Ruby Dilinde method adının sonundaki ünlem bize kalıcı değişiklik yaptığını belirtir. Yani merge ile dize dönen sonuç merge! ile uygulandığı değişkenin değeri olur.


enum.inject({}) do |h, e|
h.merge(e => e)
end


enum.inject({}) do |h, e|
h.merge(e => e)
end

Eğer nesnemizin değişmesi bizim için sorun değilse yani eski değeri korumamız gerekmiyorsa 3 kat performans kazanmamız mümkün.

Peki size daha da performanslı bir çözüm olduğunu söylesem ne dersiniz?


enum.each_with_object({}) do |e, h|
h[e] = e
end

Bu yöntem ise 3 kat hızlı olan yöntemimizden tam 2 kat daha hızlı yani ilk yöntemden tamı tamına 6 kat daha hızlı sonuç alabilmemiz mümkün.

Hash#fetch mi Hash#fetch ile blok kullanmak mı?


{:bar => :foo}.fetch(:bar, (1..10).to_a)


{:bar => :foo}.fetch(bar) { (1..10).to_a }

Tahmin edilenin aksine blok içeren yöntem yaklaşık olarak 2 kat daha hızlı sonuç vermekte. Tabi ki bu sonuç blook içerisindeki koda göre değişiklik gösterecektir.

String#gsub mı String#sub mı?

'http://www.tehbe.com/'.gsub('http://' , 'https://')

'http://www.tehbe.com/'.sub('http://' , 'https://')

Yukarıda bildiğiniz üzere gsub tüm metni tararken sub sadece bulacağı ilk ‘http://’ parçasını bulana kadar tarayacaktır. Bir url’deki bu gibi sadece bir yerde yapmak istediğiniz bir değişiklikte ya da tekrar ediyorsa bile ibareyi değiştirmek istemiyorsanız neden tüm stringi taratasınız. Böyle kısa bir stringte bile %50 düzeyinde bir performans kazanacaksınız. Ancak gsub muhakkan gerektiğinde de Rails üzerinde daha performanslı bir seçeneğiniz var.

'slug from title'.gsub('  ' , '_')

'slug from title'.tr('  ' , '_')

Tamı tamına 5 kat daha hızlı sonuç alacaksınız tr ile. Aksi takdirde ise Ruby yavaş yahu diyenler kervanına katılmanız işten bile değil.

Paralel atama daha kısa ama daha yavaştır!

Eğer değişkenler birbirlerinin değerlerini kendi aralarında değişmeyecekse:

x, y = 0, 1

gibi atama yapmaktan kaçının. Hem gereksiz hem de yaklaşık %40 daha yavaş.

Kontrol için Exception tercih etmek mi?


begin
foo
rescue NoMethodError
'yok öyle bişi!'
end

Her zaman daha yavaş olacaktır. Oysa aşağıdaki gibi de kontrol edebilirdik:


if respond_to?(:foo)
foo
else
'yok öyle bişi!'
end

Kontrolü if-else yaptığımızda 10 kat daha hızlı sonuç almamız exception kullanımı ile ilgili kulağımıza küpe olmalı.

While döngüsü mü Array#each_with_index mi?

Bilmeniz gerekiyor ki each_with_index ile yaptığınız iterasyon size daha yavaş bir sonuç dönecektir.

dizi_degiskeni.each_with_index do |number, index|
#number ve index sözde değişkenleri ile işlem yap
end



index = 0
while index < dizi_degiskeni.size
# dizi_degiskeni[index] ve index ile işlem yap
index += 1
end

While döngüsü kullanarak %80 daha fazla performans kazanıp işlemlerinizi hızlandırabilirsiniz.

Gördüğünüz üzere kodumuzun hızı tekniğimizden ve dile hakimiyetimizden etkilenir. Bazen kodu optimize etmek okunuşu bozacağından ya da performans artışı oranı çok cüzzi ise optimize etmemek daha akıllıca olacaktır. Bu konuda karar için analizde bulunmak optimizasyon kadar önemlidir.

Hepinize sevgi ve saygılarımla…

 

Hakkında Gökhan Çağlar

Yazar, Şair, Hayalperest, Rubyist

Bunu mu demek istemiştiniz?

Atom Editör

Atom Metin Düzenleyicisini bir IDE imişçesine Etkin Kullanmak (Ruby ve Python İçin)

Atom Editor:  https://atom.io/ Atom ücretsiz ve açık kaynak bir text ve kaynak kod düzenleyicisidir. Linux, …

Bir Cevap Yazın

E-posta hesabınız yayımlanmayacak. Gerekli alanlar * ile işaretlenmişlerdir