คู่มือ Skyframe StateMachines

รายงานปัญหา ดูแหล่งที่มา

ภาพรวม

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

StateMachine อาจใช้เพื่อแสดงลําดับ การโยงหัวข้อ การเกิดขึ้นพร้อมกันเชิงโครงสร้างและปรับให้เหมาะกับการโต้ตอบของ Skyframe โดยเฉพาะ StateMachine สามารถเขียนลงใน StateMachine ที่ใหญ่กว่าและแชร์ StateMachine ย่อยได้ การเกิดขึ้นพร้อมกันมีลําดับชั้นในการก่อสร้างเสมอและมีเหตุผล ทุกงานย่อยที่เกิดขึ้นพร้อมกันจะทํางานในชุดข้อความ SkyFunction ระดับบนสุดเพียงรายการเดียว

ข้อมูลเบื้องต้น

ส่วนนี้สร้างแรงบันดาลใจและเปิดตัว StateMachine ช่วงสั้นๆ ซึ่งพบในแพ็กเกจ java.com.google.devtools.build.skyframe.state

แนะนําสั้นๆ เกี่ยวกับ Skyframe รีสตาร์ท

Skyframe เป็นกรอบงานที่ทําการประเมินกราฟการพึ่งพา แต่ละโหนดในกราฟจะสอดคล้องกับการประเมิน SkyFunction ด้วย SkyKey ที่ระบุพารามิเตอร์และ SkyValue ที่ระบุผลลัพธ์ โมเดลคอมพิวเตอร์นี้เป็นแบบ SkyFunction อาจค้นหา SkyValues โดย SkyKey ซึ่งจะเรียกใช้การประเมิน SkyFunction เพิ่มเติมแบบเรียกซ้ํา แทนการบล็อก ซึ่งจะผูกข้อมูลเมื่อ SkyValue ที่ขอยังไม่สมบูรณ์เนื่องจากการคํานวณย่อยบางส่วนไม่สมบูรณ์ ฟังก์ชัน SkyFunction ที่ส่งคําขอสังเกตเห็นการตอบสนอง null getValue และควรแสดงผล null แทน SkyValue ซึ่งส่งสัญญาณว่าไม่สมบูรณ์เนื่องจากไม่มีอินพุต Skyframe รีสตาร์ท SkyFunction เมื่อมี SkyValues ที่ขอก่อนหน้านี้พร้อมใช้งาน

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

StateMachine เป็นออบเจ็กต์ที่อาศัยอยู่ภายใน SkyKeyComputeState และกําจัดการคํานวณทั้งหมดเสมือนเมื่อ SkyFunction รีสตาร์ท (สมมติว่า SkyKeyComputeState ไม่ได้ออกจากแคช) โดยแสดงการระงับและฮุกสั่งการต่อ

การคํานวณแบบเก็บสถานะภายใน SkyKeyComputeState

จากมุมมองด้านการออกแบบเชิงวัตถุ คุณควรพิจารณาจัดเก็บวัตถุที่คํานวณไว้ภายใน SkyKeyComputeState แทนค่าข้อมูลเพียงอย่างเดียว ใน Java รายละเอียดขั้นต่ําของออบเจ็กต์ที่แสดงลักษณะการทํางานคืออินเทอร์เฟซการทํางานและดูเหมือนว่าเพียงพอแล้ว StateMachine มีคําจํากัดความต่อไปนี้ซ้ําแบบน่าสนใจ2

@FunctionalInterface
public interface StateMachine {
  StateMachine step(Tasks tasks) throws InterruptedException;
}

อินเทอร์เฟซ Tasks คล้ายกับ SkyFunction.Environment แต่ได้รับการออกแบบมาให้รองรับแบบอะซิงโครนัสและเพิ่มการรองรับงานย่อยที่เกิดขึ้นพร้อมกันเชิงตรรกะ3

ค่าการแสดงผล step เป็น StateMachine อีกค่าหนึ่ง ซึ่งช่วยให้กําหนดขั้นตอนของลําดับได้แบบอุปนัย step จะแสดงผล DONE เมื่อ StateMachine เสร็จสิ้น เช่น

class HelloWorld implements StateMachine {
  @Override
  public StateMachine step(Tasks tasks) {
    System.out.println("hello");
    return this::step2;  // The next step is HelloWorld.step2.
  }

  private StateMachine step2(Tasks tasks) {
     System.out.println("world");
     // DONE is special value defined in the `StateMachine` interface signaling
     // that the computation is done.
     return DONE;
  }
}

อธิบาย StateMachine ด้วยเอาต์พุตต่อไปนี้

hello
world

โปรดทราบว่าการอ้างอิงเมธอด this::step2 เป็น StateMachine เนื่องจาก step2 ตรงกับคําจํากัดความของอินเทอร์เฟซฟังก์ชันการทํางานของ StateMachine การอ้างอิงเมธอดเป็นวิธีที่ใช้ระบุสถานะถัดไปใน StateMachine ที่ใช้กันมากที่สุด

การระงับและทํางานต่อ

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

Callback, ความต่อเนื่อง และการคํานวณแบบอะซิงโครนัส

ในทางเทคนิค StateMachine จะทําหน้าที่เป็นการทํางานต่อเนื่อง ซึ่งกําหนดการคํานวณที่ตามมา แทนที่จะบล็อก StateMachine จะระงับตามความสมัครใจได้โดยการส่งคืนจากฟังก์ชัน step ซึ่งจะโอนการควบคุมกลับไปยังอินสแตนซ์ Driver จากนั้น Driver จะเปลี่ยนไปใช้ StateMachine เวอร์ชันพร้อมใช้ หรือเลิกใช้การควบคุมกลับไปเป็น Skyframe

เดิมทีการติดต่อกลับและความต่อเนื่องจะรวมกันเป็นแนวคิดเดียว อย่างไรก็ตาม StateMachine จะแยกความแตกต่างระหว่างเนื้อหาทั้งสองได้

  • การเรียกกลับ - อธิบายตําแหน่งจัดเก็บผลการคํานวณแบบอะซิงโครนัส
  • ความต่อเนื่อง - ระบุสถานะการดําเนินการถัดไป

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

ความต่อเนื่องคือค่าที่แสดงผล StateMachine ของ StateMachine และรวมการดําเนินการที่ซับซ้อนซึ่งจะตามมาเมื่อการคํานวณแบบไม่พร้อมกันทั้งหมดได้รับการแก้ไข แนวทางเชิงโครงสร้างนี้จะช่วยรักษา ความซับซ้อนของการเรียกกลับ

งาน

อินเทอร์เฟซ Tasks ให้ API ที่มี StateMachine ในการค้นหา SkyValues โดย SkyKey และเพื่อกําหนดเวลางานย่อยที่เกิดขึ้นพร้อมกัน

interface Tasks {
  void enqueue(StateMachine subtask);

  void lookUp(SkyKey key, Consumer<SkyValue> sink);

  <E extends Exception>
  void lookUp(SkyKey key, Class<E> exceptionClass, ValueOrExceptionSink<E> sink);

  // lookUp overloads for 2 and 3 exception types exist, but are elided here.
}

การค้นหา SkyValue

StateMachine ใช้การโอเวอร์โหลด Tasks.lookUp ครั้งเพื่อหา SkyValues พารามิเตอร์เหล่านี้คล้ายกับ SkyFunction.Environment.getValue และ SkyFunction.Environment.getValueOrThrow และมีการจัดการข้อยกเว้นที่คล้ายกัน การติดตั้งใช้งานจะไม่ดําเนินการค้นหาในทันที แต่จะจัดกลุ่ม4จํานวนการค้นหาให้มากที่สุดเท่าที่จะเป็นไปได้ก่อนที่จะทําเช่นนั้น ค่านี้อาจไม่พร้อมใช้งานทันที เช่น จําเป็นต้องรีสตาร์ท Skyframe เพื่อให้ผู้โทรระบุสิ่งที่ต้องทํากับค่าผลลัพธ์โดยใช้โค้ดเรียกกลับ

โปรเซสเซอร์ StateMachine (Driver และการบริดจ์ไปยัง SkyFrame) รับประกันว่าค่าจะใช้ได้ก่อนที่สถานะถัดไปจะเริ่มขึ้น ดูตัวอย่างต่อไปนี้

class DoesLookup implements StateMachine, Consumer<SkyValue> {
  private Value value;

  @Override
  public StateMachine step(Tasks tasks) {
    tasks.lookUp(new Key(), (Consumer<SkyValue>) this);
    return this::processValue;
  }

  // The `lookUp` call in `step` causes this to be called before `processValue`.
  @Override  // Implementation of Consumer<SkyValue>.
  public void accept(SkyValue value) {
    this.value = (Value)value;
  }

  private StateMachine processValue(Tasks tasks) {
    System.out.println(value);  // Prints the string representation of `value`.
    return DONE;
  }
}

ในตัวอย่างด้านบน ขั้นตอนแรกจะค้นหา new Key() และส่งต่อ this เป็นผู้บริโภค นั่นเป็นเพราะ DoesLookup ใช้งาน Consumer<SkyValue>

ตามสัญญา ก่อนที่สถานะถัดไป DoesLookup.processValue จะเริ่มขึ้น การค้นหาทั้งหมดของ DoesLookup.step จะเสร็จสมบูรณ์ ดังนั้น value จึงจะใช้งานได้เมื่อเข้าถึงใน processValue

งานย่อย

Tasks.enqueue ขอให้เรียกใช้งานย่อยเชิงตรรกะพร้อมกัน งานย่อยยังเป็น StateMachine และทําทุกอย่างที่ StateMachine ทําประจําได้ ซึ่งรวมถึงการสร้างงานย่อยซ้ําๆ หรือการค้นหา SkyValue เช่นเดียวกับ lookUp ไดรเวอร์ของเครื่องยนต์จะช่วยให้งานย่อยทั้งหมดเสร็จสมบูรณ์ก่อนที่จะทําขั้นตอนถัดไป ดูตัวอย่างต่อไปนี้

class Subtasks implements StateMachine {
  private int i = 0;

  @Override
  public StateMachine step(Tasks tasks) {
    tasks.enqueue(new Subtask1());
    tasks.enqueue(new Subtask2());
    // The next step is Subtasks.processResults. It won't be called until both
    // Subtask1 and Subtask 2 are complete.
    return this::processResults;
  }

  private StateMachine processResults(Tasks tasks) {
    System.out.println(i);  // Prints "3".
    return DONE;  // Subtasks is done.
  }

  private class Subtask1 implements StateMachine {
    @Override
    public StateMachine step(Tasks tasks) {
      i += 1;
      return DONE;  // Subtask1 is done.
    }
  }

  private class Subtask2 implements StateMachine {
    @Override
    public StateMachine step(Tasks tasks) {
      i += 2;
      return DONE;  // Subtask2 is done.
    }
  }
}

แม้ว่า Subtask1 และ Subtask2 จะเกิดขึ้นพร้อมกันในเชิงตรรกะ แต่ทุกอย่างจะทํางานในชุดข้อความเดียว ดังนั้นการอัปเดต "พร้อมกัน" ของ i จึงไม่ต้องซิงค์

การเกิดขึ้นพร้อมกันที่มีโครงสร้าง

เนื่องจาก lookUp และ enqueue ทั้งหมดจะต้องได้รับการแก้ไขก่อนที่จะเปลี่ยนเป็นสถานะถัดไป จึงหมายความว่าการเกิดขึ้นพร้อมกันนั้นจํากัดอยู่ที่โครงสร้างต้นไม้อยู่แล้ว คุณอาจสร้างการเกิดขึ้นพร้อมกัน5แบบลําดับชั้นดังที่แสดงในตัวอย่างต่อไปนี้

การเกิดขึ้นพร้อมกันแบบมีโครงสร้าง

การบอกจาก UML เป็นเรื่องยากที่โครงสร้างการเกิดขึ้นพร้อมกันจะสร้างต้นไม้ และมีมุมมองสํารองที่แสดงโครงสร้างของต้นไม้ได้ดีขึ้น

การเกิดขึ้นพร้อมกันแบบไม่มีโครงสร้าง

การเกิดขึ้นพร้อมกันนั้นง่ายกว่ามาก

รูปแบบของการเรียบเรียงและการควบคุม

ส่วนนี้จะแสดงตัวอย่างวิธีเขียน StateMachine หลายๆ วิธีและแนวทางแก้ไขปัญหาขั้นตอนการควบคุมบางอย่าง

สถานะตามลําดับ

นี่เป็นรูปแบบขั้นตอนการควบคุมที่พบบ่อยที่สุดและตรงไปตรงมา ตัวอย่างนี้แสดงการคํานวณแบบเก็บสถานะภายใน SkyKeyComputeState

กิ่งไม้

สถานะ Branch ใน StateMachine จะสําเร็จได้โดยการแสดงผลค่าที่ต่างกันโดยใช้ขั้นตอนการควบคุม Java ปกติ ดังที่แสดงในตัวอย่างต่อไปนี้

class Branch implements StateMachine {
  @Override
  public StateMachine step(Tasks tasks) {
    // Returns different state machines, depending on condition.
    if (shouldUseA()) {
      return this::performA;
    }
    return this::performB;
  }
  …
}

สาขาบางสาขามักจะส่งคืน DONE เพื่อเรียนจนจบหลักสูตร

การเรียงตามลําดับขั้นสูง

เนื่องจากโครงสร้างการควบคุม StateMachine ไม่มีความทรงจํา จึงอาจแชร์คําจํากัดความ StateMachine เป็นงานย่อยเป็นครั้งคราว ให้ M1 และ M2 เป็นอินสแตนซ์ StateMachine ที่แชร์ StateMachine, S กับ M1 และ M2 เป็นลําดับ <A, S, B> และ <X, S, Y> ตามลําดับ ปัญหาคือ S ไม่รู้ว่าจะเลือกไปที่ B หรือ Y ต่อหลังจากที่ดําเนินการเสร็จสมบูรณ์ และ StateMachine ไม่เก็บข้อมูลสแต็ก ส่วนนี้จะอธิบายเทคนิคในการบรรลุเป้าหมายนี้

StateMachine เป็นองค์ประกอบลําดับเทอร์มินัล

การดําเนินการนี้ไม่สามารถแก้ไขปัญหาเบื้องต้นได้ โดยจะแสดงเฉพาะการเรียบเรียงตามลําดับ เมื่อ StateMachine ที่แชร์คือเทอร์มินัลในลําดับ

// S is the shared state machine.
class S implements StateMachine { … }

class M1 implements StateMachine {
  @Override
  public StateMachine step(Tasks tasks) {
    performA();
    return new S();
  }
}

class M2 implements StateMachine {
  @Override
  public StateMachine step(Tasks tasks) {
    performX();
    return new S();
  }
}

ยังคงทํางานได้แม้ว่า S จะเป็นเครื่องประมวลผลที่ซับซ้อนก็ตาม

งานย่อยสําหรับการแต่งเพลงตามลําดับ

เนื่องจากเรารับประกันว่างานย่อยที่เข้าคิวจะเสร็จสิ้นก่อนสถานะถัดไป บางครั้งก็อาจทําการละเมิดเล็กๆ น้อยๆ6 กับกลไกงานย่อยย่อยๆ ได้

class M1 implements StateMachine {
  @Override
  public StateMachine step(Tasks tasks) {
    performA();
    // S starts after `step` returns and by contract must complete before `doB`
    // begins. It is effectively sequential, inducing the sequence < A, S, B >.
    tasks.enqueue(new S());
    return this::doB;
  }

  private StateMachine doB(Tasks tasks) {
    performB();
    return DONE;
  }
}

class M2 implements StateMachine {
  @Override
  public StateMachine step(Tasks tasks) {
    performX();
    // Similarly, this induces the sequence < X, S, Y>.
    tasks.enqueue(new S());
    return this::doY;
  }

  private StateMachine doY(Tasks tasks) {
    performY();
    return DONE;
  }
}

แทรก runAfter

บางครั้งการใช้ Tasks.enqueue อย่างไม่เหมาะสมเป็นไปไม่ได้เนื่องจากมีงานย่อยอื่นๆ พร้อมกันหรือการเรียก Tasks.lookUp ที่ต้องดําเนินการให้เสร็จก่อนที่จะดําเนินการ S ในกรณีนี้ การแทรกพารามิเตอร์ runAfter ลงใน S อาจใช้เพื่อบอก S ถึงสิ่งที่ต้องทําต่อไปได้

class S implements StateMachine {
  // Specifies what to run after S completes.
  private final StateMachine runAfter;

  @Override
  public StateMachine step(Tasks tasks) {
    … // Performs some computations.
    return this::processResults;
  }

  @Nullable
  private StateMachine processResults(Tasks tasks) {
    … // Does some additional processing.

    // Executes the state machine defined by `runAfter` after S completes.
    return runAfter;
  }
}

class M1 implements StateMachine {
  @Override
  public StateMachine step(Tasks tasks) {
    performA();
    // Passes `this::doB` as the `runAfter` parameter of S, resulting in the
    // sequence < A, S, B >.
    return new S(/* runAfter= */ this::doB);
  }

  private StateMachine doB(Tasks tasks) {
    performB();
    return DONE;
  }
}

class M2 implements StateMachine {
  @Override
  public StateMachine step(Tasks tasks) {
    performX();
    // Passes `this::doY` as the `runAfter` parameter of S, resulting in the
    // sequence < X, S, Y >.
    return new S(/* runAfter= */ this::doY);
  }

  private StateMachine doY(Tasks tasks) {
    performY();
    return DONE;
  }
}

วิธีนี้สะอาดกว่าการละเมิดงานย่อย แต่การใช้หลักการนี้มากเกินไป เช่น การซ้อน StateMachine กับ runAfter หลายเส้นทาง ก็เป็นเส้นทางไปสู่ Callback Hell แต่คุณควรแบ่ง runAfter ตามลําดับเป็นสถานะลําดับปกติแทน

  return new S(/* runAfter= */ new T(/* runAfter= */ this::nextStep))

สามารถแทนที่ด้วย

  private StateMachine step1(Tasks tasks) {
     doStep1();
     return new S(/* runAfter= */ this::intermediateStep);
  }

  private StateMachine intermediateStep(Tasks tasks) {
    return new T(/* runAfter= */ this::nextStep);
  }

ตัวเลือกต้องห้าม: runAfterUnlessError

ในฉบับร่างก่อนหน้านี้ เราถือว่า runAfterUnlessError อาจเกิดข้อผิดพลาดขึ้นในไม่ช้า ซึ่งเกิดจากข้อเท็จจริงที่ว่าข้อผิดพลาดมักจะต้องได้รับการตรวจสอบ 2 ครั้ง โดยครั้งแรกที่ StateMachine มีการอ้างถึง runAfter และหนึ่งครั้งโดยเครื่อง runAfter

หลังจากไตร่ตรองกันแล้ว เราตัดสินว่าโค้ดที่เหมือนกัน มีความสําคัญมากกว่าการคัดลอกการตรวจสอบข้อผิดพลาด อาจทําให้สับสนหากกลไก runAfter ไม่ทํางานอย่างสอดคล้องกับกลไก tasks.enqueue ซึ่งต้องมีการตรวจสอบข้อผิดพลาดเสมอ

การมอบสิทธิ์โดยตรง

ทุกครั้งที่มีการเปลี่ยนสถานะอย่างเป็นทางการ ลูป Driver หลักจะไปข้างหน้า ตามสัญญาขั้นสูง สถานะขั้นสูงหมายความว่า การค้นหา SkyValue และงานที่เป็นคิวก่อนหน้านี้ทั้งหมดจะได้รับการแก้ไขก่อนสถานะถัดไปจะดําเนินการ บางครั้งตรรกะของผู้รับมอบสิทธิ์ StateMachine จะทําให้ขั้นตอนล่วงหน้าไม่จําเป็นหรือข้ามขั้นตอนได้ เช่น หากผู้ได้รับมอบหมาย step คนแรกดําเนินการค้นหา SkyKey ที่อาจโหลดคู่ขนานกับการค้นหาสถานะการมอบอํานาจ การดําเนินเฟสล่วงหน้าจะทําให้ลําดับเหล่านั้นเป็นลําดับ การดําเนินการการมอบสิทธิ์โดยตรงอาจสมเหตุสมผลกว่าในตัวอย่างด้านล่าง

class Parent implements StateMachine {
  @Override
  public StateMachine step(Tasks tasks ) {
    tasks.lookUp(new Key1(), this);
    // Directly delegates to `Delegate`.
    //
    // The (valid) alternative:
    //   return new Delegate(this::afterDelegation);
    // would cause `Delegate.step` to execute after `step` completes which would
    // cause lookups of `Key1` and `Key2` to be sequential instead of parallel.
    return new Delegate(this::afterDelegation).step(tasks);
  }

  private StateMachine afterDelegation(Tasks tasks) {
    …
  }
}

class Delegate implements StateMachine {
  private final StateMachine runAfter;

  Delegate(StateMachine runAfter) {
    this.runAfter = runAfter;
  }

  @Override
  public StateMachine step(Tasks tasks) {
    tasks.lookUp(new Key2(), this);
    return …;
  }

  // Rest of implementation.
  …

  private StateMachine complete(Tasks tasks) {
    …
    return runAfter;
  }
}

การรับส่งข้อมูล

โฟกัสของการพูดคุยครั้งก่อนๆ อยู่ที่การจัดการขั้นตอนการควบคุม ส่วนนี้จะอธิบายการเผยแพร่ค่าข้อมูล

การใช้โค้ดเรียกกลับ Tasks.lookUp รายการ

ตัวอย่างการใช้โค้ดเรียกกลับ Tasks.lookUp ในการค้นหา SkyValue ส่วนนี้ให้เหตุผลและแนะนําแนวทางในการจัดการ SkyValues หลายรายการ

Tasks.lookUp โค้ดเรียกกลับ

เมธอด Tasks.lookUp จะใช้การเรียกกลับ sink เป็นพารามิเตอร์

  void lookUp(SkyKey key, Consumer<SkyValue> sink);

วิธีการสอนคือการใช้ lambda Java นํามาใช้งานในลักษณะต่อไปนี้

  tasks.lookUp(key, value -> myValue = (MyValueClass)value);

โดยมี myValue เป็นตัวแปรสมาชิกของอินสแตนซ์ StateMachine ที่ค้นหา แต่ lambda จะต้องใช้การจัดสรรหน่วยความจําเพิ่มเติมเมื่อเทียบกับการใช้อินเทอร์เฟซ Consumer<SkyValue> ในการใช้งาน StateMachine แลมบ์ดายังคงเป็นประโยชน์เมื่อมีการค้นหาหลายรายการที่จะกํากวม

นอกจากนี้ ยังมีข้อผิดพลาดในการจัดการการใช้งาน Tasks.lookUp มากเกินไป ซึ่งคล้ายกับ SkyFunction.Environment.getValueOrThrow

  <E extends Exception> void lookUp(
      SkyKey key, Class<E> exceptionClass, ValueOrExceptionSink<E> sink);

  interface ValueOrExceptionSink<E extends Exception> {
    void acceptValueOrException(@Nullable SkyValue value, @Nullable E exception);
  }

ตัวอย่างการติดตั้งใช้งานมีดังนี้

class PerformLookupWithError extends StateMachine, ValueOrExceptionSink<MyException> {
  private MyValue value;
  private MyException error;

  @Override
  public StateMachine step(Tasks tasks) {
    tasks.lookUp(new MyKey(), MyException.class, ValueOrExceptionSink<MyException>) this);
    return this::processResult;
  }

  @Override
  public acceptValueOrException(@Nullable SkyValue value, @Nullable MyException exception) {
    if (value != null) {
      this.value = (MyValue)value;
      return;
    }
    if (exception != null) {
      this.error = exception;
      return;
    }
    throw new IllegalArgumentException("Both parameters were unexpectedly null.");
  }

  private StateMachine processResult(Tasks tasks) {
    if (exception != null) {
      // Handles the error.
      …
      return DONE;
    }
    // Processes `value`, which is non-null.
    …
  }
}

การค้นหาคลาส StateMachine โดยตรงจะช่วยประหยัดหน่วยความจําสําหรับ LAMB เช่นเดียวกับการค้นหาที่ไม่มีการจัดการข้อผิดพลาด

การจัดการข้อผิดพลาดให้รายละเอียดเพิ่มเติมเล็กน้อย แต่โดยพื้นฐานแล้ว การแพร่ข้อผิดพลาดและค่าปกติจะไม่มีความแตกต่างกัน

ใช้ SkyValues หลายรายการ

ต้องมีการค้นหา SkyValue หลายครั้ง วิธีการทํางานแบบส่วนใหญ่ คือสลับประเภทของ SkyValue ต่อไปนี้คือตัวอย่างง่ายๆ จาก โค้ดการสร้างต้นแบบ

  @Nullable
  private StateMachine fetchConfigurationAndPackage(Tasks tasks) {
    var configurationKey = configuredTarget.getConfigurationKey();
    if (configurationKey != null) {
      tasks.lookUp(configurationKey, (Consumer<SkyValue>) this);
    }

    var packageId = configuredTarget.getLabel().getPackageIdentifier();
    tasks.lookUp(PackageValue.key(packageId), (Consumer<SkyValue>) this);

    return this::constructResult;
  }

  @Override  // Implementation of `Consumer<SkyValue>`.
  public void accept(SkyValue value) {
    if (value instanceof BuildConfigurationValue) {
      this.configurationValue = (BuildConfigurationValue) value;
      return;
    }
    if (value instanceof PackageValue) {
      this.pkg = ((PackageValue) value).getPackage();
      return;
    }
    throw new IllegalArgumentException("unexpected value: " + value);
  }

การใช้โค้ดเรียกกลับ Consumer<SkyValue> สามารถแชร์ได้อย่างตรงไปตรงมาเนื่องจากประเภทค่าต่างกัน เมื่อไม่ได้อยู่ในกรณีดังกล่าว ให้เปลี่ยนไปใช้การติดตั้งใช้งานแบบ lambda หรืออินสแตนซ์แบบเต็มภายในซึ่งใช้งานโค้ดเรียกกลับที่เหมาะสม

กําลังเผยแพร่ค่าระหว่าง StateMachine วินาที

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

class BarProducer implements StateMachine {
  // Callers of BarProducer implement the following interface to accept its
  // results. Exactly one of the two methods will be called by the time
  // BarProducer completes.
  interface ResultSink {
    void acceptBarValue(Bar value);
    void acceptBarError(BarException exception);
  }

  private final ResultSink sink;

  BarProducer(ResultSink sink) {
     this.sink = sink;
  }

  … // StateMachine steps that end with this::complete.

  private StateMachine complete(Tasks tasks) {
    if (hasError()) {
      sink.acceptBarError(getError());
      return DONE;
    }
    sink.acceptBarValue(getValue());
    return DONE;
  }
}

จากนั้นผู้โทร StateMachine จะมีลักษณะดังนี้

class Caller implements StateMachine, BarProducer.ResultSink {
  interface ResultSink {
    void acceptCallerValue(Bar value);
    void acceptCallerError(BarException error);
  }

  private final ResultSink sink;

  private Bar value;

  Caller(ResultSink sink) {
    this.sink = sink;
  }

  @Override
  @Nullable
  public StateMachine step(Tasks tasks) {
    tasks.enqueue(new BarProducer((BarProducer.ResultSink) this));
    return this::processResult;
  }

  @Override
  public void acceptBarValue(Bar value) {
    this.value = value;
  }

  @Override
  public void acceptBarError(BarException error) {
    sink.acceptCallerError(error);
  }

  private StateMachine processResult(Tasks tasks) {
    // Since all enqueued subtasks resolve before `processResult` starts, one of
    // the `BarResultSink` callbacks must have been called by this point.
    if (value == null) {
      return DONE;  // There was a previously reported error.
    }
    var finalResult = computeResult(value);
    sink.acceptCallerValue(finalResult);
    return DONE;
  }
}

ตัวอย่างก่อนหน้านี้แสดงถึงบางสิ่ง Caller ต้องเผยแพร่ผลทั้งหมด และกําหนด Caller.ResultSink ของตัวเอง Caller ใช้โค้ดเรียกกลับ BarProducer.ResultSink เมื่อกลับมาใช้งานได้ processResult จะตรวจสอบว่า value มีค่าเป็นศูนย์หรือไม่เพื่อตรวจสอบว่าเกิดข้อผิดพลาดหรือไม่ นี่เป็นรูปแบบพฤติกรรมที่พบได้ทั่วไป หลังจากยอมรับเอาต์พุตจากงานย่อยหรือการค้นหา SkyValue

โปรดทราบว่าการใช้ acceptBarError จะส่งต่อผลลัพธ์ไปยัง Caller.ResultSink ตามที่กําหนดโดยการฟองข้อผิดพลาด

ทางเลือกสําหรับ StateMachine ระดับบนสุดมีอธิบายไว้ใน Driver และเชื่อมต่อไปยัง SkyFunctions

เกิดข้อผิดพลาดในการจัดการ

มีตัวอย่างการจัดการข้อผิดพลาดอยู่ 2 แบบแล้วในTasks.lookUp การเรียกกลับและการเผยแพร่ค่าระหว่าง StateMachines ข้อยกเว้น ยกเว้น InterruptedException จะไม่มีการส่ง แต่ส่งกลับมาผ่าน โค้ดเรียกกลับเป็นค่า โค้ดเรียกกลับดังกล่าวมักจะมีความหมายเฉพาะตัวหรือมีความหมาย โดยเป็นค่าหรือข้อผิดพลาดอย่างใดอย่างหนึ่งที่เกิดขึ้น

ส่วนถัดไปจะอธิบายการโต้ตอบที่สําคัญเล็กน้อย แต่กับการจัดการข้อผิดพลาดของ Skyframe

เกิดข้อผิดพลาดในการฟองอากาศ (--nokeep_going)

ในระหว่างการฟองอากาศข้อผิดพลาด SkyFunction อาจรีสตาร์ทแม้ว่า SkyValues ที่ขอทั้งหมดจะไม่พร้อมใช้งาน ในกรณีดังกล่าว จะไม่มีการเข้าถึงสถานะต่อๆ มาตามสัญญา API ของ Tasks อย่างไรก็ตาม StateMachine ควรเผยแพร่ข้อยกเว้นต่อไป

เนื่องจากการเผยแพร่ต้องเกิดขึ้นไม่ว่าสถานะถัดไปจะมาถึงหรือไม่ การเรียกกลับที่เกิดข้อผิดพลาดจะต้องทํางานนี้ สําหรับStateMachineภายใน ระบบจะดึงการเรียกใช้กลับระดับบนสุด

ที่ระดับบนสุด StateMachine ซึ่งเชื่อมต่อกับ SkyFunction จะทําได้โดยการเรียกเมธอด setException ของ ValueOrExceptionProducer จากนั้น ValueOrExceptionProducer.tryProduceValue จะแสดงข้อยกเว้น แม้ว่าจะไม่มี SkyValues ก็ตาม

หากมีการใช้ Driver โดยตรง คุณจําเป็นต้องตรวจหาข้อผิดพลาดที่พิสูจน์แล้วจาก SkyFunction แม้ว่าเครื่องจะยังประมวลผลไม่เสร็จสิ้นก็ตาม

การจัดการกิจกรรม

สําหรับ SkyFunction ที่จําเป็นต้องปล่อยเหตุการณ์ ระบบจะแทรก StoredEventHandler ลงใน SkyKeyComputeState และแทรกอีกใน StateMachine ที่ต้องการ ข้อมูลย้อนหลังจําเป็นต้องใช้ StoredEventHandler เนื่องจาก Skyframe ยกเลิกกิจกรรมบางอย่าง เว้นแต่จะมีการเล่นซ้ํา แต่ปัญหานี้ได้รับการแก้ไขในภายหลัง การแทรก StoredEventHandler ถูกเก็บรักษาไว้เนื่องจากช่วยลดความยุ่งยากในการใช้งานเหตุการณ์ที่เกิดจากการเรียกกลับเพื่อจัดการข้อผิดพลาด

Driver และการบริดจ์ไปยัง SkyFunctions

Driver จะเป็นผู้รับผิดชอบในการจัดการการดําเนินการของ StateMachine เริ่มต้นด้วยรูทที่ระบุ StateMachine เนื่องจาก StateMachine สามารถจัดลําดับงานย่อย StateMachine ได้ซ้ําๆ Driver งานเดียวจึงสามารถจัดการงานย่อยต่างๆ ได้มากมาย งานย่อยเหล่านี้จะสร้างโครงสร้างแบบต้นไม้ซึ่งเป็นผลมาจากการเกิดขึ้นพร้อมกันที่มีโครงสร้าง Driver รวมการค้นหา SkyValue ระหว่างงานย่อยทั้งหมดเพื่อเพิ่มประสิทธิภาพ

มีคลาสจํานวนมากที่สร้างขึ้นในช่วง Driver ด้วย API ต่อไปนี้

public final class Driver {
  public Driver(StateMachine root);
  public boolean drive(SkyFunction.Environment env) throws InterruptedException;
}

Driver จะใช้ราก StateMachine เดียวเป็นพารามิเตอร์ การเรียก Driver.drive จะเรียกใช้ StateMachine ให้ไกลสุดเท่าที่จะทําได้โดยไม่มีการรีสตาร์ท Skyframe โดยจะแสดงผลเป็นจริงเมื่อ StateMachine เสร็จสมบูรณ์และเป็น "เท็จ" เพื่อระบุว่ามีบางค่าที่ใช้ได้

Driver รักษาสถานะพร้อมกันของ StateMachine และเหมาะสําหรับการฝังใน SkyKeyComputeState

กําลังสร้างอินสแตนซ์ Driver โดยตรง

การใช้งาน StateMachine จะสื่อสารผลลัพธ์ผ่านโค้ดเรียกกลับตามเดิม คุณสามารถสร้างอินสแตนซ์ Driver ได้โดยตรงดังที่แสดงในตัวอย่างต่อไปนี้

Driver ฝังอยู่ในการใช้งาน SkyKeyComputeState พร้อมกับการติดตั้งใช้งาน ResultSink ที่เกี่ยวข้องเพื่อให้ถูกนิยามต่อไปอีกเล็กน้อย ในระดับบนสุด ออบเจ็กต์ State เป็นรีซีฟเวอร์ที่เหมาะสมสําหรับการคํานวณที่คํานวณได้เนื่องจากมีอายุการใช้งานนานกว่า Driver

class State implements SkyKeyComputeState, ResultProducer.ResultSink {
  // The `Driver` instance, containing the full tree of all `StateMachine`
  // states. Responsible for calling `StateMachine.step` implementations when
  // asynchronous values are available and performing batched SkyFrame lookups.
  //
  // Non-null while `result` is being computed.
  private Driver resultProducer;

  // Variable for storing the result of the `StateMachine`
  //
  // Will be non-null after the computation completes.
  //
  private ResultType result;

  // Implements `ResultProducer.ResultSink`.
  //
  // `ResultProducer` propagates its final value through a callback that is
  // implemented here.
  @Override
  public void acceptResult(ResultType result) {
    this.result = result;
  }
}

โค้ดด้านล่างร่าง ResultProducer

class ResultProducer implements StateMachine {
  interface ResultSink {
    void acceptResult(ResultType value);
  }

  private final Parameters parameters;
  private final ResultSink sink;

  … // Other internal state.

  ResultProducer(Parameters parameters, ResultSink sink) {
    this.parameters = parameters;
    this.sink = sink;
  }

  @Override
  public StateMachine step(Tasks tasks) {
    …  // Implementation.
    return this::complete;
  }

  private StateMachine complete(Tasks tasks) {
    sink.acceptResult(getResult());
    return DONE;
  }
}

จากนั้นโค้ดสําหรับการประมวลผลแบบ Lazy Loading ผลลัพธ์อาจมีลักษณะดังนี้

@Nullable
private Result computeResult(State state, Skyfunction.Environment env)
    throws InterruptedException {
  if (state.result != null) {
    return state.result;
  }
  if (state.resultProducer == null) {
    state.resultProducer = new Driver(new ResultProducer(
      new Parameters(), (ResultProducer.ResultSink)state));
  }
  if (state.resultProducer.drive(env)) {
    // Clears the `Driver` instance as it is no longer needed.
    state.resultProducer = null;
  }
  return state.result;
}

การฝัง Driver

หาก StateMachine สร้างค่าและไม่มีข้อยกเว้น การฝัง Driver อาจเป็นอีกหนึ่งการดําเนินการที่เป็นไปได้ ดังที่แสดงในตัวอย่างต่อไปนี้

class ResultProducer implements StateMachine {
  private final Parameters parameters;
  private final Driver driver;

  private ResultType result;

  ResultProducer(Parameters parameters) {
    this.parameters = parameters;
    this.driver = new Driver(this);
  }

  @Nullable  // Null when a Skyframe restart is needed.
  public ResultType tryProduceValue( SkyFunction.Environment env)
      throws InterruptedException {
    if (!driver.drive(env)) {
      return null;
    }
    return result;
  }

  @Override
  public StateMachine step(Tasks tasks) {
    …  // Implementation.
}

SkyFunction อาจมีโค้ดที่มีลักษณะดังนี้ (โดยที่ State คือฟังก์ชันเฉพาะประเภท SkyKeyComputeState)

@Nullable  // Null when a Skyframe restart is needed.
Result computeResult(SkyFunction.Environment env, State state)
    throws InterruptedException {
  if (state.result != null) {
    return state.result;
  }
  if (state.resultProducer == null) {
    state.resultProducer = new ResultProducer(new Parameters());
  }
  var result = state.resultProducer.tryProduceValue(env);
  if (result == null) {
    return null;
  }
  state.resultProducer = null;
  return state.result = result;
}

การฝัง Driver ในการติดตั้งใช้งาน StateMachine เหมาะกับรูปแบบการเขียนโค้ดแบบซิงโครนัสของ SkyFrame มากกว่า

เครื่องคอมพิวเตอร์ที่อาจทําให้เกิดข้อยกเว้น

มิฉะนั้นจะมีคลาส SkyKeyComputeState ที่ฝังได้ ValueOrExceptionProducer และ ValueOrException2Producer ซึ่งมี API แบบซิงโครนัสเพื่อให้ตรงกับโค้ด SkyFunction แบบซิงโครนัส

คลาสนามธรรม ValueOrExceptionProducer จะมีเมธอดต่อไปนี้

public abstract class ValueOrExceptionProducer<V, E extends Exception>
    implements StateMachine {
  @Nullable
  public final V tryProduceValue(Environment env)
      throws InterruptedException, E {
    …  // Implementation.
  }

  protected final void setValue(V value)  {  … // Implementation. }
  protected final void setException(E exception) {  … // Implementation. }
}

ซึ่งรวมถึงอินสแตนซ์ Driver ที่ฝัง และมีลักษณะคล้ายกับคลาส ResultProducer ในไดรเวอร์แบบฝังและอินเทอร์เฟซกับ SkyFunction ในลักษณะคล้ายกัน แทนที่จะกําหนด ResultSink การติดตั้งใช้งานจะเรียกใช้ setValue หรือ setException เมื่อเกิดเหตุการณ์ใดเหตุการณ์หนึ่งข้างต้น เมื่อเหตุการณ์ทั้งสองเกิดขึ้น ข้อยกเว้นจะได้รับการจัดลําดับความสําคัญก่อน เมธอด tryProduceValue จะเชื่อมโยงโค้ดเรียกกลับแบบไม่พร้อมกันไปยังโค้ดซิงโครนัสและทิ้งข้อยกเว้นเมื่อตั้งค่าไว้

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

คําใบ้: ในที่สุดก็เอาการเรียกกลับออก

StateMachine เป็นวิธีขั้นสูงที่มีประสิทธิภาพสูงแต่ต้องใช้เวลาในการประมวลผลในระดับสูงไม่พร้อมกัน ความต่อเนื่อง (โดยเฉพาะอย่างยิ่งในรูปแบบ Runnable ที่ส่งไปยัง ListenableFuture) ถูกกระจายอย่างกว้างขวางในบางส่วนของโค้ด Bazel แต่ไม่เป็นที่นิยมในการวิเคราะห์ SkyFunctions การวิเคราะห์มักจะเชื่อมโยงกับ CPU และไม่มี API แบบอะซิงโครนัสที่มีประสิทธิภาพสําหรับดิสก์ I/O ท้ายที่สุดแล้ว การเพิ่มประสิทธิภาพโค้ดเรียกกลับก็จะเป็นการดี เพราะมีเส้นโค้งการเรียนรู้และเพิ่มความสามารถในการอ่านได้

อีกทางเลือกที่น่าสนใจที่สุดคือชุดข้อความเสมือนจริงของ Java แทนที่จะต้องเขียนการเรียกกลับ ทุกอย่างจะแทนที่ด้วยการเรียกแบบบล็อกพร้อมกัน กรณีนี้เป็นไปได้เนื่องจากการรวมทรัพยากรของชุดข้อความเสมือน ซึ่งต่างจากชุดข้อความของแพลตฟอร์มควรจะเป็นราคาถูก อย่างไรก็ตาม แม้จะมีชุดข้อความเสมือน แต่การแทนที่การดําเนินการแบบซิงโครนัสแบบง่ายๆ ด้วยการสร้างชุดข้อความและการซิงค์ข้อมูลชั่วคราวก็แพงเกินไป เราดําเนินการย้ายชุดข้อความเสมือนจาก StateMachine ไปยัง Java เสมือน ซึ่งเป็นลําดับของขนาดที่ช้ากว่า ซึ่งทําให้เวลาในการตอบสนองการวิเคราะห์จากต้นทางถึงปลายทางเพิ่มขึ้นเกือบ 3 เท่า เนื่องจากชุดข้อความเสมือนยังคงเป็นฟีเจอร์แสดงตัวอย่าง จึงเป็นไปได้ว่าการย้ายข้อมูลนี้อาจทําได้ในภายหลังเมื่อประสิทธิภาพดีขึ้น

อีกวิธีการหนึ่งที่ควรพิจารณาคือการรอรับโครูนิสต์ของ Loom ที่พร้อมให้บริการ ข้อดีของตรงนี้คืออาจช่วยลด การซิงค์ข้อมูลในการดําเนินการโดยใช้การทํางานหลายอย่างพร้อมกัน

หากการดําเนินการอื่นๆ ไม่สามารถทําได้ การเขียนไบต์ใหม่ในระดับล่างก็อาจกลายเป็นทางเลือกที่ใช้ได้ เมื่อมีการเพิ่มประสิทธิภาพมากพอ ก็อาจจะบรรลุประสิทธิภาพที่ใกล้โค้ดเรียกกลับที่เขียนด้วยลายมือ

ภาคผนวก

Call Hell

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

class CallbackHell implements StateMachine {
  @Override
  public StateMachine step(Tasks task) {
    doA();
    return (t, l) -> {
      doB();
      return (t1, l2) -> {
        doC();
        return DONE;
      };
    };
  }
}

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

class CallbackHellAvoided implements StateMachine {
  @Override
  public StateMachine step(Tasks task) {
    doA();
    return this::step2;
  }

  private StateMachine step2(Tasks tasks) {
    doB();
    return this::step3;
  }

  private StateMachine step3(Tasks tasks) {
    doC();
    return DONE;
  }
}

อย่างไรก็ตาม อาจมีนรกย้อนกลับแสดงขึ้นหากมีการใช้รูปแบบการแทรก runAfter มากเกินไป

ตัวอย่าง: การค้นหา SkyValue แบบห่วงโซ่

ส่วนใหญ่แล้วตรรกะลอจิกของแอปพลิเคชันจําเป็นต้องใช้เชนที่ขึ้นอยู่กับการค้นหา SkyValue ตัวอย่างเช่น หาก SkyKey ที่สองขึ้นอยู่กับ SkyValue แรก เมื่อคิดในแง่นี้ แบบนี้จะส่งผลให้เกิดโครงสร้างการเรียกกลับที่ซับซ้อนและซับซ้อน

private ValueType1 value1;
private ValueType2 value2;

private StateMachine step1(...) {
  tasks.lookUp(key1, (Consumer<SkyValue>) this);  // key1 has type KeyType1.
  return this::step2;
}

@Override
public void accept(SkyValue value) {
  this.value1 = (ValueType1) value;
}

private StateMachine step2(...) {
  KeyType2 key2 = computeKey(value1);
  tasks.lookup(key2, this::acceptValueType2);
  return this::step3;
}

private void acceptValueType2(SkyValue value) {
  this.value2 = (ValueType2) value;
}

อย่างไรก็ตาม เนื่องจากมีการระบุต่อไปว่าเป็นการอ้างอิงเมธอด โค้ดดังกล่าวจะดูเหมือนเป็นกระบวนการเปลี่ยนแปลงระหว่างสถานะ: step2 ติดตาม step1 โปรดทราบว่าตรงนี้จะใช้ lambda เพื่อกําหนด value2 สิ่งนี้จะทําให้การเรียงลําดับของโค้ด ตรงกับการจัดลําดับการคํานวณจากบนลงล่าง

เคล็ดลับเบ็ดเตล็ด

ความอ่านง่าย: ลําดับการดําเนินการ

เพื่อปรับปรุงให้อ่านเนื้อหาได้ง่ายขึ้น ให้พยายามทําให้การใช้งาน StateMachine.step อยู่ในลําดับการเรียกใช้งานและโค้ดเรียกกลับทันทีที่โค้ดได้รับ ในบางครั้งฝั่งควบคุมก็ไม่มีทาง ความคิดเห็นเพิ่มเติมอาจเป็นประโยชน์ในกรณีนี้

ในตัวอย่าง: การค้นหา SkyValue แบบเชน ระบบจะสร้างการอ้างอิงเมธอดระดับกลางเพื่อให้บรรลุเป้าหมายนี้ วิธีนี้ทําให้เห็นประสิทธิภาพการทํางานเพียงเล็กน้อย ซึ่งสามารถอ่านได้ที่นี่

สมมติฐานการสร้าง

วัตถุ Java ที่มีอายุการใช้งานปานกลางจะทําลายสมมติฐานการสร้างตัวเก็บขยะ Java ซึ่งออกแบบมาเพื่อจัดการกับวัตถุที่คงอยู่เป็นระยะเวลาสั้นๆ หรือวัตถุที่อยู่ได้ชั่วนิรันดร์ ตามคําจํากัดความแล้ว ออบเจ็กต์ใน SkyKeyComputeState ละเมิดสมมติฐานนี้ ออบเจ็กต์ดังกล่าวซึ่งมีโครงสร้างที่สร้างขึ้นของ StateMachine ทั้งหมดที่กําลังทํางานอยู่ ซึ่งรูทที่ Driver จะมีอายุการใช้งานขั้นกลางขณะที่ระงับเพื่อรอการคํานวณแบบไม่เสร็จ

ฟังก์ชันดังกล่าวอาจดูแย่ใน JDK19 แต่เมื่อใช้ StateMachine ก็อาจสังเกตเห็นถึงการเพิ่มขึ้นของ GC ได้ ถึงแม้ว่าจะมีมูลขยะจริงที่ลดลงอย่างมากก็ตาม เนื่องจาก StateMachine มีช่วงชีวิตแบบระยะใกล้ จึงอาจได้รับการโปรโมตเป็นรุ่นเก่า ทําให้เติมข้อมูลได้เร็วขึ้น จึงจําเป็นต้องล้างข้อมูล GC หลักหรือเวอร์ชันเต็มที่มีราคาแพง

ข้อควรระวังเบื้องต้นคือการลดการใช้งานตัวแปร StateMachine ให้เหลือน้อยที่สุด แต่ในบางกรณี เช่น ไม่จําเป็นต้องระบุค่าในหลายรัฐ หากเป็นไปได้ ตัวแปรสแต็ก step ในเครื่องจะเป็นตัวแปรรุ่นใหม่และ GC มีประสิทธิภาพ

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

สุดท้าย ในบางกรณี อาจต้องใช้ตัวแปร StateMachine ในสถานะก่อนๆ แต่ไม่ต้องใช้ในสถานะถัดไป การเว้นว่างการอ้างอิงออบเจ็กต์ขนาดใหญ่จะเป็นประโยชน์เมื่อทราบว่าไม่จําเป็นต้องใช้อีกต่อไป

สถานะการตั้งชื่อ

เมื่อตั้งชื่อเมธอด โดยปกติแล้วคุณจะตั้งชื่อเมธอดสําหรับพฤติกรรมที่เกิดขึ้นภายในเมธอดนั้นได้ ซึ่งดูไม่ค่อยชัดในวิธีทํา StateMachine เพราะไม่มีภาพซ้อน เช่น สมมติว่าเมธอด foo เรียกเมธอดย่อย bar ในStateMachineอาจแปลเป็นลําดับรัฐ foo ตามด้วย bar foo ไม่มีลักษณะการทํางาน bar แล้ว ดังนั้น ชื่อวิธีการสําหรับรัฐจึงมีแนวโน้มที่จะจํากัดขอบเขตให้แคบลง ซึ่งอาจแสดงถึงพฤติกรรมในเครื่อง

แผนภาพแผนผังการเกิดขึ้นพร้อมกัน

ต่อไปนี้คือมุมมองอื่นของแผนภาพในการเกิดขึ้นพร้อมกันที่มีโครงสร้าง ซึ่งแสดงถึงโครงสร้างต้นไม้ได้ดีขึ้น ส่วนบล็อกจะเป็นต้นไม้เล็กๆ

การเกิดขึ้นพร้อมกันที่มีโครงสร้าง 3 มิติ


  1. ในทางตรงกันข้ามกับรูปแบบแบบเดิมของ Skyframe ที่จะเริ่มต้นใหม่ตั้งแต่ต้น เมื่อไม่มีค่า

  2. โปรดทราบว่า step ได้รับอนุญาตให้นํา InterruptedException ไปใช้ได้ แต่ตัวอย่างนี้กลับไม่แสดง โค้ด Bazel มีเมธอดที่ผ่านข้อยกเว้นนี้เป็นจํานวนไม่มาก และเผยแพร่เป็น Driver เพื่ออธิบายในภายหลังซึ่งจะเรียกใช้ StateMachine การไม่ประกาศว่านํามาโยนที่ไม่จําเป็น

  3. งานย่อยที่เกิดขึ้นพร้อมกันได้รับแรงจูงใจจาก ConfiguredTargetFunction ซึ่งทํางานอิสระในการพึ่งพาแต่ละรายการ แทนที่จะจัดการโครงสร้างข้อมูลที่ซับซ้อนซึ่งประมวลผลทรัพยากร Dependency ทั้งหมดพร้อมกัน การแนะนําสิ่งที่ไร้ประสิทธิภาพนั้น ทรัพยากร Dependency แต่ละรายการนั้นมี StateMachine ของตนเองแยกต่างหาก

  4. การโทรจาก tasks.lookUp หลายรายการภายในขั้นตอนเดียวจะจัดกลุ่มเข้าด้วยกัน กลุ่มสามารถสร้างเพิ่มเติมได้โดยการค้นหาที่เกิดขึ้นภายในงานย่อยที่เกิดขึ้นพร้อมกัน

  5. แนวคิดนี้มีแนวคิดคล้ายกับการเกิดขึ้นพร้อมกันของ Javajeps/428

  6. การดําเนินการนี้คล้ายกับการสร้างชุดข้อความและเข้าร่วมในชุดข้อความดังกล่าวเพื่อให้มีการเรียงองค์ประกอบตามลําดับ