ราคาขี้เกียจของ Scala (ซ่อนไว้) ราคาเท่าไหร่?


165

คุณลักษณะที่มีประโยชน์อย่างหนึ่งของ Scala คือlazy valการประเมิน a valล่าช้าจนกว่าจะมีความจำเป็น (ในการเข้าถึงครั้งแรก)

แน่นอน a lazy valต้องมีค่าใช้จ่ายบางส่วน - สกาล่าบางแห่งต้องติดตามว่าได้รับการประเมินค่าแล้วและการประเมินผลต้องทำข้อมูลให้ตรงกันเนื่องจากหลายเธรดอาจพยายามเข้าถึงค่าเป็นครั้งแรกในเวลาเดียวกัน

ค่าใช้จ่ายของ a lazy valคืออะไร - มีค่าสถานะบูลีนที่ซ่อนอยู่ซึ่งเชื่อมโยงกับ a lazy valเพื่อติดตามว่าได้รับการประเมินหรือไม่สิ่งที่ถูกซิงโครไนซ์และมีค่าใช้จ่ายเพิ่มเติมอีกหรือไม่

นอกจากนี้สมมติว่าฉันทำสิ่งนี้:

class Something {
    lazy val (x, y) = { ... }
}

นี่คือเหมือนกับการมีสองแยกlazy vals xและyหรือฉันจะได้รับค่าใช้จ่ายเพียงครั้งเดียวสำหรับทั้งคู่(x, y)?

คำตอบ:


86

สิ่งนี้นำมาจากรายการส่งเมลสกาล่าและให้รายละเอียดการใช้งานlazyในแง่ของรหัส Java (แทนที่จะเป็น bytecode):

class LazyTest {
  lazy val msg = "Lazy"
}

ถูกคอมไพล์กับบางสิ่งที่เทียบเท่ากับโค้ด Java ต่อไปนี้:

class LazyTest {
  public int bitmap$0;
  private String msg;

  public String msg() {
    if ((bitmap$0 & 1) == 0) {
        synchronized (this) {
            if ((bitmap$0 & 1) == 0) {
                synchronized (this) {
                    msg = "Lazy";
                }
            }
            bitmap$0 = bitmap$0 | 1;
        }
    }
    return msg;
  }

}

33
ฉันคิดว่าการใช้งานต้องมีการเปลี่ยนแปลงตั้งแต่ Java เวอร์ชันนี้โพสต์ในปี 2007 มีเพียงหนึ่งบล็อกที่ซิงโครไนซ์และbitmap$0เขตข้อมูลมีความผันผวนในการใช้งานปัจจุบัน (2.8)
Mitch Blevins

1
ใช่ - ฉันควรให้ความสำคัญกับสิ่งที่ฉันโพสต์มากขึ้น!
oxbow_lakes

8
@ Mitch - ฉันหวังว่าการใช้งานจะเปลี่ยนไป! รูปแบบการต่อต้านการเริ่มต้นตรวจสอบซ้ำเป็นข้อผิดพลาดที่ลึกซึ้งคลาสสิก ดูen.wikipedia.org/wiki/Double-checked_locking
Malvolio

20
มันเป็นปฏิปักษ์ถึง Java 1.4 เนื่องจากคำสำคัญระเหย Java 1.5 มีความหมาย bit เข้มงวดและตอนนี้การตรวจสอบสองครั้งนั้นตกลง
iirekm

8
ดังนั้น ณ วันที่สกาล่า 2.10 การดำเนินการในปัจจุบันคืออะไร? นอกจากนี้โปรดใครสักคนให้คำใบ้ว่าค่าใช้จ่ายนี้ในทางปฏิบัติเท่าไหร่และกฎง่ายๆเมื่อใช้เพื่อหลีกเลี่ยง?
ib84

39

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

โดยใช้:

class Something {
  lazy val foo = getFoo
  def getFoo = "foo!"
}

สร้าง bytecode ตัวอย่าง:

 0  aload_0 [this]
 1  getfield blevins.example.Something.bitmap$0 : int [15]
 4  iconst_1
 5  iand
 6  iconst_0
 7  if_icmpne 48
10  aload_0 [this]
11  dup
12  astore_1
13  monitorenter
14  aload_0 [this]
15  getfield blevins.example.Something.bitmap$0 : int [15]
18  iconst_1
19  iand
20  iconst_0
21  if_icmpne 42
24  aload_0 [this]
25  aload_0 [this]
26  invokevirtual blevins.example.Something.getFoo() : java.lang.String [18]
29  putfield blevins.example.Something.foo : java.lang.String [20]
32  aload_0 [this]
33  aload_0 [this]
34  getfield blevins.example.Something.bitmap$0 : int [15]
37  iconst_1
38  ior
39  putfield blevins.example.Something.bitmap$0 : int [15]
42  getstatic scala.runtime.BoxedUnit.UNIT : scala.runtime.BoxedUnit [26]
45  pop
46  aload_1
47  monitorexit
48  aload_0 [this]
49  getfield blevins.example.Something.foo : java.lang.String [20]
52  areturn
53  aload_1
54  monitorexit
55  athrow

ค่าเริ่มต้นในสิ่งอันดับเหมือนlazy val (x,y) = { ... }มีการแคชที่ซ้อนกันผ่านกลไกเดียวกัน ผลลัพธ์ของ tuple นั้นได้รับการประเมินและเก็บไว้อย่างเกียจคร้านและการเข้าถึงของ x หรือ y จะทำให้เกิดการประเมิน tuple การแยกค่าแต่ละค่าจาก tuple ทำได้อย่างอิสระและเกียจคร้าน (และแคช) ดังนั้นรหัสดับเบิล instantiation ข้างต้นสร้างx, yและสาขาของประเภทx$1Tuple2


26

ด้วย Scala 2.10 ค่าที่ขี้เกียจเช่น:

class Example {
  lazy val x = "Value";
}

ถูกคอมไพล์ไปยังโค้ดไบต์ที่คล้ายกับโค้ด Java ต่อไปนี้:

public class Example {

  private String x;
  private volatile boolean bitmap$0;

  public String x() {
    if(this.bitmap$0 == true) {
      return this.x;
    } else {
      return x$lzycompute();
    }
  }

  private String x$lzycompute() {
    synchronized(this) {
      if(this.bitmap$0 != true) {
        this.x = "Value";
        this.bitmap$0 = true;
      }
      return this.x;
    }
  }
}

booleanโปรดทราบว่าบิตแมปเป็นตัวแทนจาก หากคุณเพิ่มฟิลด์อื่นคอมไพเลอร์จะเพิ่มขนาดของฟิลด์เพื่อให้สามารถแสดงค่าอย่างน้อย 2 ค่าคือbyteค่าคือเป็น นี่เป็นเพียงการเรียนต่อครั้งใหญ่เท่านั้น

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


11

สกาล่า SIP-20เสนอการใช้งานใหม่ของ lazy val ซึ่งถูกต้องมากกว่า แต่ช้ากว่าเวอร์ชั่น "ปัจจุบัน" ประมาณ 25%

เสนอการดำเนินงานดูเหมือนว่า:

class LazyCellBase { // in a Java file - we need a public bitmap_0
  public static AtomicIntegerFieldUpdater<LazyCellBase> arfu_0 =
    AtomicIntegerFieldUpdater.newUpdater(LazyCellBase.class, "bitmap_0");
  public volatile int bitmap_0 = 0;
}
final class LazyCell extends LazyCellBase {
  import LazyCellBase._
  var value_0: Int = _
  @tailrec final def value(): Int = (arfu_0.get(this): @switch) match {
    case 0 =>
      if (arfu_0.compareAndSet(this, 0, 1)) {
        val result = 0
        value_0 = result
        @tailrec def complete(): Unit = (arfu_0.get(this): @switch) match {
          case 1 =>
            if (!arfu_0.compareAndSet(this, 1, 3)) complete()
          case 2 =>
            if (arfu_0.compareAndSet(this, 2, 3)) {
              synchronized { notifyAll() }
            } else complete()
        }
        complete()
        result
      } else value()
    case 1 =>
      arfu_0.compareAndSet(this, 1, 2)
      synchronized {
        while (arfu_0.get(this) != 3) wait()
      }
      value_0
    case 2 =>
      synchronized {
        while (arfu_0.get(this) != 3) wait()
      }
      value_0
    case 3 => value_0
  }
}

ตั้งแต่เดือนมิถุนายน 2013 SIP นี้ยังไม่ได้รับการอนุมัติ ฉันคาดว่าน่าจะได้รับการอนุมัติและรวมอยู่ใน Scala เวอร์ชันในอนาคตตามการอภิปรายรายชื่อผู้รับจดหมาย ดังนั้นฉันคิดว่าคุณควรที่จะระวังการสังเกตของ Daniel Spiewak :

Lazy val เป็น * ไม่ * ฟรี (หรือถูกกว่า) ใช้มันเฉพาะในกรณีที่คุณต้องการความเกียจคร้านสำหรับความถูกต้องไม่ใช่เพื่อการปรับให้เหมาะสม


10

ฉันเขียนโพสต์เกี่ยวกับปัญหานี้https://dzone.com/articles/cost-laziness

สรุปว่าโทษนั้นเล็กมาก ๆ ซึ่งในทางปฏิบัติคุณสามารถเพิกเฉยได้


1
ขอบคุณสำหรับมาตรฐานที่ คุณสามารถเปรียบเทียบกับการปรับใช้ SIP-20 ที่เสนอได้หรือไม่?
Turadg

-6

รับ bycode ที่สร้างขึ้นโดย Scala สำหรับขี้เกียจก็สามารถประสบปัญหาความปลอดภัยหัวข้อตามที่กล่าวไว้ในการตรวจสอบคู่ล็อคhttp://www.javaworld.com/javaworld/jw-05-2001/jw-0525-double.html?page=1


3
การอ้างสิทธิ์นี้ทำโดยความเห็นต่อคำตอบที่ได้รับการยอมรับโดย mitch และข้องแวะโดย @iirekm: รูปแบบนี้ใช้ได้จาก java1.5 เป็นต้นไป
Jens Schauder
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.