Computer >> คอมพิวเตอร์ >  >> การเขียนโปรแกรม >> Ruby

คู่มือ Rubyists เพื่อการท่องจำ

วันนี้ฉันอยากจะพูดเกี่ยวกับหนึ่งในเทคนิคที่ฉันโปรดปรานในการปรับปรุงประสิทธิภาพ เป็นแหล่งที่มาของประสิทธิภาพเล็กๆ น้อยๆ ที่เพิ่มพูนขึ้นในที่สุด และลดการใช้งานของคุณลงเหลือเพียงเศษหินที่คุกรุ่นเป็นบางครั้งเท่านั้น เป็นครั้งคราวเท่านั้น

เทคนิคนี้เรียกว่า "การท่องจำ" แม้จะมีคำศัพท์วิทยาศาสตร์คอมพิวเตอร์มูลค่า 10 เหรียญ แต่ก็หมายความว่าแทนที่จะทำงานแบบเดิมทุกครั้งที่คุณเรียกใช้เมธอด คุณจะบันทึกค่าที่ส่งคืนไปยังตัวแปรและใช้ค่านั้นแทน

โค้ดเทียมมีลักษณะดังนี้:

def my_method
  @memo = <work> if @memo is undefined
  return @memo
end

และนี่คือวิธีที่คุณทำใน Ruby นี่เป็นแนวทางที่แข็งแกร่งที่สุด แต่ค่อนข้างละเอียด มีแนวทางอื่นๆ ที่กระชับกว่านี้ที่เราจะพูดคุยกันในภายหลัง

class MyClass
  def my_method
    unless defined?(@my_method)
      @my_method = begin 
         # Do your calculation, database query
         # or other long-running thing here. 
      end
    end
    @my_method
  end
end

รหัสด้านบนทำสามสิ่ง:

  1. ตรวจสอบเพื่อดูว่ามีตัวแปรอินสแตนซ์ชื่อ @my_method . หรือไม่ .
  2. ถ้ามี จะใช้งานได้และบันทึกผลลัพธ์ใน @my_method .
  3. ส่งกลับ @my_method

อย่าสับสนว่าเรามีทั้งเมธอดและตัวแปรอินสแตนซ์ชื่อ my_method . ฉันสามารถตั้งชื่อตัวแปรของฉันอะไรก็ได้ แต่มันเป็นเรื่องธรรมดาที่จะตั้งชื่อตามเมธอดที่กำลังบันทึก

เวอร์ชันชวเลข

ปัญหาอย่างหนึ่งของโค้ดด้านบนคือค่อนข้างยุ่งยาก ด้วยเหตุนี้ คุณจึงมีแนวโน้มที่จะเห็นเวอร์ชันชวเลขซึ่งเกือบจะเหมือนกัน:

class MyClass
  def my_method1
    @my_method1 ||= some_long_calculation
  end

  def my_method2
    @my_method2 ||= begin
      # The begin-end block lets you easily 
      # use multiple lines of code here. 
    end
  end
end

ทั้งสองใช้ a ||= b . ของ Ruby โอเปอเรเตอร์ ซึ่งเป็นชวเลขสำหรับ a || (a = b) ซึ่งเป็นตัวย่อมากหรือน้อยสำหรับ:

# You wouldn't use return like this in real life. 
# I'm just using it to express to beginners the idea
# that the conditional evaluates to whatever winds up in `a`.
if a
  return a
else
  a = b
  return a
end

ที่มาของบั๊ก

หากคุณให้ความสนใจอย่างใกล้ชิด คุณอาจสังเกตเห็นว่าเวอร์ชันชวเลขประเมิน "ความจริง" ของตัวแปรบันทึกแทนที่จะตรวจสอบการมีอยู่ของตัวแปรนั้น นี่คือที่มาของหนึ่งในข้อจำกัดที่สำคัญของเวอร์ชันชวเลข:มันไม่เคยจำ nil หรือ false .

มีกรณีการใช้งานมากมายที่สิ่งนี้ไม่สำคัญ แต่มันเป็นหนึ่งในข้อเท็จจริงที่น่ารำคาญที่คุณต้องจำไว้ทุกครั้งที่คุณท่องจำ

วิธีการบันทึกด้วยอาร์กิวเมนต์

จนถึงตอนนี้ เราได้ดำเนินการเฉพาะการท่องจำค่าเดียว แต่มีฟังก์ชันไม่มากที่ส่งคืนผลลัพธ์เดียวกันตลอดเวลา มาดูบทสัมภาษณ์ทางเทคนิคที่โปรดปรานกัน:ลำดับฟีโบนักชี

คุณสามารถคำนวณลำดับฟีโบนักชีแบบวนซ้ำใน Ruby ได้ดังนี้:

class Fibonacci
  def self.calculate(n)
    return n if n == 0 || n == 1
    calculate(n - 1) + calculate(n - 2)
  end
end

Fibonacci.calculate(10) # => 55

ปัญหาของการดำเนินการนี้คือไม่มีประสิทธิภาพ เพื่อพิสูจน์สิ่งนี้ ให้เพิ่ม print คำสั่งเพื่อดูค่าของ n .

class Fibonacci
  def self.calculate(n)
    print "#{ n } "
    return n if n == 0 || n == 1
    calculate(n - 1) + calculate(n - 2)
  end
end

Fibonacci.calculate(4)

# Outputs: 4 3 2 1 0 1 2 1 0

อย่างที่คุณเห็น calculate ถูกเรียกซ้ำๆ ด้วยค่า n . ที่เหมือนกันหลายค่า . อันที่จริงจำนวนการโทรไปที่ calculate จะเติบโตแบบทวีคูณด้วย n .

วิธีหนึ่งในการแก้ปัญหานี้คือการจดจำผลลัพธ์ของ calculate . การทำเช่นนี้ไม่ได้แตกต่างจากตัวอย่างการท่องจำอื่นๆ ที่เรากล่าวถึงมากนัก

class Fibonacci
  def self.calculate(n)
    @calculate ||= {}
    @calculate[n] ||= begin
      print "#{ n } "
      if n == 0 || n == 1
        n
      else
        calculate(n - 1) + calculate(n - 2)
      end
    end
  end
end

Fibonacci.calculate(4)

# Outputs: 4 3 2 1 0

ตอนนี้เราได้บันทึก calculate จำนวนการโทรไม่เพิ่มขึ้นแบบทวีคูณด้วย n . อีกต่อไป .

Fibonacci.calculate(20)

# Outputs: 20 19 18 17 16 15 14 13 12 11 10 9 8 7 6 5 4 3 2 1 0

ใช้ไม่ได้

การท่องจำนั้นคล้ายกับการแคชมาก ยกเว้นว่าผลลัพธ์ที่บันทึกไว้จะไม่หมดอายุโดยอัตโนมัติ และโดยปกติแล้วจะไม่มีวิธีง่ายๆ ในการล้างข้อมูลเหล่านี้เมื่อตั้งค่าไว้

สำหรับกรณีการใช้งานเช่นตัวสร้างลำดับฟีโบนักชี เรื่องนี้แทบจะไม่มีความสำคัญ Fibonacci.calculate(10) จะให้ผลลัพธ์เหมือนเดิมเสมอ แต่ในกรณีการใช้งานอื่น ๆ มันไม่สำคัญ

ตัวอย่างเช่น คุณอาจเห็นโค้ดดังนี้:

# Not the best idea
class User
  def full_name
    @full_name ||= [first_name, last_name].join(" ")
  end
end

โดยส่วนตัวแล้วฉันจะไม่ใช้การท่องจำที่นี่ เพราะหากชื่อหรือนามสกุลมีการเปลี่ยนแปลง ชื่อเต็มอาจไม่ได้รับการอัปเดต

ที่เดียวที่คุณสามารถผ่อนคลายได้อีกนิดคือภายในตัวควบคุม Rails เป็นเรื่องปกติมากที่จะเห็นโค้ดแบบนี้:

class ApplicationController
  def current_user
    @current_user ||= User.find(...)
  end
end

ไม่เป็นไร เพราะอินสแตนซ์ของตัวควบคุมจะถูกทำลายหลังจากคำขอเว็บแต่ละครั้ง ไม่น่าเป็นไปได้ที่ผู้ใช้ที่เข้าสู่ระบบในปัจจุบันจะเปลี่ยนแปลงในระหว่างการขอตามปกติใดๆ

เมื่อต้องรับมือกับการเชื่อมต่อสตรีมมิ่งเช่น ActionCable คุณอาจต้องระวังให้มากกว่านี้ ฉันไม่รู้. ฉันไม่เคยใช้มัน

ใช้มากเกินไป

สุดท้ายนี้ ฉันรู้สึกว่าควรที่จะชี้ให้เห็นว่า เป็นไปได้ไหมที่จะท่องจำมากเกินไป เป็นเทคนิคที่ควรนำไปใช้กับการดำเนินการที่มีราคาแพงเท่านั้น ซึ่งจะไม่มีวันเปลี่ยนแปลงตลอดอายุของตัวแปรบันทึกช่วยจำ