我正在更新我的网络服务以使用最新的BookSleeve库,1.3.38。以前我使用的是1.1.0.7
在做一些基准测试时,我注意到使用新版BookSleeve在Redis中设置哈希值比旧版本慢很多倍。请考虑以下C#基准测试代码:
public void TestRedisHashes()
{
int numItems = 1000; // number of hash items to set in redis
int numFields = 30; // number of fields in each redis hash
RedisConnection redis = new RedisConnection("10.0.0.01", 6379);
redis.Open();
// wait until the connection is open
while (!redis.State.Equals(BookSleeve.RedisConnectionBase.ConnectionState.Open)) { }
Stopwatch timer = new Stopwatch();
timer.Start();
for (int i = 0; i < numItems; i++)
{
string key = "test_" + i.ToString();
for (int j = 0; j < numFields; j++)
{
// set a value for each field in the hash
redis.Hashes.Set(0, key, "field_" + j.ToString(), "testdata");
}
redis.Keys.Expire(0, key, 30); // 30 second ttl
}
timer.Stop();
Console.WriteLine("Elapsed time for hash writes: {0} ms", timer.ElapsedMilliseconds);
}
BookSleeve 1.1.0.7需要大约20ms才能为Redis 2.6设置1000个哈希值,而1.3.38需要大约400ms。这慢了20倍!我测试的BookSleeve 1.3.38的其他部分要么比旧版本快或快。我也尝试使用Redis 2.4进行相同的测试以及将所有内容包装在事务中。在这两种情况下,我都有类似的表现。
有没有人注意到这样的事情?我一定做错了什么......我使用新版BookSleeve正确设置哈希值吗?这是执行“即发即弃”命令的正确方法吗?我看了the unit tests作为如何使用哈希的一个例子,但是却找不到我正在做的不同的事情。在这种情况下,最新版本是否可能更慢?
答案 0 :(得分:0)
要实际测试整体速度,您需要添加等待最后处理消息的代码,例如:
Task last = null;
for (int i = 0; i < numItems; i++)
{
string key = "test_" + i.ToString();
for (int j = 0; j < numFields; j++)
{
// set a value for each field in the hash
redis.Hashes.Set(0, key, "field_" + j.ToString(), "testdata");
}
last = redis.Keys.Expire(0, key, 30); // 30 second ttl
}
redis.Wait(last);
否则,您的时间安排就是对Set
/ Expire
的调用的速度。在这种情况下,这可能很重要。您会看到,在1.1.0.7中,所有消息都会立即放入队列中,然后单独的专用编写器线程会接收该消息并将其写入流中。在1.3.38中,专用编写器线程已消失(由于各种原因)。所以如果套接字可用,则调用线程写入底层流(如果套接字正在使用,则有一种机制来处理它)。更重要的是,在1.1.0.7的原始测试中,有可能实际上还没有实际有用的工作 - 不能保证工作在插座附近等任何地方。
在大多数情况下,这不会导致任何开销(并且在分摊时会减少开销),但是:在您的情况下,您可能会受到有效的影响>缓冲区欠载 - 在1.1.0.7中你会快速填充缓冲区 ,并且工作线程可能总是会发现更多的等待消息 - 因此它不会在直到结束时刷新流;在1.3.38中,它可能在消息之间刷新。所以:让我们解决这个问题:
Task last = null;
redis.SuspendFlush();
try {
for (int i = 0; i < numItems; i++)
{
string key = "test_" + i.ToString();
for (int j = 0; j < numFields; j++)
{
// set a value for each field in the hash
redis.Hashes.Set(0, key, "field_" + j.ToString(), "testdata");
}
last = redis.Keys.Expire(0, key, 30); // 30 second ttl
}
}
finally {
redis.ResumeFlush();
}
redis.Wait(last);
SuspendFlush()
/ ResumeFlush()
对在单个线程上调用大量操作时非常理想,以避免任何额外的刷新。要复制intellisense笔记:
//
// Summary:
// Temporarily suspends eager-flushing (flushing if the write-queue becomes
// empty briefly). Buffer-based flushing will still occur when the data is full.
// This is useful if you are performing a large number of operations in close
// duration, and want to avoid packet fragmentation. Note that you MUST call
// ResumeFlush at the end of the operation - preferably using Try/Finally so
// that flushing is resumed even upon error. This method is thread-safe; any
// number of callers can suspend/resume flushing concurrently - eager flushing
// will resume fully when all callers have called ResumeFlush.
//
// Remarks:
// Note that some operations (transaction conditions, etc) require flushing
// - this will still occur even if the buffer is only part full.
请注意,在大多数高吞吐量方案中,有多个操作来自多个线程:在这些情况下,并发线程中的任何工作都将以最小化线程数的方式自动排队。