Neden sözcüksel kapsam ile daha hızlı “izin” verilir?


31

dolistMakro için kaynak kodu okurken , aşağıdaki yorumu koştum.

;; Bu güvenilir bir test değildir, fakat farketmez, çünkü her iki anlambilim de kabul edilebilir, diğeri dinamik kapsamlama ile biraz daha hızlı , diğeri ise daha hızlı (ve daha temiz anlambilimsel) ve sözcüksel kapsam ile .

Hangi (bu açıklık için basitleştirdim) bu pasajı atıfta bulundu.

(if lexical-binding
    (let ((temp list))
      (while temp
        (let ((it (car temp)))
          ;; Body goes here
          (setq temp (cdr temp)))))
  (let ((temp list)
        it)
    (while temp
      (setq it (car temp))
      ;; Body goes here
      (setq temp (cdr temp)))))

letBir döngü içinde kullanılan bir form görmek beni şaşırttı . setqAynı harici değişkende tekrar tekrar kullanılmaya kıyasla yavaş olduğunu düşünürdüm (yukarıdaki ikinci durumda olduğu gibi).

Hemen üstündeki yorum için olmasa da, bunun alternatiften daha hızlı olduğunu söyleyerek reddetmiş olurdum (sözcüksel bağlanma ile). Öyleyse neden?

  1. Neden yukarıdaki kod sözlü ve dinamik ciltleme konusundaki performansta farklı?
  2. letForm neden sözcüksel olarak daha hızlı?

Yanıtlar:


38

Genel olarak dinamik bağlamaya karşı sözcüksel bağlanma

Aşağıdaki örneği düşünün:

(let ((lexical-binding nil))
  (disassemble
   (byte-compile (lambda ()
                   (let ((foo 10))
                     (message foo))))))

lambdaYerel bir değişkeni olan basit bir nesneyi derler ve hemen demonte eder . İle lexical-bindingengelli yukarıdaki gibi, bayt kod görünüyor şöyle:

0       constant  10
1       varbind   foo
2       constant  message
3       varref    foo
4       call      1
5       unbind    1
6       return    

Not varbindve varreftalimatları. Bu talimatlar bağlamak ve arama sırasıyla değişkenler kendi adıyla bir de küresel üzerindeki bağlanma çevre yığın belleğe . Bütün bunlar performans üzerinde olumsuz bir etkiye sahiptir: Dize hashini ve karşılaştırmasını , global veri erişimi için senkronizasyonu ve CPU önbelleklemesi ile kötü oynayan tekrarlanan yığın bellek erişimini içerir. Ayrıca, dinamik değişken bağlantılarının , sonunda bağları olan her blok için ek aramalar ekleyen, önceki değişkenlerine geri getirilmesi gerekir .letnletn

Eğer bağlama lexical-bindingiçin tyukarıdaki örnekte, bayt kod biraz farklı görünüyor:

0       constant  10
1       constant  message
2       stack-ref 1
3       call      1
4       return    

Bunu varbindve varreftamamen gittiğini unutmayın. Yerel değişken basitçe istifin üzerine itilir ve stack-refkomut aracılığıyla sabit bir ofset ile ifade edilir . Esas olarak, değişken bağlanmış ile okunur sabit zamanda , içinde yığın bellek okur ve böylece tamamen yerel ve yazma, eşzamanlılık ve CPU önbelleğe alma ile de oynar ve hiç bir dizeleri anlamına gelmez.

Genel olarak, yerel değişkenlerin sözcük bağlanma aramaları (örn ile let, setqvs.) sahip çok daha az çalışma zamanı ve bellek karmaşıklığı .

Bu özel örnek

Dinamik bağlamada, her biri yukarıdaki sebeplerden dolayı bir performans cezasına çarptırılır. Ne kadar çok olursa, değişken değişken bağlamalar o kadar fazla olur.

Özellikle, gövde letiçinde bir ek ile loop, sınır değişkeninin, her bir yinelemeye ek bir değişken araması ekleyerek , döngünün her yinelemesinde geri yüklenmesi gerekir . Bu nedenle, döngü gövdesinden uzak tutulması daha hızlıdır, böylece yineleme değişkeni tüm döngü bittikten sonra yalnızca bir kez sıfırlanır . Bununla birlikte, bu özellikle zarif değildir, çünkü yineleme değişkeni gerçekte gerekmeden önce bağlanır.

Sözlüksel bağlamayla, lets ucuzdur. Özellikle, bir letilmek gövdesi içindeki bir ilmek, ilmek gövdesinin letdış yüzeyinden daha kötü değildir (performans açısından) . Bu nedenle, değişkenleri olabildiğince yerel olarak bağlamak ve yineleme değişkeninin döngü gövdesiyle sınırlı kalması tamamen iyidir.

Aynı zamanda biraz daha hızlı, çünkü daha az talimat alıyor. Takip eden yan yana sökmeyi göz önünde bulundurun (sağ taraftaki yerel izin):

0       varref    list            0       varref    list         
1       constant  nil             1:1     dup                    
2       varbind   it              2       goto-if-nil-else-pop 2 
3       dup                       5       dup                    
4       varbind   temp            6       car                    
5       goto-if-nil-else-pop 2    7       stack-ref 1            
8:1     varref    temp            8       cdr                    
9       car                       9       discardN-preserve-tos 2
10      varset    it              11      goto      1            
11      varref    temp            14:2    return                 
12      cdr       
13      dup       
14      varset    temp
15      goto-if-not-nil 1
18      constant  nil
19:2    unbind    2
20      return    

Ne var ki, aradaki farka neden olan hiçbir fikrim yok.


7

Kısacası - dinamik bağlama çok yavaş. Lexical ciltleme, çalışma zamanında oldukça hızlıdır. Temel sebep, dinamik bağlamanın yapamadığı zaman sözcüksel bağlamanın derleme zamanında çözülebilmesidir.

Aşağıdaki kodu göz önünde bulundurun:

(let ((x 42))
    (foo)
    (message "%d" x))

letDerleyici derlerken, derleyici foo(dinamik olarak bağlı) değişkeni elde edip edemeyeceğini bilemez x, bu nedenle bir bağlayıcı oluşturmalı xve değişkenin adını korumalıdır. Sözcük bağlama ile, derleyici sadece döker değeri arasında xonun adı olmadan, bağlamaları yığını üzerinde ve doğrudan sağ girişi erişir.

Ama bekleyin - dahası var. Sözlüksel bağlamayla, derleyici bu belirli bağlamanın xyalnızca kodda kullanıldığını doğrulayabilir message; çünkü xdeğiştirilmiş asla, bu satır içi güvenlidir xve verim

(progn
  (foo)
  (message "%d" 42))

Geçerli bytecode derleyicisinin bu optimizasyonu gerçekleştirdiğini sanmıyorum, ancak gelecekte bunu yapacağına eminim.

Yani kısacası:

  • dinamik bağlanma, optimizasyon için az sayıda fırsat sağlayan ağır bir işlemdir;
  • sözcüksel bağlanma hafif bir işlemdir;
  • Salt okunur bir değerin sözcüksel olarak bağlanması çoğu zaman uzakta optimize edilebilir.

3

Bu yorum, sözcüksel bağlamanın dinamik bağlamaya göre daha hızlı ya da daha yavaş olduğunu göstermez. Aksine, bu farklı formların sözcüksel ve dinamik bağlanma altında farklı performans özelliklerine sahip olduğunu, örneğin bir tanesinin bir bağlayıcı disiplinin altında, diğerinin de tercih edildiğini öne sürmektedir.

Peki , sözlük kapsamı dinamik kapsamdan daha mı hızlı? Bu durumda çok fazla fark olmadığından şüpheliyim ama bilmiyorum - gerçekten ölçmek zorundasın.


1
varbindSözcüksel bağlanma altında derlenmiş kod yoktur . Bütün amaç ve amaç bu.
lunaryorn

Hmm. Sözcüksel bağlanma altında derlenmiş bir tanım ürettiğini varsayarak , başlangıçta ;; -*- lexical-binding: t -*-onu yükleyen ve çağrılan yukarıdaki kaynağı içeren bir dosya oluşturdum (byte-compile 'sum1). Ancak, öyle görünmüyor.
gsg

Bayt kodu yorumları, bu yanlış varsayıma dayalı olarak kaldırıldı.
gsg

Bu kod açıkça lunaryon cevabı gösterileri ise bağlayıcı hızlı sözcük altında (her ne kadar ders sadece mikro düzeyde).
shosti

@gsg Bu bildirim, karşılık gelen dosya arabelleğinin dışından çağrılan işlevler üzerinde etkisi olmayan standart bir dosya değişkenidir. IOW, sadece kaynak dosyayı ziyaret ederseniz ve ardından byte-compilebyte derleyicisinin tam olarak ne yaptığını gösteren güncel arabellek ile güncel çağrıda bulunursanız, bunun bir etkisi olur . byte-compileAyrı ayrı çağırırsanız , cevabımda lexical-bindingyaptığım gibi açıkça ayarlamanız gerekir .
lunaryorn
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.