yield java_Java Memory:认识Yield Point

YieldPoint是Managed Language虚拟机实现中一个重要的技术点,恰好最近梳理内存管理技术,发现ISMM 2015年一篇论文《Stop and go: understanding yieldpoint behavior》,是一篇非常不错的入门学习材料。本篇论文也是我们项目组专家王坤山发表的一篇文章,一些不懂的问题刚好可以找他咨询。尽管yieldpoint是个独立的技术,但是内存管理GC是依赖这个技术的。

1,什么是Yield Point?

A yieldpoint is a frequently executed check by managed application code in high performance managed run-time systems, used to determine when a thread must yield。虚拟机(例如JVM)在执行managed code(如java bytecode)时候,thread需要在一些点停下来去做一些任务,例如GC,用户态线程主动调度,解释代码被替换为编译代码等。如何高效实现线程的停顿机制,就是YieldPoint技术。

Yield Point在不同的虚拟机也被称为CheckPoint,SafePoint等。

2,如何实现Yield Point?

本论文中提到了三种实现Yield Point的方法,应该也是目前可行的所有方法。

2.1 实现YieldPoint有三种方法

方法1:a conditional guarded by a state variable

编译器或者解释器执行的时候,比较一个全局变量的值。如果值满足一定条件,则调用处理yieldpoint的代码(The compiler injects a constant comparison against the value of the variable and a conditional jump to the slow path on true)。

此方法的优点是:flexibility,全局变量值可以是多种,不同的场景可以设置不同的值,然后调用不同处理yieldpoint的函数。缺点应该是因为yieldpoint本身很多,导致codesize变大。

方法2:an unconditional load or store from/to a guard page

通过load/write指令读写一个guard page内存,正常的时候此guardpage属性设置为可读或可写,当需要执行yieldpoint的时候,通过设置此page属性不可读写,然后会触发OS系统的SEGV signal,进入注册的信号处理函数,信号处理函数通过判断当前执行的指令序列或者发生问题的地址,去判断是否触发了yieldpoint,然后进行特定的处理函数。

方法3:code patching

NOP patching. 编译器在编译的时候插入nop指令在特定的位置,当需要yieldpoint的时候,动态替换这些NOP指令为yieldpoint的处理函数。此种方法的优势是执行的时候性能开销非常低,但是替换nop指令耗时。同时也不适用类似方舟编译器这种HOST AOT编译器场景。

2.2 插入YieldPoint的时机:

yieldpoints typically occur on loop back edges and on method prologs or epilogs of the application。虚拟机一般有三种执行代码的方式:解释模式,JIT模式及其AOT预编译

JIT/AOT模式:通过编译器产生机器指令,在合适的位置插入YeildPoint指令

解释器模式:解释执行完每个字节码之后去check是否需要Yield。可能不同的虚拟机check的位置不同。

2.3 Scope分类

Global YieldPoint:turned on and off for all application thread,适合stop the world场景。可以用a global conditional variable, a single global protected page

Thread-Scope YieldPoint:turned on and off for a single thread or a group of threads,适合一个暂停一个线程或者一组线程。可以用a thread-local condition variable or thread-local protected page。

Group-based Yieldpoints:暂停一组线程,常用的实现方法是基于条件变量或者code patching的方式实现。

3,YieldPoint实现举例

3.1 ART虚拟机

ART虚拟机YieldPoint被称为CheckPoint/SuspendCheck。ART实现的基于thread-scope的YieldPoint。在每个线程的tls变量中,增加一个指针变量/状态:YieldPoint定义:

//定义在thread.h // In certain modes, setting this to 0 will trigger a SEGV and thus a suspend check. // It is normally set to the address of itself. uintptr_t* suspend_trigger;

// Trigger a suspend check by making the suspend_trigger_ TLS value an invalid pointer. // The next time a suspend check is done, it will load from the value at this address // and trigger a SIGSEGV. void TriggerSuspend() {

tlsPtr_.suspend_trigger = nullptr;

}

//设置成自己的地址 void RemoveSuspendTrigger() {

tlsPtr_.suspend_trigger = reinterpret_cast(&tlsPtr_.suspend_trigger);

}

template

static constexpr ThreadOffset ThreadSuspendTriggerOffset() {

return ThreadOffsetFromTlsPtr(

OFFSETOF_MEMBER(tls_ptr_sized_values, suspend_trigger));

}

...

union StateAndFlags state_and_flags;YieldPoint的指令插入:

1. JIT/AOT编译器插入

ART使用的IR SuspendCheck,这块代码没有仔细研究,代码主要集中在:

// optimizing/code_generator_arm64.cc:

void InstructionCodeGeneratorARM64::GenerateSuspendCheck(HSuspendCheck* instruction,

...

// 产生testsuspend指令

void EmitNativeCode(CodeGenerator* codegen) override {

LocationSummary* locations = instruction_->GetLocations();

CodeGeneratorARM64* arm64_codegen = down_cast(codegen);

__ Bind(GetEntryLabel());

SaveLiveRegisters(codegen, locations); // Only saves live 128-bit regs for SIMD.

arm64_codegen->InvokeRuntime(kQuickTestSuspend, instruction_, instruction_->GetDexPc(), this);

CheckEntrypointTypes();

RestoreLiveRegisters(codegen, locations); // Only restores live 128-bit regs for SIMD.

if (successor_ == nullptr) {

__ B(GetReturnLabel());

} else {

__ B(arm64_codegen->GetLabelOf(successor_));

}

}

// quick_entrypoints_list.h

//art/runtime/entrypoints/quick/quick_default_init_entrypoints.h

// Thread

qpoints->pTestSuspend = art_quick_test_suspend;

//register_allocator_linear_scan.cc

2. 解释器:调用MterpSuspendCheck函数

inline void Thread::AllowThreadSuspension() {

DCHECK_EQ(Thread::Current(), this);

if (UNLIKELY(TestAllFlags())) {

CheckSuspend();

}

}

3. Runtime:主动调用SuspendCheck函数RequestCheckpoint/RequestEmptyCheckpoint/ModifySuspendCountInternal中按照不同场景也会调用触发suspend。

YieldPoint触发处理:ART虚拟机处理AOT/JIT YieldPoint通过触发SIGSEGV处理(ARR已经废弃此方法,implicit_suspend_checks_选项已经没有用了)

// ARM64为例

// art/runtime/arch/arm64/fault_handler_arm64.cc

bool SuspensionHandler::Action(int sig ATTRIBUTE_UNUSED, siginfo_t* info ATTRIBUTE_UNUSED,

void* context) {

....

//判断触发的SIGSEGV是不是yieldpoint指令

1. 首先判断是不是ldr x0, [x0]

2. 然后向上搜索ldr x0, [x18, ,#xxx],x18是tls线程寄存器,x18+offset是suspend_trigger偏移

//如果匹配

pc设置为art_quick_implicit_suspend执行挂载函数

//恢复

}

2. ART另外一种方式通过a conditional guarded by a state variable

上面也阐述了,其实调用的是art_quick_test_suspend

3. 解释器模式

直接调用CheckSuspend处理对应的CheckPoint函数。

3.2 方舟编译器

目前方舟编译器是全静态编译器,yieldpoint的实现方式是方法2,即通过编译器插入指令访问guard page触发信号。编译器插入可以参考开源代码:https://gitee.com/openarkcompiler-incubator/mapleall/tree/master/maple_be/src/cg/aarch64/aarch64_cg_func.cpp

void AArch64CGFunc::GenerateYieldpoint(BB * bb) {

// ldr wzr, [RYP] # RYP hold address of the polling page. auto &wzr = AArch64RegOperand::Get32bitZeroRegister();

auto pollingPage = CreateMemOpnd(RYP, 0, 32);

auto yieldPoint = cg->BuildInstruction(MOP_wldr, &wzr, pollingPage);

if (cg->GenerateVerboseAsm()) {

yieldPoint->AddComment("yieldpoint");

}

bb->AppendInsn(yieldPoint);

}

编译器yieldpoint规则:

AArch64CGFunc::HandleRCCall:在函数头所有引用变量初始化完以后插入

AArch64YieldPointInsertion::InsertYieldPoint:Epilogue/循环回边插入

运行时还未开源:

通过注册SIGSEGV 信号处理函数,判断触发信号的地址是否polling page?如果是,则发生了yieldpoint,处理寄存器保存等各种操作。

auto &wzr = AArch64RegOperand::Get32bitZeroRegister();

ldr wzr, [RYP] # RYP hold address of the polling page.

运行时将页面设置为不可读。

参考:(PDF) Stop and go: understanding yieldpoint behavior​www.researchgate.netyield java_Java Memory:认识Yield Point_第1张图片ART是如何保证checkpoint 点一定会被跑到的?​www.zhihu.com

你可能感兴趣的:(yield,java)