状況:
私はLZF圧縮アルゴリズムのpure-java実装を最適化しています。これには、ハッシュと比較のための多くのbyte[]アクセスと基本的なint数学が含まれます。圧縮の目標はI/O要件を減らすことであるため、パフォーマンスは非常に重要です。コードはまだクリーンアップされておらず、大幅に再構築されている可能性があるため、コードを投稿していません。
質問:
- より高速なSSE操作を使用してフォームにJITコンパイルできるようにコードを作成するにはどうすればよいですか?
- コンパイラが配列境界チェックを簡単に排除できるように、どのように構造化できますか?
- 特定の数学演算の相対速度に関する幅広い参考資料はありますか(通常の加算/減算に等しくなるために必要な増分/減分の数、シフトの速度、または配列アクセスの速度)?
- 分岐の最適化にどのように取り組むことができますか?短い本体を持つ多数の条件ステートメント、またはいくつかの長いステートメント、またはネストされた条件を持つ短いステートメントがある方が良いですか?
- 現在の1.6JVMでは、System.arraycopyがコピーループを打ち負かす前に、いくつの要素をコピーする必要がありますか?
私がすでにしたこと:
時期尚早の最適化で攻撃される前に:基本的なアルゴリズムはすでに優れていますが、Javaの実装は同等のCの2/3未満の速度です。コピーループをSystem.arraycopyに置き換え、ループの最適化に取り組み、 -必要な操作。
私は、パフォーマンスのためにビットをいじったり、バイトをintにパックしたり、シフトやマスキングを多用しています。
法的な理由から、同様のライブラリの実装を確認することはできません。また、既存のライブラリのライセンス条項は制限が厳しすぎて使用できません。
良い(受け入れられた)答えの要件:
- 受け入れられない答え:「これはより速い」とは、その量と理由の説明がない場合、またはJITコンパイラでテストされていない場合です。
- 境界線の回答:Hotspot1.4より前では何もテストされていません
- 基本的な答え:一般的なルールと、コンパイラレベルで高速である理由と、おおよそどれだけ高速であるかについての説明を提供します
- 良い答え:デモンストレーション用のコードのサンプルをいくつか含めてください
- 優れた回答: JRE1.5と1.6の両方でベンチマークを実行する
- 完璧な答え: HotSpotコンパイラに取り組んだ人によるもので、使用する最適化の条件と、通常の速度を完全に説明または参照できます。HotSpotによって生成されたJavaコードとサンプルアセンブリコードが含まれる場合があります。
また、ホットスポットの最適化と分岐のパフォーマンスの要点を詳しく説明しているリンクがあれば、それを歓迎します。私はバイトコードについて十分に知っているので、ソースコードレベルではなくバイトコードでパフォーマンスを分析するサイトが役立つでしょう。
(編集)部分的な回答:境界-除去のチェック:
これは、次のHotSpot内部Wikiへの提供されたリンクから取得されます:https ://wikis.oracle.com/display/HotSpotInternals/RangeCheckElimination
HotSpotは、次の条件ですべてのforループの境界チェックを排除します。
- 配列はループ不変です(ループ内で再割り当てされません)
- インデックス変数は一定のストライドを持ちます(可能な場合は1つのスポットでのみ一定量だけ増加/減少します)
- 配列は、変数の線形関数によってインデックスが付けられます。
例: int val = array[index*2 + 5]
また: int val = array[index+9]
いいえ: int val = array[Math.min(var,index)+7]
コードの初期バージョン:
これはサンプルバージョンです。これはH2データベースプロジェクトの未リリースバージョンのコードであるため、盗まないでください。最終バージョンはオープンソースになります。これは、ここのコードの最適化です:H2CompressLZFコード
論理的には、これは開発バージョンと同じですが、for(...)ループを使用して入力をステップスルーし、if/elseループを使用してリテラルモードと後方参照モードの間の異なるロジックを実行します。アレイへのアクセスを減らし、モード間のチェックを行います。
public int compressNewer(final byte[] in, final int inLen, final byte[] out, int outPos){
int inPos = 0;
// initialize the hash table
if (cachedHashTable == null) {
cachedHashTable = new int[HASH_SIZE];
} else {
System.arraycopy(EMPTY, 0, cachedHashTable, 0, HASH_SIZE);
}
int[] hashTab = cachedHashTable;
// number of literals in current run
int literals = 0;
int future = first(in, inPos);
final int endPos = inLen-4;
// Loop through data until all of it has been compressed
while (inPos < endPos) {
future = (future << 8) | in[inPos+2] & 255;
// hash = next(hash,in,inPos);
int off = hash(future);
// ref = possible index of matching group in data
int ref = hashTab[off];
hashTab[off] = inPos;
off = inPos - ref - 1; //dropped for speed
// has match if bytes at ref match bytes in future, etc
// note: using ref++ rather than ref+1, ref+2, etc is about 15% faster
boolean hasMatch = (ref > 0 && off <= MAX_OFF && (in[ref++] == (byte) (future >> 16) && in[ref++] == (byte)(future >> 8) && in[ref] == (byte)future));
ref -=2; // ...EVEN when I have to recover it
// write out literals, if max literals reached, OR has a match
if ((hasMatch && literals != 0) || (literals == MAX_LITERAL)) {
out[outPos++] = (byte) (literals - 1);
System.arraycopy(in, inPos - literals, out, outPos, literals);
outPos += literals;
literals = 0;
}
//literal copying split because this improved performance by 5%
if (hasMatch) { // grow match as much as possible
int maxLen = inLen - inPos - 2;
maxLen = maxLen > MAX_REF ? MAX_REF : maxLen;
int len = 3;
// grow match length as possible...
while (len < maxLen && in[ref + len] == in[inPos + len]) {
len++;
}
len -= 2;
// short matches write length to first byte, longer write to 2nd too
if (len < 7) {
out[outPos++] = (byte) ((off >> 8) + (len << 5));
} else {
out[outPos++] = (byte) ((off >> 8) + (7 << 5));
out[outPos++] = (byte) (len - 7);
}
out[outPos++] = (byte) off;
inPos += len;
//OPTIMIZATION: don't store hashtable entry for last byte of match and next byte
// rebuild neighborhood for hashing, but don't store location for this 3-byte group
// improves compress performance by ~10% or more, sacrificing ~2% compression...
future = ((in[inPos+1] & 255) << 16) | ((in[inPos + 2] & 255) << 8) | (in[inPos + 3] & 255);
inPos += 2;
} else { //grow literals
literals++;
inPos++;
}
}
// write out remaining literals
literals += inLen-inPos;
inPos = inLen-literals;
if(literals >= MAX_LITERAL){
out[outPos++] = (byte)(MAX_LITERAL-1);
System.arraycopy(in, inPos, out, outPos, MAX_LITERAL);
outPos += MAX_LITERAL;
inPos += MAX_LITERAL;
literals -= MAX_LITERAL;
}
if (literals != 0) {
out[outPos++] = (byte) (literals - 1);
System.arraycopy(in, inPos, out, outPos, literals);
outPos += literals;
}
return outPos;
}
最終編集:
締め切りが迫っているので、これまでのところ、ベストアンサーをマークしました。私はコードを投稿することを決定する前に非常に長い時間がかかったので、私は可能な限り賛成票を投じてコメントに返信し続けます。 コードが乱雑な場合はお詫びします。これは開発中のコードであり、コミットのために洗練されたものではありません。