Java 9のコンパクト文字列

1概要

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

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

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

また重要なことは、 __ String が通常JVMヒープスペースの大部分を占めることです。そして、それらがJVMによって保存される方法のために、ほとんどの場合、 String__インスタンスは実際には2倍のスペースを必要とする可能性があります。

この記事では、JDK 6で導入されたCompressed Stringオプションと、JDK 9で最近導入された新しいCompact Stringについて説明します。どちらもJMVの文字列のメモリ消費を最適化するように設計されています。

2圧縮 String - Java 6

JDK 6 update 21 Performance Releaseでは、新しいVMオプションが導入されました。

-XX:+UseCompressedStrings

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

3コンパクト String - Java 9

Java 9はコンパクトな文字列の概念をもたらしました。

これは、 String のすべての文字がbyte-LATIN-1表現を使用して表現できる場合は、 String を作成するたびに、内部でバイト配列が使用され、1文字に1バイトが与えられることを意味します。

他のケースでは、もしある文字がそれを表現するのに8ビット以上を必要とするなら、すべての文字はそれぞれ2バイトを使って格納されます - UTF-16表現。

したがって、基本的には、可能な限り、各文字に1バイトを使用するだけです。

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

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

3.1. Java 9 での String の実装

今までは、 String char[] として格納されていました。

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に準備ができて利用可能である必要があるすべての情報があるので、 CompactString VMオプションはデフォルトで有効になっています。無効にするには、次のようにします。

+XX:-CompactStrings

3.2. coder のしくみ

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

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

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

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

  • Compact String、 に対して行われたすべての変更は String クラスの内部実装にあり、 String usersを使用する開発者にとっては完全に透過的です。

4コンパクト 文字列 と圧縮 文字列

JDK 6 Compressed Stringsの場合、 String コンストラクタが char[] のみを引数として受け入れたという大きな問題がありました。これに加えて、多くの String 操作はバイト配列ではなく char[]__表現に依存していました。このため、多くの解凍を行わなければならず、パフォーマンスに影響を及ぼしました。

Compact Stringの場合は、 追加フィールドの "coder"を維持することでオーバーヘッドも増加する可能性があります。 coder のコストと __byte sから char sへのアンパック(UTF-16表現の場合)を軽減するために、いくつかのメソッドはhttps://en.wikipedia.org/wiki/Intrinsic function[intrinsified]と生成されたASMコードです。 JITコンパイラによるものも改善されています。

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

したがって、パフォーマンスの面では、コンパクトな弦は圧縮された弦より優れています。

コンパクト__文字列を使用して節約されたメモリ量を確認するために、さまざまなJavaアプリケーションヒープダンプが分析されました。そして、結果は特定のアプリケーションに大きく依存していましたが、全体的な改善はほぼ常にかなりのものでした。

4.1. 性能の違い

コンパクト__文字列の有効化と無効化のパフォーマンスの違いの非常に単純な例を見てみましょう。

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.");

ここでは、1000万個の __String __を作成してから、それらを単純な方法で追加しています。このコードを実行すると(デフォルトでコンパクトストリングが有効になっています)、次のような出力が得られます。

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結論

このチュートリアルでは、メモリ効率の良い方法で __String __を格納することによって、JVM上のパフォーマンスとメモリ消費を最適化する試みを見ました。

いつものように、コード全体はhttps://github.com/eugenp/tutorials/tree/master/java-strings[Githubで利用可能]です。