在编写这样的基准测试(尤其是使用 JVM 的基准测试)之前,您需要考虑以下几点:
在大多数(物理)机器上,Redis 在使用流水线时能够处理超过 100K ops/s。您的基准仅处理 100K 项目,因此它持续的时间不足以产生有意义的结果。此外,没有时间让 JIT 的后续阶段启动。
绝对时间不是一个非常相关的指标。在保持基准运行至少 10 秒的同时显示吞吐量(即每秒操作数)将是一个更好、更稳定的指标。
你的内部循环会产生很多垃圾。如果您打算对 Jedis+Redis 进行基准测试,那么您需要将自己的程序的开销保持在较低水平。
因为您已将所有内容都定义到主函数中,所以 JIT 不会编译您的循环(取决于您使用的 JVM)。只有内部方法调用可能。如果您希望 JIT 高效,请确保将您的代码封装到 JIT 可以编译的方法中。
可选地,您可能希望在执行实际测量之前添加一个预热阶段,以避免计算使用准系统解释器运行第一次迭代的开销以及 JIT 本身的成本。
现在,关于 Redis 管道,您的管道太长了。管道中的 100K 命令意味着 Jedis 在向 Redis 发送任何内容之前必须构建一个 6MB 的缓冲区。这意味着套接字缓冲区(在客户端,也许在服务器端)将饱和,Redis 也将不得不处理 6 MB 的通信缓冲区。
此外,您的基准测试仍然是同步的(使用管道不会神奇地使其异步)。换句话说,Jedis 不会开始阅读回复,直到您的管道的最后一个查询已发送到 Redis。当管道太长时,它有可能阻塞东西。
考虑将管道的大小限制为 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 倍的吞吐量。通过优化内部循环中的键/值格式并添加预热阶段,可以进一步改进程序。