ภาพรวม
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
ของ StateMachine
s และ
สรุปการดำเนินการที่ซับซ้อนซึ่งจะเกิดขึ้นเมื่อไม่พร้อมกันทั้งหมด
คำนวณได้ง่ายขึ้น แนวทางที่มีโครงสร้างนี้ช่วยให้
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
ด้วยเหตุนี้ ชื่อเมธอดสำหรับรัฐต่างๆ มักจะมีขอบเขตแคบกว่า
ซึ่งอาจสะท้อนให้เห็นถึงพฤติกรรมในพื้นที่
แผนภาพแผนผังการเกิดขึ้นพร้อมกัน
ต่อไปนี้เป็นอีกมุมมองหนึ่งของแผนภาพในโครงสร้าง การเกิดขึ้นพร้อมกันที่แสดงโครงสร้างแบบต้นไม้ได้ดีขึ้น บล็อกที่สร้างจากต้นไม้ขนาดเล็ก
-
ตรงข้ามกับแบบแผนของ Skyframe ที่จะรีสตาร์ทตั้งแต่ต้น ไม่สามารถใช้ได้↩
-
โปรดทราบว่า
step
ได้รับอนุญาตให้ส่งInterruptedException
แต่ ละเว้นข้อความนี้ได้ มีเมธอดต่ำ 2-3 รายการในโค้ด Bazel ที่ส่ง ข้อยกเว้นนี้ และจะมีผลกับDriver
ซึ่งจะอธิบายภายหลัง ที่เรียกใช้StateMachine
ไม่ต้องประกาศให้ส่งเมื่อ ที่ไม่จำเป็น↩ -
งานย่อยที่ทำพร้อมกันได้รับแรงจูงใจจาก
ConfiguredTargetFunction
ที่ จะทำงานอิสระสำหรับทรัพยากร Dependency แต่ละรายการ แทนที่จะควบคุมทุกอย่าง โครงสร้างข้อมูลที่ซับซ้อนซึ่งประมวลผล ทรัพยากร Dependency ทั้งหมดพร้อมกัน ทำให้เกิดความด้อยประสิทธิภาพ ทรัพยากร Dependency แต่ละรายการมีการทำงานแบบอิสระStateMachine
↩ -
การเรียก
tasks.lookUp
หลายครั้งในขั้นตอนเดียวจะรวมกันเป็นกลุ่ม สามารถสร้างกลุ่มเพิ่มเติมได้โดยการค้นหาที่เกิดขึ้นพร้อมกัน งานย่อย↩ -
แนวคิดนี้คล้ายกับการเกิดขึ้นพร้อมกันแบบมีโครงสร้างของ Java jeps/428↩
-
วิธีนี้คล้ายกับการสร้างชุดข้อความและเข้าร่วมเพื่อให้บรรลุเป้าหมาย ตามลำดับ↩