2
0

benchmark.zig 7.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158
  1. const std = @import("std");
  2. const builtin = @import("builtin");
  3. const engine_mod = @import("engine.zig");
  4. const redis = @import("redis.zig");
  5. const pkbfi = @import("pkbfi.zig");
  6. const allocator = std.heap.smp_allocator;
  7. fn elapsed(start: i128) u64 {
  8. return @intCast(@max(@as(i128, 1), std.time.nanoTimestamp() - start));
  9. }
  10. fn rate(operations: u64, nanoseconds: u64) u64 {
  11. return @intCast((@as(u128, operations) * std.time.ns_per_s) / nanoseconds);
  12. }
  13. fn rss() u64 {
  14. const value = std.posix.getrusage(0).maxrss;
  15. return switch (builtin.target.os.tag) {
  16. .linux => @as(u64, @intCast(value)) * 1024,
  17. else => @intCast(value),
  18. };
  19. }
  20. fn percentile(values: []u64, numerator: usize, denominator: usize) u64 {
  21. std.sort.heap(u64, values, {}, std.sort.asc(u64));
  22. return values[@min(values.len - 1, values.len * numerator / denominator)];
  23. }
  24. pub fn main() !void {
  25. var tmp = std.testing.tmpDir(.{});
  26. defer tmp.cleanup();
  27. var path_buffer: [std.fs.max_path_bytes]u8 = undefined;
  28. const directory = try tmp.dir.realpath(".", &path_buffer);
  29. const path = try std.fs.path.join(allocator, &.{ directory, "benchmark.pkvdb" });
  30. defer allocator.free(path);
  31. var engine = try engine_mod.Engine.open(allocator, path);
  32. var value: [128]u8 = [_]u8{'v'} ** 128;
  33. var latencies: [1000]u64 = undefined;
  34. const initial_file = engine.status().file_bytes;
  35. var started = std.time.nanoTimestamp();
  36. for (0..1000) |index| {
  37. var key_buffer: [32]u8 = undefined;
  38. const key = try std.fmt.bufPrint(&key_buffer, "bench/{d:0>8}", .{index});
  39. const before = std.time.nanoTimestamp();
  40. _ = try engine.put(key, &value);
  41. latencies[index] = elapsed(before);
  42. }
  43. const put_ns = elapsed(started);
  44. const put_p95 = percentile(&latencies, 95, 100);
  45. started = std.time.nanoTimestamp();
  46. for (0..10000) |index| {
  47. var key_buffer: [32]u8 = undefined;
  48. const key = try std.fmt.bufPrint(&key_buffer, "bench/{d:0>8}", .{index % 1000});
  49. const before = std.time.nanoTimestamp();
  50. const result = (try engine.get(allocator, key)).?;
  51. if (index < latencies.len) latencies[index] = elapsed(before);
  52. allocator.free(result.bytes);
  53. }
  54. const get_ns = elapsed(started);
  55. const get_p95 = percentile(&latencies, 95, 100);
  56. const redis_get = "*2\r\n$3\r\nGET\r\n$14\r\nbench/00000001\r\n";
  57. var pipeline = std.ArrayListUnmanaged(u8){};
  58. defer pipeline.deinit(allocator);
  59. for (0..64) |_| try pipeline.appendSlice(allocator, redis_get);
  60. started = std.time.nanoTimestamp();
  61. var redis_operations: u64 = 0;
  62. for (0..100) |_| {
  63. var position: usize = 0;
  64. while (position < pipeline.items.len) {
  65. const parsed = try redis.parse(pipeline.items[position..]);
  66. var response = try redis.execute(&engine, allocator, parsed.command);
  67. response.deinit(allocator);
  68. position += parsed.consumed;
  69. redis_operations += 1;
  70. }
  71. }
  72. const redis_ns = elapsed(started);
  73. var session = pkbfi.Session.init(allocator);
  74. defer session.deinit();
  75. var get_payload: [18]u8 = undefined;
  76. std.mem.writeInt(u32, get_payload[0..4], 14, .little);
  77. @memcpy(get_payload[4..], "bench/00000001");
  78. started = std.time.nanoTimestamp();
  79. for (0..5000) |index| {
  80. const response = try session.execute(&engine, .{ .opcode = .get, .flags = 0, .request_id = index, .payload = &get_payload, .consumed = 0 });
  81. allocator.free(response);
  82. }
  83. const pkbfi_point_ns = elapsed(started);
  84. var batch_payload = std.ArrayListUnmanaged(u8){};
  85. defer batch_payload.deinit(allocator);
  86. try batch_payload.appendNTimes(allocator, 0, 8);
  87. std.mem.writeInt(u32, batch_payload.items[0..4], 10, .little);
  88. for (0..10) |index| {
  89. var key_buffer: [32]u8 = undefined;
  90. const key = try std.fmt.bufPrint(&key_buffer, "batch/{d:0>8}", .{index});
  91. const position = batch_payload.items.len;
  92. try batch_payload.appendNTimes(allocator, 0, 12);
  93. batch_payload.items[position] = 1;
  94. std.mem.writeInt(u32, batch_payload.items[position + 4 ..][0..4], @intCast(key.len), .little);
  95. std.mem.writeInt(u32, batch_payload.items[position + 8 ..][0..4], 32, .little);
  96. try batch_payload.appendSlice(allocator, key);
  97. try batch_payload.appendSlice(allocator, value[0..32]);
  98. }
  99. started = std.time.nanoTimestamp();
  100. for (0..100) |index| {
  101. const response = try session.execute(&engine, .{ .opcode = .batch_write, .flags = 0, .request_id = index, .payload = batch_payload.items, .consumed = 0 });
  102. allocator.free(response);
  103. }
  104. const pkbfi_batch_ns = elapsed(started);
  105. started = std.time.nanoTimestamp();
  106. var cursor: []u8 = try allocator.alloc(u8, 0);
  107. var scanned: u64 = 0;
  108. while (true) {
  109. var batch = try engine.scan(allocator, "bench/", cursor, 128, false, 1024 * 1024);
  110. allocator.free(cursor);
  111. cursor = try allocator.dupe(u8, batch.next_cursor);
  112. scanned += batch.entries.len;
  113. const done = batch.done;
  114. batch.deinit(allocator);
  115. if (done) break;
  116. }
  117. allocator.free(cursor);
  118. const scan_ns = elapsed(started);
  119. started = std.time.nanoTimestamp();
  120. try engine.checkpoint();
  121. const checkpoint_ns = elapsed(started);
  122. const before_overwrite_rss = rss();
  123. for (0..500) |_| _ = try engine.put("overwrite", &value);
  124. const overwrite_rss = rss();
  125. const before_churn_rss = rss();
  126. for (0..250) |index| {
  127. var key_buffer: [32]u8 = undefined;
  128. const key = try std.fmt.bufPrint(&key_buffer, "churn/{d}", .{index});
  129. _ = try engine.put(key, &value);
  130. _ = try engine.delete(key);
  131. }
  132. const churn_rss = rss();
  133. const before_recovery = engine.status();
  134. engine.close();
  135. started = std.time.nanoTimestamp();
  136. engine = try engine_mod.Engine.open(allocator, path);
  137. const recovery_ns = elapsed(started);
  138. defer engine.close();
  139. const status = engine.status();
  140. std.debug.print("point_put ops_s={d} p95_ns={d}\n", .{ rate(1000, put_ns), put_p95 });
  141. std.debug.print("point_get ops_s={d} p95_ns={d}\n", .{ rate(10000, get_ns), get_p95 });
  142. std.debug.print("resp_pipeline ops_s={d} pipeline=64\n", .{rate(redis_operations, redis_ns)});
  143. std.debug.print("pkbfi_get ops_s={d}\n", .{rate(5000, pkbfi_point_ns)});
  144. std.debug.print("pkbfi_batch transactions_s={d} operations_s={d}\n", .{ rate(100, pkbfi_batch_ns), rate(1000, pkbfi_batch_ns) });
  145. std.debug.print("prefix_scan keys_s={d} keys={d}\n", .{ rate(scanned, scan_ns), scanned });
  146. std.debug.print("checkpoint ns={d}\n", .{checkpoint_ns});
  147. std.debug.print("recovery ns={d} keys={d}\n", .{ recovery_ns, status.live_keys });
  148. std.debug.print("rss_overwrite before={d} after={d} delta={d}\n", .{ before_overwrite_rss, overwrite_rss, overwrite_rss -| before_overwrite_rss });
  149. std.debug.print("rss_churn before={d} after={d} delta={d}\n", .{ before_churn_rss, churn_rss, churn_rss -| before_churn_rss });
  150. std.debug.print("directory bytes_per_live_key={d:.2} bytes={d}\n", .{ @as(f64, @floatFromInt(status.keydir_bytes + status.ordered_index_bytes)) / @as(f64, @floatFromInt(status.live_keys)), status.keydir_bytes + status.ordered_index_bytes });
  151. std.debug.print("file_growth bytes={d} bytes_per_put={d:.2} total={d}\n", .{ before_recovery.file_bytes - initial_file, @as(f64, @floatFromInt(before_recovery.file_bytes - initial_file)) / 2600.0, status.file_bytes });
  152. }