Java 9のコンパクト文字列

Java 9のコンパクト文字列

1. 概要

JavaのStringsは、内部的にはStringの文字を含むchar[]によって表されます。 また、Java internally uses UTF-16.は2バイトで構成されているため、すべてのcharは2バイトで構成されています。

たとえば、Stringに英語の単語が含まれている場合、ASCII文字は1バイトを使用して表すことができるため、先頭の8ビットはすべてのcharに対して0になります。

多くの文字は、それらを表すために16ビットを必要としますが、統計的には8ビットのみが必要です— LATIN-1文字表現。 そのため、メモリ消費とパフォーマンスを改善する余地があります。

また重要なのは、通常、StringsがJVMヒープスペースの大部分を占めることです。 また、JVMによる保存方法のため、ほとんどの場合、a String instance can take up doubleスペースit actually needsです。

この記事では、JDK6で導入された圧縮文字列オプションと、最近JDK9で導入された新しいコンパクト文字列について説明します。 これらは両方とも、JMV上の文字列のメモリ消費を最適化するように設計されています。

2. 圧縮されたString – Java 6

JDK 6アップデート21パフォーマンスリリースでは、新しいVMオプションが導入されました。

-XX:+UseCompressedStrings

このオプションを有効にすると、Stringschar[] –ではなくbyte[]として格納されるため、多くのメモリを節約できます。 ただし、このオプションは最終的にJDK 7で削除されました。これは主に、意図しないパフォーマンスへの影響があったためです。

3. コンパクトString – Java 9

Java 9は、コンパクトなStrings back.の概念をもたらしました。

これは、whenever we create a String if all the characters of the String can be represented using a byte — LATIN-1 representation, a byte array will be usedが内部的にあり、1文字に1バイトが与えられることを意味します。

その他の場合、文字を表現するために8ビット以上が必要な場合、すべての文字は、UTF-16表現ごとに2バイトを使用して格納されます。

したがって、基本的には、可能な場合は常に、各文字に1バイトを使用します。

さて、問題は–すべてのString操作はどのように機能するのでしょうか? LATIN-1とUTF-16の表現をどのように区別しますか?

この問題に取り組むために、Stringの内部実装に別の変更が加えられました。 この情報を保持する最後のフィールドcoderがあります。

3.1. Java 9でのStringの実装

これまで、Stringchar[]として保存されていました。

private final char[] value;

これからはbyte[]:になります

private final byte[] value;

変数coder

private final byte coder;

coderは次のようになります。

static final byte LATIN1 = 0;
static final byte UTF16 = 1;

String操作のほとんどは、コーダーをチェックし、特定の実装にディスパッチするようになりました。

public int indexOf(int ch, int fromIndex) {
    return isLatin1()
      ? StringLatin1.indexOf(value, ch, fromIndex)
      : StringUTF16.indexOf(value, ch, fromIndex);
}

private boolean isLatin1() {
    return COMPACT_STRINGS && coder == LATIN1;
}

JVMが準備して利用可能にするために必要なすべての情報があるため、CompactStringVMオプションはデフォルトで有効になっています。 無効にするには、次を使用できます。

+XX:-CompactStrings

3.2. coderのしくみ

Java 9Stringクラスの実装では、長さは次のように計算されます。

public int length() {
    return value.length >> coder;
}

StringにLATIN-1のみが含まれている場合、coderの値は0になるため、Stringの長さはバイト配列の長さと同じになります。

その他の場合、StringがUTF-16表現の場合、coderの値は1になるため、長さは実際のバイト配列の半分のサイズになります。

CompactString,に対して行われたすべての変更は、Stringクラスの内部実装にあり、Stringを使用する開発者に対して完全に透過的であることに注意してください。

4. コンパクトStrings対。 圧縮されたStrings

JDK 6 CompressedStrings,の場合、直面した主な問題は、Stringコンストラクターが引数としてchar[]のみを受け入れることでした。 これに加えて、多くのString操作は、バイト配列ではなくchar[]表現に依存していました。 このため、多くの解凍を行う必要があり、パフォーマンスに影響しました。

一方、コンパクトString,の場合、追加のフィールド「コーダー」を維持すると、オーバーヘッドも増加する可能性があります。 coderのコストとbytesからcharsへのアンパック(UTF-16表現の場合)を軽減するために、いくつかのメソッドはintrinsifiedとASMコードです。 JITコンパイラによって生成されるものも改善されました。

この変更により、直感に反する結果が生じました。 LATIN-1indexOf(String)は組み込みメソッドを呼び出しますが、indexOf(char)は呼び出しません。 UTF-16の場合、これらのメソッドは両方とも組み込みメソッドを呼び出します。 この問題はLATIN-1Stringにのみ影響し、将来のリリースで修正される予定です。

したがって、パフォーマンスの点では、圧縮Stringsは圧縮Stringsよりも優れています。

CompactStrings,を使用して節約されたメモリの量を調べるために、さまざまなJavaアプリケーションのヒープダンプが分析されました。 また、結果は特定のアプリケーションに大きく依存していましたが、全体的な改善はほとんど常に重要でした。

4.1. パフォーマンスの違い

CompactStrings:の有効化と無効化のパフォーマンスの違いの非常に簡単な例を見てみましょう。

long startTime = System.currentTimeMillis();

List strings = IntStream.rangeClosed(1, 10_000_000)
  .mapToObj(Integer::toString)
  .collect(toList());

long totalTime = System.currentTimeMillis() - startTime;
System.out.println(
  "Generated " + strings.size() + " strings in " + totalTime + " ms.");

startTime = System.currentTimeMillis();

String appended = (String) strings.stream()
  .limit(100_000)
  .reduce("", (l, r) -> l.toString() + r.toString());

totalTime = System.currentTimeMillis() - startTime;
System.out.println("Created string of length " + appended.length()
  + " in " + totalTime + " ms.");

ここでは、1,000万Stringsを作成し、単純な方法で追加しています。 このコードを実行すると(既定ではコンパクトな文字列が有効になっています)、出力が取得されます。

Generated 10000000 strings in 854 ms.
Created string of length 488895 in 5130 ms.

同様に、-XX:-CompactStringsオプションを使用してコンパクト文字列を無効にして実行すると、出力は次のようになります。

Generated 10000000 strings in 936 ms.
Created string of length 488895 in 9727 ms.

明らかに、これは表面レベルのテストであり、非常に代表的なものではありません。これは、この特定のシナリオでパフォーマンスを向上させるために新しいオプションが実行できることのスナップショットにすぎません。

5. 結論

このチュートリアルでは、Stringsをメモリ効率の高い方法で格納することにより、JVMのパフォーマンスとメモリ消費を最適化する試みを確認しました。

いつものように、コード全体がover on Githubで利用可能です。