はじめに
Java の起動オプション CICompilerCount はコンパイラスレッドの数を指定するものである。
ドキュメント/によると CICompilerCount のデフォルト値は自動的に選択されるとあるが実際にどの様になるかソースコードと実機確認で調べた。
結論としては CICompilerCount のデフォルト値は
CPUの数を n とすると以下で計算される値、
log2(n) * log2(log2(n)) * 3 / 2
または 2 の大きい方となる。
ただし、NeverActAsServerClassMachine が有効、または TieredCompilation 無効の場合は 1 となる。
例えば CPU 4個の場合、
log2(4) = 2
log2(log2(4)) = 1
2 * 1 * 3 / 2 = 3
で CICompilerCount は 3 となる。
ソースコードの確認
OpenJDK17 のソースで対応するソースコードを確認した。ソースコードには一部、説明のためのコメントを追加した。
対応するソースコード
CICompilerCount のデフォルト値は以下の3つの部分で設定される。
- 変数CICompilerCountの定義(初期値)
- CompilerConfig での設定
- CICompilerCountConstraintFunc でのチェック
- CompilationPolicy での設定
CompilerConfigでは主にクライアントタイプの場合1を設定する。
CompilationPolicyではCPU数に基づいた値を設定する。
変数CICompilerCountの定義
オプション CICompilerCount に対応する変数 CICompilerCount の定義。
....
product(intx, CICompilerCount, CI_COMPILER_COUNT, \
"Number of compiler threads to run") \
range(0, max_jint) \
constraint(CICompilerCountConstraintFunc, AfterErgo) \
CI_COMPILER_COUNT の定義。
....
#if COMPILER2_OR_JVMCI
#define CI_COMPILER_COUNT 2
#else
#define CI_COMPILER_COUNT 1
#endif // COMPILER2_OR_JVMCI
COMPILER2_OR_JVMCI の定義は未確認。たぶん CI_COMPILER_COUNT は 2
よって CICompilerCount はこの時点では 2 になる。
CompilerConfig での設定
以下の順に呼ばれる。
Threads::create_vm() →
Arguments::apply_ergo() →
CompilerConfig::ergo_initialize() →
set_client_emulation_mode_flags()
jint Threads::create_vm(JavaVMInitArgs* args, bool* canTryAgain) {
jint ergo_result = Arguments::apply_ergo(); //★
// Initialize global modules
jint status = init_globals(); //★
jint Arguments::apply_ergo() {
....
// Set compiler flags after GC is selected and GC specific
// flags (LoopStripMiningIter) are set.
CompilerConfig::ergo_initialize(); // ★
void CompilerConfig::ergo_initialize() {
.....
if (has_c1()) {
if (!is_compilation_mode_selected()) {
#if defined(_WINDOWS) && !defined(_LP64)
if (FLAG_IS_DEFAULT(NeverActAsServerClassMachine)) {
FLAG_SET_ERGO(NeverActAsServerClassMachine, true);
}
#endif
if (NeverActAsServerClassMachine) {
set_client_emulation_mode_flags(); //★
}
} else if (!has_c2() && !is_jvmci_compiler()) {
set_client_emulation_mode_flags(); //★
}
}
void CompilerConfig::set_client_emulation_mode_flags() {
....
if (FLAG_IS_DEFAULT(CICompilerCount)) {
FLAG_SET_ERGO(CICompilerCount, 1);
}
CICompilerCount が指定されていなければ 1に設定。
set_client_emulation_mode_flags() が呼ばれる条件は
- NeverActAsServerClassMachine が有効
- C2コンパイラがない、かつ、is_jvmci_compiler がない
後者の条件はなにかの条件(オプションやHW構成など)で変わるものではないので考えなくても良いかも。
CICompilerCountConstraintFunc でのチェック
CICompilerCountConstraintFunc は Arguments::apply_ergo() の直後に検証される。
JVMFlag::Error CICompilerCountConstraintFunc(intx value, bool verbose) {
int min_number_of_compiler_threads = 0;
#if COMPILER1_OR_COMPILER2
if (CompilerConfig::is_tiered()) {
min_number_of_compiler_threads = 2;
} else if (!CompilerConfig::is_interpreter_only()) {
min_number_of_compiler_threads = 1;
}
#else
.....
if (value < (intx)min_number_of_compiler_threads) {
JVMFlag::printError(verbose,
"CICompilerCount (" INTX_FORMAT ") must be "
"at least %d \n",
value, min_number_of_compiler_threads);
階層コンパイルの場合(-XX:+TieredCompilation)は最低2。
これより小さい値を設定している場合はエラー。
CompilationPolicy での設定
以下の順に呼ばれる
Threads::create_vm() →
init_globals() →
ompilationPolicy_init() →
CompilationPolicy::initialize()
jint init_globals() {
management_init();
JvmtiExport::initialize_oop_storage();
bytecodes_init();
classLoader_init1();
compilationPolicy_init(); //★
....
void compilationPolicy_init() {
CompilationPolicy::initialize(); //★
}
void CompilationPolicy::initialize() {
if (!CompilerConfig::is_interpreter_only()) { // (1)
int count = CICompilerCount;
bool c1_only = CompilerConfig::is_c1_only();
bool c2_only = CompilerConfig::is_c2_or_jvmci_compiler_only();
#ifdef _LP64
// Turn on ergonomic compiler count selection
if (FLAG_IS_DEFAULT(CICompilerCountPerCPU) && FLAG_IS_DEFAULT(CICompilerCount)) {
FLAG_SET_DEFAULT(CICompilerCountPerCPU, true); // (2)
}
if (CICompilerCountPerCPU) { // (3)
// Simple log n seems to grow too slowly for tiered, try something faster: log n * log log n
int log_cpu = log2i(os::active_processor_count());
int loglog_cpu = log2i(MAX2(log_cpu, 1));
count = MAX2(log_cpu * loglog_cpu * 3 / 2, 2); // (4)
// Make sure there is enough space in the code cache to hold all the compiler buffers
size_t c1_size = 0;
#ifdef COMPILER1
c1_size = Compiler::code_buffer_size();
#endif
size_t c2_size = 0;
#ifdef COMPILER2
c2_size = C2Compiler::initial_code_buffer_size();
#endif
size_t buffer_size = c1_only ? c1_size : (c1_size/3 + 2*c2_size/3);
int max_count = (ReservedCodeCacheSize - (CodeCacheMinimumUseSpace DEBUG_ONLY(* 3))) / (int)buffer_size;
if (count > max_count) { // (5)
// Lower the compiler count such that all buffers fit into the code cache
count = MAX2(max_count, c1_only ? 1 : 2);
}
FLAG_SET_ERGO(CICompilerCount, count);
}
#else // _LP64 に対する else
....
(1)
is_interpreter_only(-Xint が指定されている)の場合はコンパイラは関係ないので除外(以下の処理はされない)。
(2)
CICompilerCountPerCPU はデフォルト false だがCICompilerCountPerCPU CICompilerCount 両方とも無指定の場合は true になる。
(3)
CICompilerCountPerCPU が有効なら CICompilerCount はエルゴノミクスに基づいて設定される。
CICompilerCount を明示的に指定していてもCICompilerCountPerCPUが有効であれば明示的な値は無視され、エルゴノミクスの値が設定される。
(4)
CPU数に基づき以下で CICompilerCountPerCPU の値を計算する。
log2(n) * log2(log2(n)) * 3 / 2
(5)
コードキャッシュにすべてのコンパイラバッファを保持するのに十分なスペースがあることを確認し、なければその分、CICompilerCount を減らす。ただこの条件にひっかかることはほぼなさそう。
実機確認
確認環境 openjdk 17.0.0.1 CentOS Linux release 8.5.2111 CPU4個
CPU4個の場合、CICompilerCount のデフォルト値は3になる。
orange >> java17/bin/java -XX:+PrintFlagsFinal -version | grep CICompilerCount
intx CICompilerCount = 3 {product} {ergonomic}
bool CICompilerCountPerCPU = true {product} {default}
NeverActAsServerClassMachine が有効な場合は 1 となる。
orange >> java17/bin/java -XX:+NeverActAsServerClassMachine -XX:+PrintFlagsFinal -version | grep CICompilerCount
intx CICompilerCount = 1 {product} {ergonomic}
bool CICompilerCountPerCPU = false {product} {default}
CICompilerCount は2以上を指定する必要がある。
orange >> java17/bin/java -XX:CICompilerCount=1 -XX:+PrintFlagsFinal -version | grep CICompilerCount
CICompilerCount (1) must be at least 2
ただし、TieredCompilationが無効の場合は 1 以上。
orange >> java17/bin/java -XX:-TieredCompilation -XX:CICompilerCount=1 -XX:+PrintFlagsFinal -version | grep CICompilerCount
intx CICompilerCount = 1 {product} {command line}
CICompilerCount を明示的に指定しても CICompilerCountPerCPU を明示的に有効にすると CICompilerCount の設定値は無視される。
orange >> java17/bin/java -XX:+CICompilerCountPerCPU -XX:CICompilerCount=4 -XX:+PrintFlagsFinal -version | grep CICompilerCount
OpenJDK 64-Bit Server VM warning: The VM option CICompilerCountPerCPU overrides CICompilerCount.
intx CICompilerCount = 3 {product} {command line, ergonomic}
bool CICompilerCountPerCPU = true {product} {command line}