Skip to content

Commit 8d5751d

Browse files
committed
Update within() to make caching optional
1 parent 878a30d commit 8d5751d

7 files changed

Lines changed: 262 additions & 36 deletions

File tree

README.md

Lines changed: 77 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -203,3 +203,80 @@ Lookups Per Second (avg):1315986.2950186788
203203
```
204204

205205
</details>
206+
207+
Use `within()` to iterate over all networks in the database using
208+
a cache to avoid re-decoding adjacent networks that share the same record.
209+
210+
```zig
211+
var cache: maxminddb.Cache(maxminddb.any.Value) = .{};
212+
defer cache.deinit();
213+
214+
var it = try db.within(
215+
allocator,
216+
maxminddb.any.Value,
217+
maxminddb.Network.all_ipv6,
218+
.{ .cache = &cache },
219+
);
220+
defer it.deinit();
221+
222+
while (try it.next()) |item| {
223+
std.debug.print("{f} {f}\n", .{item.network, item.value});
224+
}
225+
```
226+
227+
Without a cache each result owns its memory and must be freed with `item.deinit()`.
228+
229+
Here are reference results on Apple M2 Pro (full GeoLite2-City scan using `any.Value`):
230+
231+
| Benchmark | Records/sec |
232+
|--- |--- |
233+
| No cache | ~1,235,000 |
234+
| Cache (16) | ~2,900,000 |
235+
236+
<details>
237+
238+
<summary>no cache (any.Value)</summary>
239+
240+
```sh
241+
$ for i in $(seq 1 10); do
242+
zig build benchmark_within -Doptimize=ReleaseFast -- GeoLite2-City.mmdb \
243+
2>&1 | grep 'Records Per Second'
244+
done
245+
246+
Records Per Second: 1216758.945145436
247+
Records Per Second: 1238440.9772222256
248+
Records Per Second: 1234710.6362391203
249+
Records Per Second: 1229527.4688849829
250+
Records Per Second: 1243478.3908140333
251+
Records Per Second: 1226863.3718734735
252+
Records Per Second: 1240073.3248202254
253+
Records Per Second: 1247541.1528026997
254+
Records Per Second: 1230510.441029532
255+
Records Per Second: 1246311.587919839
256+
```
257+
258+
</details>
259+
260+
<details>
261+
262+
<summary>cache (any.Value)</summary>
263+
264+
```sh
265+
$ for i in $(seq 1 10); do
266+
zig build benchmark_within_cache -Doptimize=ReleaseFast -- GeoLite2-City.mmdb \
267+
2>&1 | grep 'Records Per Second'
268+
done
269+
270+
Records Per Second: 2847560.3756875996
271+
Records Per Second: 2925388.867798729
272+
Records Per Second: 2919203.9046571665
273+
Records Per Second: 2814410.555872645
274+
Records Per Second: 2933972.04386147
275+
Records Per Second: 2900700.06160036
276+
Records Per Second: 2922279.338699886
277+
Records Per Second: 2862525.847598088
278+
Records Per Second: 2916760.542913819
279+
Records Per Second: 2908245.98918392
280+
```
281+
282+
</details>

benchmarks/within.zig

Lines changed: 59 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,59 @@
1+
const std = @import("std");
2+
const maxminddb = @import("maxminddb");
3+
4+
const default_db_path: []const u8 = "GeoLite2-City.mmdb";
5+
6+
pub fn main() !void {
7+
const allocator = std.heap.smp_allocator;
8+
9+
const args = try std.process.argsAlloc(allocator);
10+
defer std.process.argsFree(allocator, args);
11+
12+
var db_path: []const u8 = default_db_path;
13+
if (args.len > 1) db_path = args[1];
14+
15+
std.debug.print("Benchmarking with:\n", .{});
16+
std.debug.print(" Database: {s}\n", .{db_path});
17+
std.debug.print("Opening database...\n", .{});
18+
19+
var open_timer = try std.time.Timer.start();
20+
var db = try maxminddb.Reader.mmap(allocator, db_path, .{});
21+
defer db.close();
22+
const open_time_ms = @as(f64, @floatFromInt(open_timer.read())) /
23+
@as(f64, @floatFromInt(std.time.ns_per_ms));
24+
std.debug.print("Database opened successfully in {d} ms. Type: {s}\n", .{
25+
open_time_ms,
26+
db.metadata.database_type,
27+
});
28+
29+
const network = if (db.metadata.ip_version == 4)
30+
maxminddb.Network.all_ipv4
31+
else
32+
maxminddb.Network.all_ipv6;
33+
34+
std.debug.print("Starting benchmark...\n", .{});
35+
var timer = try std.time.Timer.start();
36+
37+
var it = try db.within(allocator, maxminddb.any.Value, network, .{});
38+
defer it.deinit();
39+
40+
var n: usize = 0;
41+
while (try it.next()) |item| {
42+
n += 1;
43+
item.deinit();
44+
}
45+
46+
const elapsed_ns = timer.read();
47+
const elapsed_s = @as(f64, @floatFromInt(elapsed_ns)) /
48+
@as(f64, @floatFromInt(std.time.ns_per_s));
49+
50+
const records_per_second = if (elapsed_s > 0)
51+
@as(f64, @floatFromInt(n)) / elapsed_s
52+
else
53+
0.0;
54+
55+
std.debug.print("\n--- Benchmark Finished ---\n", .{});
56+
std.debug.print("Records: {d}\n", .{n});
57+
std.debug.print("Elapsed Time: {d} s\n", .{elapsed_s});
58+
std.debug.print("Records Per Second: {d}\n", .{records_per_second});
59+
}

benchmarks/within_cache.zig

Lines changed: 66 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,66 @@
1+
const std = @import("std");
2+
const maxminddb = @import("maxminddb");
3+
4+
const default_db_path: []const u8 = "GeoLite2-City.mmdb";
5+
6+
pub fn main() !void {
7+
const allocator = std.heap.smp_allocator;
8+
9+
const args = try std.process.argsAlloc(allocator);
10+
defer std.process.argsFree(allocator, args);
11+
12+
var db_path: []const u8 = default_db_path;
13+
if (args.len > 1) db_path = args[1];
14+
15+
std.debug.print("Benchmarking with:\n", .{});
16+
std.debug.print(" Database: {s}\n", .{db_path});
17+
std.debug.print("Opening database...\n", .{});
18+
19+
var open_timer = try std.time.Timer.start();
20+
var db = try maxminddb.Reader.mmap(allocator, db_path, .{});
21+
defer db.close();
22+
const open_time_ms = @as(f64, @floatFromInt(open_timer.read())) /
23+
@as(f64, @floatFromInt(std.time.ns_per_ms));
24+
std.debug.print("Database opened successfully in {d} ms. Type: {s}\n", .{
25+
open_time_ms,
26+
db.metadata.database_type,
27+
});
28+
29+
const network = if (db.metadata.ip_version == 4)
30+
maxminddb.Network.all_ipv4
31+
else
32+
maxminddb.Network.all_ipv6;
33+
34+
var cache: maxminddb.Cache(maxminddb.any.Value) = .{};
35+
defer cache.deinit();
36+
37+
std.debug.print("Starting benchmark...\n", .{});
38+
var timer = try std.time.Timer.start();
39+
40+
var it = try db.within(
41+
allocator,
42+
maxminddb.any.Value,
43+
network,
44+
.{ .cache = &cache },
45+
);
46+
defer it.deinit();
47+
48+
var n: usize = 0;
49+
while (try it.next()) |_| {
50+
n += 1;
51+
}
52+
53+
const elapsed_ns = timer.read();
54+
const elapsed_s = @as(f64, @floatFromInt(elapsed_ns)) /
55+
@as(f64, @floatFromInt(std.time.ns_per_s));
56+
57+
const records_per_second = if (elapsed_s > 0)
58+
@as(f64, @floatFromInt(n)) / elapsed_s
59+
else
60+
0.0;
61+
62+
std.debug.print("\n--- Benchmark Finished ---\n", .{});
63+
std.debug.print("Records: {d}\n", .{n});
64+
std.debug.print("Elapsed Time: {d} s\n", .{elapsed_s});
65+
std.debug.print("Records Per Second: {d}\n", .{records_per_second});
66+
}

build.zig

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,8 @@ pub fn build(b: *std.Build) void {
3232
.{ .file = "benchmarks/lookup.zig", .name = "benchmark_lookup" },
3333
.{ .file = "benchmarks/mycity.zig", .name = "benchmark_mycity" },
3434
.{ .file = "benchmarks/inspect.zig", .name = "benchmark_inspect" },
35+
.{ .file = "benchmarks/within.zig", .name = "benchmark_within" },
36+
.{ .file = "benchmarks/within_cache.zig", .name = "benchmark_within_cache" },
3537
};
3638

3739
{

examples/within.zig

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,9 +19,10 @@ pub fn main() !void {
1919
var it = try db.within(allocator, maxminddb.geolite2.City, network, .{});
2020
defer it.deinit();
2121

22-
// The iterator owns the values; each next() call invalidates the previous item.
2322
var n: usize = 0;
2423
while (try it.next()) |item| {
24+
defer item.deinit();
25+
2526
const continent = item.value.continent.code;
2627
const country = item.value.country.iso_code;
2728
var city: []const u8 = "";

src/maxminddb.zig

Lines changed: 16 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@ pub const Reader = reader.Reader;
1414
pub const Result = reader.Result;
1515
pub const Metadata = reader.Metadata;
1616
pub const Iterator = reader.Iterator;
17-
pub const LookupCache = reader.Cache;
17+
pub const Cache = reader.Cache;
1818
pub const Options = reader.Options;
1919
pub const LookupOptions = reader.LookupOptions;
2020
pub const WithinOptions = reader.WithinOptions;
@@ -887,7 +887,9 @@ test "within returns all networks" {
887887
defer it.deinit();
888888

889889
var n: usize = 0;
890-
while (try it.next()) |_| : (n += 1) {}
890+
while (try it.next()) |item| : (n += 1) {
891+
item.deinit();
892+
}
891893

892894
try expectEqual(242, n);
893895
}
@@ -908,14 +910,16 @@ test "within yields record when query prefix is narrower than record network" {
908910
defer it.deinit();
909911

910912
const item = (try it.next()) orelse return error.TestExpectedNotNull;
913+
defer item.deinit();
911914
try expectEqual(17, item.network.prefix_len);
912915

913916
var out: [256]u8 = undefined;
914917
var w = std.io.Writer.fixed(&out);
915918
try item.network.format(&w);
916919
try expectEqualStrings("89.160.0.0/17", out[0..w.end]);
917920

918-
if (try it.next()) |_| {
921+
if (try it.next()) |i| {
922+
i.deinit();
919923
return error.TestExpectedNull;
920924
}
921925
}
@@ -933,9 +937,11 @@ test "within yields record when start node is a data pointer" {
933937
defer it.deinit();
934938

935939
const item = (try it.next()) orelse return error.TestExpectedNotNull;
940+
defer item.deinit();
936941
try expectEqual(0, item.network.prefix_len);
937942

938-
if (try it.next()) |_| {
943+
if (try it.next()) |i| {
944+
i.deinit();
939945
return error.TestExpectedNull;
940946
}
941947
}
@@ -973,7 +979,9 @@ test "within skips empty records" {
973979
defer it.deinit();
974980

975981
var n: usize = 0;
976-
while (try it.next()) |_| : (n += 1) {}
982+
while (try it.next()) |item| : (n += 1) {
983+
item.deinit();
984+
}
977985
try std.testing.expectEqual(571, n);
978986
}
979987

@@ -985,7 +993,9 @@ test "within skips empty records" {
985993
defer it.deinit();
986994

987995
var n: usize = 0;
988-
while (try it.next()) |_| : (n += 1) {}
996+
while (try it.next()) |item| : (n += 1) {
997+
item.deinit();
998+
}
989999
try std.testing.expectEqual(8, n);
9901000
}
9911001
}

0 commit comments

Comments
 (0)