คู่มือสำหรับ Skyframe StateMachines

วันที่ รายงานปัญหา ดูแหล่งที่มา ตอนกลางคืน · 7.3 · 7.2 · 7.1 · 7.0 · 6.5

ภาพรวม

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

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

บทนำ

ส่วนนี้กระตุ้นและแนะนำ StateMachine ที่พบใน java.com.google.devtools.build.skyframe.state ใหม่

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

Skyframe เป็นเฟรมเวิร์กที่ทำการประเมินแบบคู่ขนานของกราฟทรัพยากร Dependency แต่ละโหนดในกราฟจะสอดคล้องกับการประเมิน SkyFunction ที่ SkyKey การระบุพารามิเตอร์และ SkyValue ที่ระบุผลลัพธ์ โมเดลการคำนวณคือ SkyFunction อาจค้นหา SkyValues ด้วย SkyKey ทริกเกอร์การประเมิน SkyFunction เพิ่มเติมแบบวนซ้ำที่เกิดซ้ำ แทนที่จะเป็น การบล็อก ซึ่งจะเชื่อมโยงชุดข้อความเมื่อ SkyValue ที่ขอยังไม่ พร้อมใช้งานเนื่องจากกราฟย่อยของการคำนวณบางอย่างไม่สมบูรณ์ SkyFunction ตรวจพบการตอบกลับ null getValue และควรแสดงผล null แทนที่จะแสดง SkyValue ซึ่งเป็นการส่งสัญญาณว่าไม่สมบูรณ์เนื่องจากไม่มีอินพุต Skyframe จะรีสตาร์ท SkyFunctions เมื่อมีการขอ 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 ขั้นตอนโดยสัญชาตญาณแทนที่จะเป็น ฟังก์ชันโมโนลิธ ให้ฮุกที่จำเป็นในการระงับและทำงานต่อ การคำนวณ เมื่อ StateMachine.step กลับมา จะมีการระงับอย่างชัดเจน คะแนน ความต่อเนื่องที่ระบุโดยค่า StateMachine ที่แสดงผลคือ จุดกลับมาทำงานอีกครั้งอย่างชัดเจน ดังนั้นจึงสามารถหลีกเลี่ยงการคำนวณซ้ำได้เพราะ คำนวณต่อจากจุดที่ค้างไว้ได้เลย

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

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

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

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

ต้องมี Callback เมื่อเรียกใช้การดำเนินการแบบไม่พร้อมกัน ซึ่งหมายความว่า การดำเนินการจริงไม่ได้เกิดขึ้นทันทีเมื่อเรียกใช้เมธอด เช่น ที่ใช้ในการค้นหา SkyValue ควรทำให้การเรียกกลับเรียบง่ายที่สุด

Continuations คือค่าการแสดงผล StateMachine ของ StateMachines และ สรุปการดำเนินการที่ซับซ้อนซึ่งจะเกิดขึ้นเมื่อไม่พร้อมกันทั้งหมด คำนวณได้ง่ายขึ้น แนวทางที่มีโครงสร้างนี้ช่วยให้ Callback ที่จัดการได้

งาน

อินเทอร์เฟซ 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 เพื่อให้ผู้โทรระบุสิ่งที่ต้องทำกับค่าที่ได้โดยใช้ Callback

โปรเซสเซอร์ 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ปกติ ซึ่งรวมถึงการสร้างงานย่อยๆ ซ้ำๆ หรือการค้นหา SkyValues ไดรเวอร์เครื่องสถานะจะคล้ายกับ 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

การแตกแขนง

สถานะสาขาใน 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;
  }
}

โฟลว์ข้อมูล

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

กําลังติดตั้งใช้งาน Callback Tasks.lookUp รายการ

มีตัวอย่างของการใช้ Callback Tasks.lookUp ใน SkyValue การค้นหา ส่วนนี้ให้เหตุผลและเสนอแนะ วิธีจัดการกับ SkyValue หลายๆ ค่า

Callback Tasks.lookUp รายการ

เมธอด Tasks.lookUp จะใช้ Callback sink เป็นพารามิเตอร์

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

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

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

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

นอกจากนี้ยังมีข้อผิดพลาดในการจัดการ 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 โดยตรง การใช้ Callback จะบันทึกการจัดสรรหน่วยความจำสำหรับ Lamba

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

การใช้ 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);
  }

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

กำลังเผยแพร่ค่าระหว่าง 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 Callback เมื่อกลับมาทํางานอีกครั้ง processResult จะตรวจสอบว่า value เป็นค่าว่างเพื่อพิจารณาว่าเกิดข้อผิดพลาดหรือไม่ นี่เป็นลักษณะการทำงานทั่วไป หลังจากยอมรับเอาต์พุตจากงานย่อยหรือการค้นหา SkyValue

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

ตัวเลือกอื่นๆ สำหรับ StateMachine ระดับบนสุดจะอธิบายไว้ใน Driver และ การบริดจ์กับ SkyFunctions

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

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

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

เกิดข้อผิดพลาดขณะทำบับเบิล (--nokeep_going)

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

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

ที่ StateMachine ระดับบนสุดซึ่งเชื่อมต่อกับ SkyFunction จะทำสิ่งต่อไปนี้ได้ ทำได้โดยการเรียกเมธอด setException ของ ValueOrExceptionProducer ValueOrExceptionProducer.tryProduceValue จะแสดงข้อผิดพลาด หากไม่มี SkyValues

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

การจัดการเหตุการณ์

สำหรับ SkyFunctions ที่ต้องการปล่อยเหตุการณ์ ระบบจะแทรก StoredEventHandler ลงใน SkyKeyComputeState และแทรกเพิ่มเติมใน StateMachine ที่ต้องใช้ ให้พวกเขา ก่อนหน้านี้ต้องใช้ StoredEventHandler เนื่องจาก Skyframe ลดลง บางกิจกรรม เว้นแต่จะมีการเล่นซ้ำ แต่ต่อมาได้รับการแก้ไข มีการรักษาการแทรก StoredEventHandler ไว้เพราะทำให้ฟังก์ชัน การนำเหตุการณ์ที่เกิดจากข้อผิดพลาดในการจัดการ Callback มาใช้

Driver และการบริดจ์กับ SkyFunction

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 จะสื่อสารผลลัพธ์ตามปกติผ่าน Callback คุณสร้างอินสแตนซ์ 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

StateMachines ที่อาจสร้างข้อยกเว้น

มิเช่นนั้น จะมี ValueOrExceptionProducer แบบฝังได้ SkyKeyComputeState รายการ และ ValueOrException2Producer คลาสที่มี API แบบซิงโครนัสที่จะจับคู่ โค้ด SkyFunction แบบซิงโครนัส

คลาส Abstract ของ 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 เมื่อการดำเนินการอย่างใดอย่างหนึ่งเกิดขึ้น เมื่อทั้ง 2 กรณีเกิดขึ้น ข้อยกเว้นจะมีผลเหนือกว่า เมธอด tryProduceValue จะเชื่อมโค้ด Callback แบบไม่พร้อมกันไปยังโค้ดแบบซิงโครนัสและส่ง ยกเว้นในกรณีที่ตั้งค่าไว้

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

บทส่งท้าย: การนำ Callback ออกในที่สุด

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

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

อีกวิธีการหนึ่งที่ควรพิจารณาคือรอฟังโครูทีน Loom หากมี พร้อมให้บริการ ข้อดีคือคุณสามารถลด ในการซิงค์ด้วยระบบการทำงานหลายๆ อย่างที่ทำงานร่วมกัน

หากไม่มีวิธีใดได้ผล การเขียนไบต์โค้ดระดับต่ำใหม่ก็อาจ ทางเลือก การเพิ่มประสิทธิภาพที่มากพอ อาจทำให้ ซึ่งมีประสิทธิภาพใกล้เคียงกับโค้ด Callback ที่เขียนด้วยลายมือ

ภาคผนวก

เรียกกลับนรก

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

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

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

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;
  }
}

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

ตัวอย่าง: การค้นหา Chained SkyValue

บ่อยครั้งที่ตรรกะแอปพลิเคชันต้องใช้เชนที่ไม่ขึ้นต่อกันของ ตัวอย่างเช่น การค้นหา SkyValue ถ้า SkyValue ที่ 2 จะขึ้นอยู่กับ 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 โปรดทราบว่า มีการใช้แลมบ์ดาเพื่อกำหนด value2 ซึ่งทำให้การจัดลำดับโค้ดตรงกับ ลำดับของการคำนวณจากบนลงล่าง

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

ความอ่านง่าย: การสั่งดำเนินการ

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

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

สมมติฐานเกี่ยวกับรุ่น

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

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

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

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

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

การตั้งชื่อรัฐ

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

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

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

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


  1. ตรงข้ามกับแบบแผนของ Skyframe ที่จะรีสตาร์ทตั้งแต่ต้น ไม่สามารถใช้ได้

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

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

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

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

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