Skip to content

Commit 0e842c7

Browse files
committed
Separate methods to use cache, rename within to scan
1 parent 77c0a19 commit 0e842c7

13 files changed

Lines changed: 259 additions & 170 deletions

File tree

README.md

Lines changed: 66 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -54,13 +54,37 @@ var db = try maxminddb.Reader.mmap(allocator, db_path, .{ .ipv4_index_first_n_bi
5454
defer db.close();
5555
```
5656

57-
Use `ArenaAllocator` for best performance, see [benchmarks](./benchmarks/).
57+
Each `lookup` result owns an arena with all decoded allocations.
58+
Call `deinit()` to free it or use `ArenaAllocator` with `reset()`,
59+
see [benchmarks](./benchmarks/lookup.zig).
60+
61+
```zig
62+
if (try db.lookup(maxminddb.geolite2.City, allocator, ip, .{})) |result| {
63+
defer result.deinit();
64+
std.debug.print("{f} {s}\n", .{ result.network, result.value.city.names.?.get("en").? });
65+
}
66+
67+
var arena = std.heap.ArenaAllocator.init(allocator);
68+
defer arena.deinit();
69+
70+
const arena_allocator = arena.allocator();
71+
for (ips) |ip| {
72+
if (try db.lookup(maxminddb.geolite2.City, arena_allocator, ip, .{})) |result| {
73+
std.debug.print("{f} {s}\n", .{ result.network, result.value.city.names.?.get("en").? });
74+
}
75+
76+
_ = arena.reset(.retain_capacity);
77+
}
78+
```
5879

5980
If you don't need all the fields, use `.only` to decode only the top-level fields you want.
6081

6182
```zig
6283
const fields = &.{ "city", "country" };
63-
const city = try db.lookup(allocator, maxminddb.geolite2.City, ip, .{ .only = fields });
84+
if (try db.lookup(maxminddb.geolite2.City, allocator, ip, .{ .only = fields })) |result| {
85+
defer result.deinit();
86+
std.debug.print("{f} {s}\n", .{ result.network, result.value.city.names.?.get("en").? });
87+
}
6488
```
6589

6690
Alternatively, define your own struct with only the fields you need.
@@ -74,36 +98,42 @@ const MyCity = struct {
7498
} = .{},
7599
};
76100
77-
const city = try db.lookup(allocator, MyCity, ip, .{});
101+
if (try db.lookup(MyCity, allocator, ip, .{})) |result| {
102+
defer result.deinit();
103+
std.debug.print("{s}\n", .{result.value.city.names.en});
104+
}
78105
```
79106

80107
Use `any.Value` to decode any record without knowing the schema.
81108

82109
```zig
83-
const result = try db.lookup(allocator, maxminddb.any.Value, ip, .{ .only = fields });
84-
if (result) |r| {
110+
if (try db.lookup(maxminddb.any.Value, allocator, ip, .{ .only = fields })) |result| {
111+
defer result.deinit();
85112
// Formats as compact JSON.
86-
std.debug.print("{f}\n", .{r.value});
113+
std.debug.print("{f}\n", .{result.value});
87114
}
88115
```
89116

90-
Use a `Cache` to skip decoding when different IPs resolve to the same record.
117+
Use `lookupWithCache` to skip decoding when different IPs resolve to the same record.
118+
The cache owns decoded memory, so results don't need to be individually freed.
91119

92120
```zig
93-
var cache: maxminddb.Cache(maxminddb.geolite2.City) = .{};
121+
var cache = try maxminddb.Cache(maxminddb.geolite2.City).init(allocator, .{});
94122
defer cache.deinit();
95123
96-
const city = try db.lookup(allocator, maxminddb.geolite2.City, ip, .{ .cache = &cache });
124+
if (try db.lookupWithCache(maxminddb.geolite2.City, &cache, ip, .{})) |result| {
125+
std.debug.print("{f} {s}\n", .{ result.network, result.value.city.names.?.get("en").? });
126+
}
97127
```
98128

99129
Here are reference results on Apple M2 Pro (1M random IPv4 lookups against GeoLite2-City
100130
with `ipv4_index_first_n_bits = 16`):
101131

102-
| Type | Default | `.only` | `Cache` |
103-
|--- |--- |--- |--- |
104-
| `geolite2.City` | ~1,284,000 | ~1,348,000 | ~1,474,000 |
105-
| `MyCity` | ~1,383,000 | | |
106-
| `any.Value` | ~1,254,000 | ~1,349,000 | |
132+
| Type | Default | `.only` | `Cache` |
133+
|--- |--- |--- |--- |
134+
| `geolite2.City` | ~1,420,000 | ~1,348,000 | ~1,565,000 |
135+
| `MyCity` | ~1,383,000 | | |
136+
| `any.Value` | ~1,254,000 | ~1,349,000 | |
107137

108138
<details>
109139

@@ -237,42 +267,45 @@ Lookups Per Second (avg):1315986.2950186788
237267

238268
</details>
239269

240-
Use `within()` to iterate over all networks in the database.
241-
A `Cache` avoids re-decoding networks that share the same record.
270+
Use `scan` to iterate over all networks in the database.
242271

243272
```zig
244-
var cache: maxminddb.Cache(maxminddb.any.Value) = .{};
273+
var it = try db.scan(maxminddb.any.Value, allocator, maxminddb.Network.all_ipv6, .{});
274+
275+
while (try it.next()) |item| {
276+
defer item.deinit();
277+
std.debug.print("{f} {f}\n", .{ item.network, item.value });
278+
}
279+
```
280+
281+
Use `scanWithCache` to avoid re-decoding networks that share the same record.
282+
The cache owns decoded memory, so results don't need to be individually freed.
283+
284+
```zig
285+
var cache = try maxminddb.Cache(maxminddb.any.Value).init(allocator, .{});
245286
defer cache.deinit();
246287
247-
var it = try db.within(
248-
allocator,
249-
maxminddb.any.Value,
250-
maxminddb.Network.all_ipv6,
251-
.{ .cache = &cache },
252-
);
253-
defer it.deinit();
288+
var it = try db.scanWithCache(maxminddb.any.Value, &cache, maxminddb.Network.all_ipv6, .{});
254289
255290
while (try it.next()) |item| {
256-
std.debug.print("{f} {f}\n", .{item.network, item.value});
291+
std.debug.print("{f} {f}\n", .{ item.network, item.value });
257292
}
258293
```
259294

260-
Without a cache each result owns its memory and must be freed with `item.deinit()`.
261-
262295
Here are reference results on Apple M2 Pro (full GeoLite2-City scan using `any.Value`):
263296

264-
| Mode | Records/sec |
265-
|--- |--- |
266-
| Default | ~1,235,000 |
267-
| `Cache` | ~2,900,000 |
297+
| Mode | Records/sec |
298+
|--- |--- |
299+
| Default | ~1,295,000 |
300+
| `Cache` | ~2,930,000 |
268301

269302
<details>
270303

271304
<summary>no cache (any.Value)</summary>
272305

273306
```sh
274307
$ for i in $(seq 1 10); do
275-
zig build benchmark_within -Doptimize=ReleaseFast -- GeoLite2-City.mmdb \
308+
zig build benchmark_scan -Doptimize=ReleaseFast -- GeoLite2-City.mmdb \
276309
2>&1 | grep 'Records Per Second'
277310
done
278311

@@ -296,7 +329,7 @@ Records Per Second: 1246311.587919839
296329

297330
```sh
298331
$ for i in $(seq 1 10); do
299-
zig build benchmark_within_cache -Doptimize=ReleaseFast -- GeoLite2-City.mmdb \
332+
zig build benchmark_scan_cache -Doptimize=ReleaseFast -- GeoLite2-City.mmdb \
300333
2>&1 | grep 'Records Per Second'
301334
done
302335

benchmarks/inspect.zig

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -59,8 +59,8 @@ pub fn main() !void {
5959
const ip = std.net.Address.initIp4(ip_bytes, 0);
6060

6161
const result = db.lookup(
62-
arena_allocator,
6362
maxminddb.any.Value,
63+
arena_allocator,
6464
ip,
6565
.{ .only = fields },
6666
) catch |err| {

benchmarks/lookup.zig

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -59,8 +59,8 @@ pub fn main() !void {
5959
const ip = std.net.Address.initIp4(ip_bytes, 0);
6060

6161
const result = db.lookup(
62-
arena_allocator,
6362
maxminddb.geolite2.City,
63+
arena_allocator,
6464
ip,
6565
.{ .only = fields },
6666
) catch |err| {

benchmarks/lookup_cache.zig

Lines changed: 4 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -43,13 +43,9 @@ pub fn main() !void {
4343
db.metadata.database_type,
4444
});
4545

46-
var cache: maxminddb.Cache(maxminddb.geolite2.City) = .{};
46+
var cache = try maxminddb.Cache(maxminddb.geolite2.City).init(allocator, .{});
4747
defer cache.deinit();
4848

49-
var arena = std.heap.ArenaAllocator.init(allocator);
50-
defer arena.deinit();
51-
const arena_allocator = arena.allocator();
52-
5349
std.debug.print("Starting benchmark...\n", .{});
5450
var timer = try std.time.Timer.start();
5551
var not_found_count: u64 = 0;
@@ -60,11 +56,11 @@ pub fn main() !void {
6056
std.crypto.random.bytes(&ip_bytes);
6157
const ip = std.net.Address.initIp4(ip_bytes, 0);
6258

63-
const result = db.lookup(
64-
arena_allocator,
59+
const result = db.lookupWithCache(
6560
maxminddb.geolite2.City,
61+
&cache,
6662
ip,
67-
.{ .only = fields, .cache = &cache },
63+
.{ .only = fields },
6864
) catch |err| {
6965
std.debug.print("! Lookup error for IP {any}: {any}\n", .{ ip, err });
7066
lookup_errors += 1;
@@ -74,8 +70,6 @@ pub fn main() !void {
7470
not_found_count += 1;
7571
continue;
7672
}
77-
78-
_ = arena.reset(.retain_capacity);
7973
}
8074

8175
const elapsed_ns = timer.read();

benchmarks/mycity.zig

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -53,8 +53,8 @@ pub fn main() !void {
5353
const ip = std.net.Address.initIp4(ip_bytes, 0);
5454

5555
const result = db.lookup(
56-
arena_allocator,
5756
MyCity,
57+
arena_allocator,
5858
ip,
5959
.{},
6060
) catch |err| {
Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -34,8 +34,7 @@ pub fn main() !void {
3434
std.debug.print("Starting benchmark...\n", .{});
3535
var timer = try std.time.Timer.start();
3636

37-
var it = try db.within(allocator, maxminddb.any.Value, network, .{});
38-
defer it.deinit();
37+
var it = try db.scan(maxminddb.any.Value, allocator, network, .{});
3938

4039
var n: usize = 0;
4140
while (try it.next()) |item| {
Lines changed: 4 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -31,19 +31,18 @@ pub fn main() !void {
3131
else
3232
maxminddb.Network.all_ipv6;
3333

34-
var cache: maxminddb.Cache(maxminddb.any.Value) = .{};
34+
var cache = try maxminddb.Cache(maxminddb.any.Value).init(allocator, .{});
3535
defer cache.deinit();
3636

3737
std.debug.print("Starting benchmark...\n", .{});
3838
var timer = try std.time.Timer.start();
3939

40-
var it = try db.within(
41-
allocator,
40+
var it = try db.scanWithCache(
4241
maxminddb.any.Value,
42+
&cache,
4343
network,
44-
.{ .cache = &cache },
44+
.{},
4545
);
46-
defer it.deinit();
4746

4847
var n: usize = 0;
4948
while (try it.next()) |_| {

build.zig

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -27,14 +27,14 @@ pub fn build(b: *std.Build) void {
2727
name: []const u8,
2828
}{
2929
.{ .file = "examples/lookup.zig", .name = "example_lookup" },
30-
.{ .file = "examples/within.zig", .name = "example_within" },
3130
.{ .file = "examples/inspect.zig", .name = "example_inspect" },
31+
.{ .file = "examples/scan.zig", .name = "example_scan" },
3232
.{ .file = "benchmarks/lookup.zig", .name = "benchmark_lookup" },
3333
.{ .file = "benchmarks/lookup_cache.zig", .name = "benchmark_lookup_cache" },
3434
.{ .file = "benchmarks/mycity.zig", .name = "benchmark_mycity" },
3535
.{ .file = "benchmarks/inspect.zig", .name = "benchmark_inspect" },
36-
.{ .file = "benchmarks/within.zig", .name = "benchmark_within" },
37-
.{ .file = "benchmarks/within_cache.zig", .name = "benchmark_within_cache" },
36+
.{ .file = "benchmarks/scan.zig", .name = "benchmark_scan" },
37+
.{ .file = "benchmarks/scan_cache.zig", .name = "benchmark_scan_cache" },
3838
};
3939

4040
{

examples/inspect.zig

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,8 +15,8 @@ pub fn main() !void {
1515
defer db.close();
1616

1717
const result = try db.lookup(
18-
allocator,
1918
maxminddb.any.Value,
19+
allocator,
2020
try std.net.Address.parseIp(ip, 0),
2121
.{},
2222
) orelse {

examples/lookup.zig

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@ pub fn main() !void {
1414
// Note, for better performance use arena allocator and reset it after calling lookup().
1515
// You won't need to call city.deinit() in that case.
1616
const ip = try std.net.Address.parseIp("89.160.20.128", 0);
17-
const city = try db.lookup(allocator, maxminddb.geoip2.City, ip, .{}) orelse return;
17+
const city = try db.lookup(maxminddb.geoip2.City, allocator, ip, .{}) orelse return;
1818
defer city.deinit();
1919

2020
for (city.value.country.names.?.entries) |e| {

0 commit comments

Comments
 (0)