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