Existem alguns pontos que você precisa considerar antes de escrever tal benchmark (e especialmente um benchmark usando a JVM):
-
na maioria das máquinas (físicas), o Redis é capaz de processar mais de 100 mil operações/s quando o pipeline é usado. Seu benchmark lida apenas com itens de 100 mil, portanto, não dura o suficiente para produzir resultados significativos. Além disso, não há tempo para as sucessivas etapas do JIT começarem.
-
o tempo absoluto não é uma métrica muito relevante. Exibir a taxa de transferência (ou seja, o número de operações por segundo) enquanto mantém o benchmark em execução por pelo menos 10 segundos seria uma métrica melhor e mais estável.
-
seu loop interno gera muito lixo. Se você planeja fazer benchmark de Jedis+Redis, então você precisa manter a sobrecarga de seu próprio programa baixa.
-
como você definiu tudo na função principal, seu loop não será compilado pelo JIT (dependendo da JVM que você usa). Apenas as chamadas de métodos internos podem ser. Se você deseja que o JIT seja eficiente, certifique-se de encapsular seu código em métodos que possam ser compilados pelo JIT.
-
opcionalmente, você pode querer adicionar uma fase de aquecimento antes de realizar a medição real para evitar contabilizar a sobrecarga de executar as primeiras iterações com o interpretador básico e o custo do próprio JIT.
Agora, em relação ao pipeline do Redis, seu pipeline é muito longo. 100 mil comandos no pipeline significam que o Jedis precisa construir um buffer de 6 MB antes de enviar qualquer coisa para o Redis. Isso significa que os buffers de soquete (no lado do cliente e talvez no lado do servidor) estarão saturados e que o Redis também terá que lidar com buffers de comunicação de 6 MB.
Além disso, seu benchmark ainda é síncrono (usar um pipeline não o torna magicamente assíncrono). Em outras palavras, os Jedis não começarão a ler as respostas até que a última consulta do seu pipeline seja enviada ao Redis. Quando o pipeline é muito longo, ele tem o potencial de bloquear coisas.
Considere limitar o tamanho do pipeline para 100 a 1.000 operações. É claro que isso gerará mais viagens de ida e volta, mas a pressão na pilha de comunicação será reduzida a um nível aceitável. Por exemplo, considere o seguinte programa:
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);
}
}
Com este programa, você pode testar com ou sem pipelining. Certifique-se de aumentar o número de iterações (parâmetro N) quando o pipelining for usado, para que ele seja executado por pelo menos 10 segundos. Se você descomentar o println no loop, perceberá que o programa é lento no início e ficará mais rápido à medida que o JIT começar a otimizar as coisas (é por isso que o programa deve ser executado pelo menos vários segundos para obter um resultado significativo).
No meu hardware (uma antiga caixa Athlon), posso obter 8 a 9 vezes mais rendimento quando o pipeline é usado. O programa pode ser aprimorado ainda mais otimizando a formatação de chave/valor no loop interno e adicionando uma fase de aquecimento.