このようなベンチマーク (特に JVM を使用したベンチマーク) を作成する前に、考慮すべき点がいくつかあります。
ほとんどの (物理) マシンでは、Redis は、パイプラインを使用すると 100K ops/s を超える処理が可能です。ベンチマークは 100K アイテムしか扱っていないため、意味のある結果を生成するのに十分な長さではありません。さらに、JIT の一連の段階を開始する時間はありません。
絶対時間はあまり重要な指標ではありません。ベンチマークを少なくとも 10 秒間実行したままスループット (つまり、1 秒あたりの操作数) を表示すると、より適切で安定したメトリックになります。
あなたの内側のループは大量のガベージを生成します。Jedis+Redis のベンチマークを行う場合は、独自のプログラムのオーバーヘッドを低く抑える必要があります。
すべてをメイン関数に定義したため、ループは JIT によってコンパイルされません (使用する JVM によって異なります)。内部メソッド呼び出しのみ可能です。JIT を効率的にしたい場合は、JIT でコンパイルできるメソッドにコードをカプセル化してください。
必要に応じて、実際の測定を実行する前にウォームアップ フェーズを追加して、ベアボーン インタープリターで最初の反復を実行するオーバーヘッドと JIT 自体のコストを考慮しないようにすることもできます。
ここで、Redis パイプラインに関して、パイプラインが長すぎます。パイプライン内の 100K コマンドは、Jedis が Redis に何かを送信する前に 6MB のバッファーを構築する必要があることを意味します。これは、ソケット バッファ (クライアント側、およびおそらくサーバー側) が飽和状態になり、Redis が 6 MB の通信バッファも処理する必要があることを意味します。
さらに、ベンチマークは依然として同期的です (パイプラインを使用しても魔法のように非同期になるわけではありません)。つまり、パイプラインの最後のクエリが Redis に送信されるまで、Jedis は応答の読み取りを開始しません。パイプラインが長すぎると、物事をブロックする可能性があります。
パイプラインのサイズを 100 ~ 1000 操作に制限することを検討してください。もちろん、より多くのラウンドトリップが生成されますが、通信スタックへのプレッシャーは許容レベルまで軽減されます。たとえば、次のプログラムを考えてみましょう。
import redis.clients.jedis.*;
import java.util.*;
public class TestPipeline {
/**
* @param args
*/
int i = 0;
Map<String, String> map = new HashMap<String, String>();
ShardedJedis jedis;
// Number of iterations
// Use 1000 to test with the pipeline, 100 otherwise
static final int N = 1000;
public TestPipeline() {
JedisShardInfo si = new JedisShardInfo("127.0.0.1", 6379);
List<JedisShardInfo> list = new ArrayList<JedisShardInfo>();
list.add(si);
jedis = new ShardedJedis(list);
}
public void push( int n ) {
ShardedJedisPipeline pipeline = jedis.pipelined();
for ( int k = 0; k < n; k++) {
map.put("id", "" + i);
map.put("name", "lyj" + i);
pipeline.hmset("m" + i, map);
++i;
}
pipeline.sync();
}
public void push2( int n ) {
for ( int k = 0; k < n; k++) {
map.put("id", "" + i);
map.put("name", "lyj" + i);
jedis.hmset("m" + i, map);
++i;
}
}
public static void main(String[] args) {
TestPipeline obj = new TestPipeline();
long startTime = System.currentTimeMillis();
for ( int j=0; j<N; j++ ) {
// Use push2 instead to test without pipeline
obj.push(1000);
// Uncomment to see the acceleration
//System.out.println(obj.i);
}
long endTime = System.currentTimeMillis();
double d = 1000.0 * obj.i;
d /= (double)(endTime - startTime);
System.out.println("Throughput: "+d);
}
}
このプログラムを使用すると、パイプラインの有無にかかわらずテストできます。パイプライン処理を使用する場合は、反復回数 (N パラメータ) を増やして、少なくとも 10 秒間実行されるようにしてください。ループ内の println のコメントを外すと、最初はプログラムが遅く、JIT が最適化を開始するにつれて速くなることがわかります (そのため、意味のある結果を得るには、プログラムを少なくとも数秒間実行する必要があります)。
私のハードウェア (古い Athlon ボックス) では、パイプラインを使用すると 8 ~ 9 倍のスループットを得ることができます。内側のループでキーと値のフォーマットを最適化し、ウォームアップ フェーズを追加することで、プログラムをさらに改善できます。