web-dev-qa-db-ja.com

Java.lang.Stringのメモリ効率の良い代替品はありますか?

この古い記事 いくつかのオブジェクトタイプのメモリ消費量を測定した後、JavaでStringsがどれだけのメモリを使用しているかを見て驚いた。

_length: 0, {class Java.lang.String} size = 40 bytes
length: 7, {class Java.lang.String} size = 56 bytes
_

この記事にはこれを最小限に抑えるためのヒントがいくつかありますが、完全に満足できるものではありませんでした。データの保存に_char[]_を使用するのは無駄のようです。ほとんどの西洋言語の明らかな改善は、代わりに_byte[]_とUTF-8のようなエンコーディングを使用することです。これは、最も頻繁な文字を格納するために2バイトではなく、1バイトしか必要ないためです。

もちろん、String.getBytes("UTF-8")new String(bytes, "UTF-8")を使用することもできます。 Stringインスタンス自体のオーバーヘッドさえなくなります。しかし、そこではequals()hashCode()length()、..のような非常に便利なメソッドが失われます。

私の知る限り、Sunは文字列の_byte[]_表現に 特許 を持っています。

Javaプログラミング環境で文字列オブジェクトを効率的に表現するためのフレームワーク
...必要に応じて、Java文字列オブジェクトを1バイト文字の配列として作成する手法を実装できます。

しかし、私はその特許のAPIを見つけることができませんでした。

なぜ気にするのですか?
ほとんどの場合、私はしません。しかし、私は、メモリをより効率的に使用することで恩恵を受けるであろう、多くの文字列を含む巨大なキャッシュを持つアプリケーションに取り組みました。

誰かがそのようなAPIを知っていますか?または、CPUパフォーマンスや醜いAPIを犠牲にしても、文字列のメモリフットプリントを小さく保つ別の方法はありますか?

上記の記事からの提案を繰り返さないでください:

  • String.intern()の独自のバリアント(おそらくSoftReferencesを使用)
  • 単一の_char[]_を格納し、現在のString.subString(.)実装を利用して、データのコピーを回避します(厄介な)

更新

Sunの現在のJVM(1.6.0_10)に関する記事のコードを実行しました。 2002年と同じ結果が得られました。

36
the.duckman

JVMからの少しの助けを借りて...

警告:このソリューションは、新しいJavaSEバージョンでは廃止されました。以下のその他のアドホックソリューションを参照してください。

HotSpot JVMを使用する場合、Java 6 update 21以降、次のコマンドラインオプションを使用できます。

-XX:+UseCompressedStrings

JVMオプション ページの読み取り:

純粋なASCIIとして表すことができる文字列にはbyte []を使用します。 (Java 6 Update 21 Performance Releaseで導入)

[〜#〜] update [〜#〜]:この機能は後のバージョンで壊れており、Java SE 6u25 6u25 b03リリースノート (ただし、 6u25最終リリースノート には表示されません)。 バグレポート701621 は、セキュリティ上の理由から表示されません。したがって、注意して使用し、最初に確認してください。他の-XXオプションと同様に、これは実験的なものと見なされ、予告なしに変更される可能性があるため、運用サーバーの起動スクリプトで使用しないことが常に最善であるとは限りません。

UPDATE 2013-03Aleksey Maximusによるコメントに感謝:これを参照してください 関連する質問 および 受け入れられた回答 。オプションは現在廃止されているようです。これはバグ 7129417 レポートでさらに確認されています。

終わりは手段を正当化する

警告:(醜い)特定のニーズに対する解決策

これは箱から出して低レベルですが、あなたが尋ねたので...メッセンジャーを叩かないでください!

あなた自身のより軽い文字列表現

ASCIIで十分な場合は、独自の実装を展開してみませんか?

あなたが言ったように、あなたは内部的にbyte[]の代わりにchar[]することができます。しかし、それだけではありません。

さらに軽量化するには、バイト配列をクラスでラップする代わりに、渡すこれらのバイト配列を操作するほとんど静的なメソッドを含むヘルパークラスを使用してみませんか?確かに、それはかなりCっぽい感じになりますが、それは機能し、Stringオブジェクトに伴うhugeオーバーヘッドを節約します。

そして確かに、それはいくつかの素晴らしい機能を見逃すでしょう...あなたがそれらを再実装しない限り。本当に必要な場合は、選択肢はあまりありません。 OpenJDKや他の多くの優れたプロジェクトのおかげで、byte[]パラメーターを操作するだけの独自の醜いLiteStringsクラスを非常にうまく展開できました。関数を呼び出す必要があるたびにシャワーを浴びているような気分になりますが、メモリのヒープを節約できます。

Stringクラスのコントラクトによく似たものにし、Stringとの間で変換するための意味のあるアダプターとビルダーを提供することをお勧めします。また、StringBufferStringBuilderとの間のアダプター、およびその他のいくつかのミラー実装も必要になる場合があります。あなたが必要とするかもしれないもの。確かにいくつかの作業ですが、それだけの価値があるかもしれません(「MakeitCount!」セクションの少し下を参照してください)。

オンザフライ圧縮/解凍

文字列をメモリ内で非常によく圧縮し、必要なときにその場で解凍することができます。結局のところ、あなたはそれらにアクセスするときだけそれらを読むことができる必要がありますよね?

もちろん、その暴力的であることは意味します:

  • より複雑な(したがって保守性の低い)コード、
  • より多くの処理能力、
  • 圧縮を適切に行うには(または、独自のストアシステムを実装して複数の文字列を1つに圧縮し、圧縮をより効果的にするために)比較的長い文字列が必要です。

両方を行う

完全な頭痛の場合は、もちろん、次のすべてを行うことができます。

  • Cっぽいヘルパークラス、
  • バイト配列、
  • オンザフライの圧縮ストア。

必ずオープンソースにしてください。 :)

それを数えなさい!

ちなみに、N。MitchellによるBuilding Memory-Efficient Java Applicationsに関するこのすばらしいプレゼンテーションを参照してください。およびG.Sevitsky:[ 2008バージョン ]、[ 2009バージョン ]。

このプレゼンテーションから、8文字の文字列が32ビットシステムで64バイトを消費することがわかります(64ビットシステムの場合は96 !!) 、そしてそのほとんどはJVMのオーバーヘッドによるものです。そしてこれから article8バイトの配列は24バイトを「のみ」食べることがわかります:12バイトのヘッダー、 8 x1バイト+4バイトのアラインメント)。

あなたが本当にそのようなものの多くを操作するなら、これは価値があるように聞こえます(そして、メモリの割り当てに費やす時間が少なくなるので、おそらく少しスピードアップしますが、それについて私を引用してベンチマークしないでください;実装に大きく依存します)。

24
haylem

Terracottaでは、大きな文字列がネットワークを介して送信されるときに圧縮し、解凍が必要になるまで実際に圧縮したままにする場合があります。これを行うには、char []をbyte []に​​変換し、byte []を圧縮してから、そのbyte []を元のchar []にエンコードします。ハッシュや長さなどの特定の操作では、圧縮された文字列をデコードせずにこれらの質問に答えることができます。大きなXML文字列などのデータの場合、この方法で大幅な圧縮を行うことができます。

圧縮されたデータをネットワーク上で移動することは間違いなく勝利です。圧縮を維持することは、ユースケースによって異なります。もちろん、これをオフにしたり、圧縮がオンになる長さを変更したりするためのノブがいくつかあります。

これはすべて、Java.lang.Stringのバイトコードインストルメンテーションで行われます。これは、起動時にStringがどのように使用されるかによって非常にデリケートですが、いくつかのガイドラインに従うと安定しています。

21
Alex Miller

この記事は2つのことを指摘しています。

  1. 文字配列は8バイトのチャンクで増加します。
  2. Char []オブジェクトとStringオブジェクトのサイズには大きな違いがあります。

オーバーヘッドは、char []オブジェクト参照と、文字列のハッシュコードを格納するためのオフセット、長さ、スペースの3つの整数に加えて、単にオブジェクトであるという標準的なオーバーヘッドが含まれているためです。

String.intern()とは少し異なり、String.substring()で使用される文字配列はすべての文字列に単一のchar []を使用します。これは、オブジェクト参照をラッパーの文字列のようなオブジェクトに格納する必要がないことを意味します。それでもオフセットが必要であり、合計で使用できる文字数に(大きな)制限を導入します。

文字列の特別な終わりマーカーを使用する場合、長さは不要になります。これにより、長さが4バイト節約されますが、マーカーに2バイトのコストがかかり、さらに時間、複雑さ、およびバッファオーバーランのリスクが追加されます。

ハッシュを保存しないという時空間のトレードオフは、ハッシュを頻繁に必要としない場合に役立つことがあります。

多数の文字列を超高速でメモリ効率よく処理する必要がある、私が使用したアプリケーションの場合、データをエンコードされた形式のままにして、バイト配列を操作することができました。私の出力エンコーディングは入力エンコーディングと同じであり、出力のためにバイトを文字にデコードしたり、バイトに再度エンコードしたりする必要はありませんでした。

さらに、入力データを最初に読み込まれたバイト配列(メモリマップファイル)に残すことができました。

私のオブジェクトは、intオフセット(私の状況に適した制限)、int長、およびintハッシュコードで構成されていました。

Java.lang.Stringは、私がやりたいことにはおなじみのハンマーでしたが、その仕事に最適なツールではありませんでした。

10
Stephen Denne

2002年のjavaworld.comの記事に基づいてアイデアや仮定を立てる場合は、十分に注意する必要があると思います。それ以来6年間で、コンパイラとJVMに多くの変更が加えられました。少なくとも、最初に最新のJVMに対して仮説とソリューションをテストして、ソリューションが努力する価値があることを確認してください。

7
matt b

内部UTF-8エンコーディングには利点がありますが(指摘したメモリフットプリントが小さいなど)、欠点もあります。

たとえば、UTF-8でエンコードされた文字列の(バイト長ではなく)文字長を決定することは、O(n)操作です。 Java文字列では、文字長を決定するコストはO(1)ですが、UTF-8表現を生成するコストはO(n)です。

優先順位がすべてです。

データ構造の設計は、多くの場合、速度とスペースの間のトレードオフと見なすことができます。この場合、Java文字列APIの設計者は、次の基準に基づいて選択を行ったと思います。

  • Stringクラスは、可能なすべてのUnicode文字をサポートする必要があります。

  • Unicodeは1バイト、2バイト、および4バイトのバリアントを定義しますが、4バイトの文字は(実際には)非常にまれであるため、それらを代理ペアとして表すことは問題ありません。そのため、Javaは2バイトのcharプリミティブを使用します。

  • Length()、indexOf()、およびcharAt()メソッドを呼び出すとき、ユーザーはバイト位置ではなく文字位置に関心があります。これらのメソッドの高速実装を作成するには、内部UTF-8エンコーディングを回避する必要があります。

  • C++のような言語は、3つの異なる文字タイプを定義し、プログラマーにそれらのいずれかを選択させることにより、プログラマーの生活をより複雑にします。ほとんどのプログラマーは単純なASCII文字列を使用することから始めますが、最終的に国際文字をサポートする必要がある場合、マルチバイト文字を使用するようにコードを変更するプロセスは非常に面倒です。 Javaの設計者は、すべての文字列が2バイト文字で構成されていると言って、妥協案として優れた選択をしたと思います。

7
benjismith

Javaは、速度とストレージサイズの妥協点としてUTF-16を選択しました。 UTF-8データの処理はUTF-16データの処理よりもはるかにPITAです(たとえば、バイト配列内の文字Xの位置を見つけようとする場合、すべての文字に1つ持つことができる場合、どのように高速に行うのですか? 2、3、または最大6バイトですか?それについて考えたことはありますか?文字列をバイトごとに調べるのはそれほど速くありませんね)。もちろん、UTF-32は処理が最も簡単ですが、ストレージスペースを2倍無駄にします。 Unicodeの初期から状況は変化しました。 UTF-16が使用されている場合でも、特定の文字には4バイトが必要です。これらを正しく処理すると、UTF-16はUTF-8とほぼ同じように悪くなります。

とにかく、UTF-8を使用する内部ストレージでStringクラスを実装すると、ある程度のメモリを獲得できる可能性がありますが、多くの文字列メソッドの処理速度が低下しますのでご安心ください。また、あなたの議論はあまりにも限られた視点です。 UTF-8ではUTF-16よりも日本語の文字が小さくならないため(実際にはUTF-8では3バイトかかりますが、UTF-16では2バイトしかないため)、日本の人には当てはまりません。 。今日のようにインターネットが遍在するこのようなグローバルな世界のプログラマーが、「西洋の言語」について話している理由がわかりません。まるで、西洋の世界だけがコンピューターを持っていて、残りの部分が洞窟。遅かれ早かれ、アプリケーションは非西洋文字を効果的に処理できないという事実に噛まれます。

2
Mecki

それらをすべてgzipで圧縮するだけです。 :)冗談です...しかし、私は奇妙なことを見てきました、そしてそれはかなりのCPUコストであなたにはるかに小さなデータを与えるでしょう。

私が知っている他のString実装は、Javolutionクラスのものだけです。ただし、メモリ効率が高いとは思いません。

http://www.javolution.com/api/javolution/text/Text.html
http://www.javolution.com/api/javolution/text/TextBuilder.html

2
jsight

UseCompressedStringsコンパイラオプションは、最も簡単な方法のようです。文字列をストレージのみに使用していて、equals/substring/split操作を実行していない場合は、次のCompactCharSequenceクラスのようなものが機能する可能性があります。

http://www.javamex.com/tutorials/memory/ascii_charsequence.shtml

1
Axl

私は現在、次のように圧縮方法を実装しています(ドキュメント間の計算を実行できるように、非常に多くのドキュメントをメモリに保存する必要があるアプリに取り組んでいます)。

  • 文字列を4文字の「単語」に分割し(すべてのUnicodeが必要な場合)、マスキング/ビットシフトを使用してそれらのバイトをlongに格納します。完全なUnicodeセットが不要で、255 ASCII文字のみ)が必要な場合は、各longに8文字を収めることができます。_(char) 0_を末尾に追加します長さが4(または8)で均等に分割されるまでの文字列。
  • ハッシュセットの実装(TroveのTLongHashSetなど)をオーバーライドし、各「Word」をそのセットに追加して、longがセット内で終わる場所の内部インデックスの配列をコンパイルします(必ずセットが再ハッシュされたときにもインデックスを更新します)
  • 2次元のint配列を使用してこれらのインデックスを格納し(したがって、最初の次元は各圧縮文字列であり、2番目の次元はハッシュセット内の各「Word」インデックスです)、単一のintその配列にインデックスを付けて呼び出し元に戻します(上記のように再ハッシュでインデックスをグローバルに更新できるように、Word配列を所有している必要があります)

利点:

  • 一定時間の圧縮/解凍
  • 長さn文字列は、長さn/ 4のint配列として表され、追加のオーバーヘッドはlong単語セットは、一意の「単語」が少なくなるにつれて漸近的に増加します。
  • ユーザーには、オブジェクトに格納するのに便利で小さい単一のint文字列 "ID"が返されます。

短所:

  • ビットシフト、ハッシュセットの内部の混乱などが含まれるため、ややハッキーです( Bill K は承認されません)
  • 重複する文字列が多くない場合にうまく機能します。文字列がライブラリにすでに存在するかどうかを確認するのは非常にコストがかかります。
1
J. Dimeo

今日(2010年)、サーバーに追加するGBごとに約80ポンドまたは120ドルの費用がかかります。文字列をリエンジニアリングする前に、それが本当に価値があるかどうかを自問する必要があります。

GBのメモリを節約する場合は、おそらく。間違いなく10GB。数十MBを節約したい場合は、その価値よりも多くの時間を使用する可能性があります。

文字列をどのように圧縮するかは、実際には使用パターンによって異なります。繰り返される文字列はたくさんありますか? (オブジェクトプールを使用)長い文字列がたくさんありますか? (圧縮/エンコードを使用)

より小さな文字列が必要になる可能性があるもう1つの理由は、キャッシュの使用量を減らすことです。最大のCPUでさえ、約8 MB〜12MBのキャッシュがあります。これはより貴重なリソースであり、簡単に増やすことはできません。この場合、文字列の代替案を検討することをお勧めしますが、それにかかる時間に対して、£または$でどの程度の違いが生じるかを覚えておく必要があります。

1
Peter Lawrey

オブジェクト(少なくともディスパッチテーブル)を作成するオーバーヘッド、1文字あたり2バイトを使用するという事実のオーバーヘッド、および実際に速度とメモリ使用量を改善するために作成されるいくつかの追加変数のオーバーヘッドがあります。多くの場合。

OOプログラミングを使用する場合、これは、明確で、使用可能で、保守可能なコードを作成するためのコストです。

明白な(メモリ使用量がそれほど重要である場合は、おそらくCを使用する必要がある)以外の答えについては、BCDバイト配列の内部表現を使用して独自の文字列を実装できます。

それは実際には楽しそうに聞こえます、私はキックのためだけにそれをするかもしれません:)

A Java配列はアイテムごとに2バイトかかります。BCDでエンコードされた数字は文字ごとに6ビットIIRCを取り、文字列を大幅に小さくします。時間の変換コストは少しかかりますが、それほど悪くはありません。 。本当に大きな問題は、それを使って何かをするために文字列に変換しなければならないということです。

心配するオブジェクトインスタンスのオーバーヘッドはまだあります...しかし、インスタンスを削除しようとするよりも、デザインを刷新することで対処できます。

最後にメモ。次の3つがない限り、このようなものを展開することには完全に反対です。

  • 最も読みやすい方法で行われた実装
  • その実装が要件をどのように満たしていないかを示すテスト結果と要件
  • 「改善された」実装がどのように要件を満たしているかに関するテスト結果。

これら3つすべてがなければ、開発者が提示した最適化されたソリューションを開始します。

1
Bill K

Javaエンジニアは可能な限り共有するためにフライウェイトデザインパターンを実装しているため、文字列はしばらくの間メモリをあまり消費しないと思います。実際、同じ値を持つ文字列は私が信じているメモリ内の非常に同じオブジェクト。

0
nkr1pt

好奇心から、節約された数バイトは本当に価値がありますか?

通常、パフォーマンス上の理由から、StringBufferを優先して文字列を破棄することをお勧めします(文字列は不変であることを忘れないでください)。

文字列参照からヒープを真剣に使い果たしていますか?

0
FlySwat

あなたはあなた自身のインターンスキームを転がすという記事の提案を繰り返さないと言いましたが、String.intern自体の何が問題になっていますか?この記事には、次の使い捨てのコメントが含まれています。

String.intern()メソッドを回避する理由は多数あります。 1つは、大量のデータをインターンできる最新のJVMはほとんどないということです。

しかし、2002年のメモリ使用量の数値が6年後も保持されているとしても、JVMがインターンできるデータの量に進展がない場合は驚きます。

これは純粋に修辞的な質問ではありません-それを避ける正当な理由があるかどうか知りたいです。高度にマルチスレッドで使用するには非効率的に実装されていますか?ヒープの特別なJVM固有の領域を埋めますか?本当に数百メガバイトの一意の文字列がありますか(したがって、インターンはとにかく役に立たないでしょう)?

0
Sam Stokes

圧縮には多くの種類があることを忘れないでください。ハフマン符号化を使用することは、優れた汎用アプローチですが、比較的CPUに負荷がかかります。数年前に取り組んだB + Treeの実装では、キーに共通の先頭文字が含まれる可能性が高いことがわかっていたため、B + Treeの各ページに先頭文字圧縮アルゴリズムを実装しました。コードは簡単で、非常に高速で、当初の3分の1のメモリ使用量になりました。私たちの場合、これを行う本当の理由は、ディスク上のスペースを節約し、ディスクに費やす時間を削減することでした-> RAM転送(そしてその1/3の節約は有効なディスクに大きな違いをもたらしましたパフォーマンス)。

私がこれを取り上げる理由は、カスタム文字列の実装がここではあまり役に立たなかったからです。文字列が存在するcontainerのレイヤーを操作したため、私たちが行った利益を達成することしかできませんでした。

Stringオブジェクト内のあちこちで数バイトを最適化しようとすると、比較する価値がない場合があります。

0
Kevin Day