Skip to content

Commit 8eceae4

Browse files
committed
std.compress.zstd: remove allocation from DecompressStream
1 parent ea83463 commit 8eceae4

File tree

4 files changed

+76
-86
lines changed

4 files changed

+76
-86
lines changed

lib/std/compress/zstandard.zig

Lines changed: 51 additions & 82 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,4 @@
11
const std = @import("std");
2-
const Allocator = std.mem.Allocator;
32
const RingBuffer = std.RingBuffer;
43

54
const types = @import("zstandard/types.zig");
@@ -10,7 +9,9 @@ pub const decompress = @import("zstandard/decompress.zig");
109

1110
pub const DecompressStreamOptions = struct {
1211
verify_checksum: bool = true,
13-
window_size_max: usize = 1 << 23, // 8MiB default maximum window size
12+
window_size_max: usize = default_window_size_max,
13+
14+
pub const default_window_size_max = 1 << 23; // 8MiB default maximum window size
1415
};
1516

1617
pub fn DecompressStream(
@@ -20,20 +21,29 @@ pub fn DecompressStream(
2021
return struct {
2122
const Self = @This();
2223

23-
allocator: Allocator,
24+
pub const window_size_max = options.window_size_max;
25+
26+
const table_size_max = types.compressed_block.table_size_max;
27+
2428
source: std.io.CountingReader(ReaderType),
2529
state: enum { NewFrame, InFrame, LastBlock },
2630
decode_state: decompress.block.DecodeState,
2731
frame_context: decompress.FrameContext,
28-
buffer: RingBuffer,
29-
literal_fse_buffer: []types.compressed_block.Table.Fse,
30-
match_fse_buffer: []types.compressed_block.Table.Fse,
31-
offset_fse_buffer: []types.compressed_block.Table.Fse,
32-
literals_buffer: []u8,
33-
sequence_buffer: []u8,
32+
buffer: WindowBuffer,
33+
literal_fse_buffer: [table_size_max.literal]types.compressed_block.Table.Fse,
34+
match_fse_buffer: [table_size_max.match]types.compressed_block.Table.Fse,
35+
offset_fse_buffer: [table_size_max.offset]types.compressed_block.Table.Fse,
36+
literals_buffer: [types.block_size_max]u8,
37+
sequence_buffer: [types.block_size_max]u8,
3438
checksum: if (options.verify_checksum) ?u32 else void,
3539
current_frame_decompressed_size: usize,
3640

41+
const WindowBuffer = struct {
42+
data: *[options.window_size_max]u8 = undefined,
43+
read_index: usize = 0,
44+
write_index: usize = 0,
45+
};
46+
3747
pub const Error = ReaderType.Error || error{
3848
ChecksumFailure,
3949
DictionaryIdFlagUnsupported,
@@ -44,14 +54,13 @@ pub fn DecompressStream(
4454

4555
pub const Reader = std.io.Reader(*Self, Error, read);
4656

47-
pub fn init(allocator: Allocator, source: ReaderType) Self {
57+
pub fn init(source: ReaderType, window_buffer: *[options.window_size_max]u8) Self {
4858
return Self{
49-
.allocator = allocator,
5059
.source = std.io.countingReader(source),
5160
.state = .NewFrame,
5261
.decode_state = undefined,
5362
.frame_context = undefined,
54-
.buffer = undefined,
63+
.buffer = .{ .data = window_buffer },
5564
.literal_fse_buffer = undefined,
5665
.match_fse_buffer = undefined,
5766
.offset_fse_buffer = undefined,
@@ -76,44 +85,11 @@ pub fn DecompressStream(
7685
options.verify_checksum,
7786
);
7887

79-
const literal_fse_buffer = try self.allocator.alloc(
80-
types.compressed_block.Table.Fse,
81-
types.compressed_block.table_size_max.literal,
82-
);
83-
errdefer self.allocator.free(literal_fse_buffer);
84-
85-
const match_fse_buffer = try self.allocator.alloc(
86-
types.compressed_block.Table.Fse,
87-
types.compressed_block.table_size_max.match,
88-
);
89-
errdefer self.allocator.free(match_fse_buffer);
90-
91-
const offset_fse_buffer = try self.allocator.alloc(
92-
types.compressed_block.Table.Fse,
93-
types.compressed_block.table_size_max.offset,
94-
);
95-
errdefer self.allocator.free(offset_fse_buffer);
96-
9788
const decode_state = decompress.block.DecodeState.init(
98-
literal_fse_buffer,
99-
match_fse_buffer,
100-
offset_fse_buffer,
89+
&self.literal_fse_buffer,
90+
&self.match_fse_buffer,
91+
&self.offset_fse_buffer,
10192
);
102-
const buffer = try RingBuffer.init(self.allocator, frame_context.window_size);
103-
104-
const literals_data = try self.allocator.alloc(u8, frame_context.block_size_max);
105-
errdefer self.allocator.free(literals_data);
106-
107-
const sequence_data = try self.allocator.alloc(u8, frame_context.block_size_max);
108-
errdefer self.allocator.free(sequence_data);
109-
110-
self.literal_fse_buffer = literal_fse_buffer;
111-
self.match_fse_buffer = match_fse_buffer;
112-
self.offset_fse_buffer = offset_fse_buffer;
113-
self.literals_buffer = literals_data;
114-
self.sequence_buffer = sequence_data;
115-
116-
self.buffer = buffer;
11793

11894
self.decode_state = decode_state;
11995
self.frame_context = frame_context;
@@ -126,16 +102,6 @@ pub fn DecompressStream(
126102
}
127103
}
128104

129-
pub fn deinit(self: *Self) void {
130-
if (self.state == .NewFrame) return;
131-
self.allocator.free(self.decode_state.literal_fse_buffer);
132-
self.allocator.free(self.decode_state.match_fse_buffer);
133-
self.allocator.free(self.decode_state.offset_fse_buffer);
134-
self.allocator.free(self.literals_buffer);
135-
self.allocator.free(self.sequence_buffer);
136-
self.buffer.deinit(self.allocator);
137-
}
138-
139105
pub fn reader(self: *Self) Reader {
140106
return .{ .context = self };
141107
}
@@ -153,7 +119,6 @@ pub fn DecompressStream(
153119
0
154120
else
155121
error.MalformedFrame,
156-
error.OutOfMemory => return error.OutOfMemory,
157122
else => return error.MalformedFrame,
158123
};
159124
}
@@ -165,33 +130,43 @@ pub fn DecompressStream(
165130
fn readInner(self: *Self, buffer: []u8) Error!usize {
166131
std.debug.assert(self.state != .NewFrame);
167132

133+
var ring_buffer = RingBuffer{
134+
.data = self.buffer.data,
135+
.read_index = self.buffer.read_index,
136+
.write_index = self.buffer.write_index,
137+
};
138+
defer {
139+
self.buffer.read_index = ring_buffer.read_index;
140+
self.buffer.write_index = ring_buffer.write_index;
141+
}
142+
168143
const source_reader = self.source.reader();
169-
while (self.buffer.isEmpty() and self.state != .LastBlock) {
144+
while (ring_buffer.isEmpty() and self.state != .LastBlock) {
170145
const header_bytes = source_reader.readBytesNoEof(3) catch
171146
return error.MalformedFrame;
172147
const block_header = decompress.block.decodeBlockHeader(&header_bytes);
173148

174149
decompress.block.decodeBlockReader(
175-
&self.buffer,
150+
&ring_buffer,
176151
source_reader,
177152
block_header,
178153
&self.decode_state,
179154
self.frame_context.block_size_max,
180-
self.literals_buffer,
181-
self.sequence_buffer,
155+
&self.literals_buffer,
156+
&self.sequence_buffer,
182157
) catch
183158
return error.MalformedBlock;
184159

185160
if (self.frame_context.content_size) |size| {
186161
if (self.current_frame_decompressed_size > size) return error.MalformedFrame;
187162
}
188163

189-
const size = self.buffer.len();
164+
const size = ring_buffer.len();
190165
self.current_frame_decompressed_size += size;
191166

192167
if (self.frame_context.hasher_opt) |*hasher| {
193168
if (size > 0) {
194-
const written_slice = self.buffer.sliceLast(size);
169+
const written_slice = ring_buffer.sliceLast(size);
195170
hasher.update(written_slice.first);
196171
hasher.update(written_slice.second);
197172
}
@@ -216,43 +191,37 @@ pub fn DecompressStream(
216191
}
217192
}
218193

219-
const size = @min(self.buffer.len(), buffer.len);
194+
const size = @min(ring_buffer.len(), buffer.len);
220195
if (size > 0) {
221-
self.buffer.readFirstAssumeLength(buffer, size);
196+
ring_buffer.readFirstAssumeLength(buffer, size);
222197
}
223-
if (self.state == .LastBlock and self.buffer.len() == 0) {
198+
if (self.state == .LastBlock and ring_buffer.len() == 0) {
224199
self.state = .NewFrame;
225-
self.allocator.free(self.literal_fse_buffer);
226-
self.allocator.free(self.match_fse_buffer);
227-
self.allocator.free(self.offset_fse_buffer);
228-
self.allocator.free(self.literals_buffer);
229-
self.allocator.free(self.sequence_buffer);
230-
self.buffer.deinit(self.allocator);
231200
}
232201
return size;
233202
}
234203
};
235204
}
236205

237206
pub fn decompressStreamOptions(
238-
allocator: Allocator,
239207
reader: anytype,
240208
comptime options: DecompressStreamOptions,
209+
window_buffer: *[options.window_size_max]u8,
241210
) DecompressStream(@TypeOf(reader), options) {
242-
return DecompressStream(@TypeOf(reader), options).init(allocator, reader);
211+
return DecompressStream(@TypeOf(reader), options).init(reader, window_buffer);
243212
}
244213

245214
pub fn decompressStream(
246-
allocator: Allocator,
247215
reader: anytype,
216+
window_buffer: *[DecompressStreamOptions.default_window_size_max]u8,
248217
) DecompressStream(@TypeOf(reader), .{}) {
249-
return DecompressStream(@TypeOf(reader), .{}).init(allocator, reader);
218+
return DecompressStream(@TypeOf(reader), .{}).init(reader, window_buffer);
250219
}
251220

252221
fn testDecompress(data: []const u8) ![]u8 {
222+
var window_buffer: [DecompressStreamOptions.default_window_size_max]u8 = undefined;
253223
var in_stream = std.io.fixedBufferStream(data);
254-
var zstd_stream = decompressStream(std.testing.allocator, in_stream.reader());
255-
defer zstd_stream.deinit();
224+
var zstd_stream = decompressStream(in_stream.reader(), &window_buffer);
256225
const result = zstd_stream.reader().readAllAlloc(std.testing.allocator, std.math.maxInt(usize));
257226
return result;
258227
}
@@ -301,9 +270,9 @@ fn expectEqualDecoded(expected: []const u8, input: []const u8) !void {
301270
}
302271

303272
{
273+
var window_buffer: [DecompressStreamOptions.default_window_size_max]u8 = undefined;
304274
var in_stream = std.io.fixedBufferStream(input);
305-
var stream = decompressStream(allocator, in_stream.reader());
306-
defer stream.deinit();
275+
var stream = decompressStream(in_stream.reader(), &window_buffer);
307276

308277
const result = try stream.reader().readAllAlloc(allocator, std.math.maxInt(usize));
309278
defer allocator.free(result);

lib/std/compress/zstandard/decompress.zig

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -409,7 +409,7 @@ pub const FrameContext = struct {
409409
.hasher_opt = if (should_compute_checksum) std.hash.XxHash64.init(0) else null,
410410
.window_size = window_size,
411411
.has_checksum = frame_header.descriptor.content_checksum_flag,
412-
.block_size_max = @min(1 << 17, window_size),
412+
.block_size_max = @min(types.block_size_max, window_size),
413413
.content_size = content_size,
414414
};
415415
}

lib/std/compress/zstandard/types.zig

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,5 @@
1+
pub const block_size_max = 1 << 17;
2+
13
pub const frame = struct {
24
pub const Kind = enum { zstandard, skippable };
35

src/Package/Fetch.zig

Lines changed: 22 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1110,7 +1110,24 @@ fn unpackResource(
11101110
try unpackTarball(f, tmp_directory.handle, dcp.reader());
11111111
},
11121112
.@"tar.xz" => try unpackTarballCompressed(f, tmp_directory.handle, resource, std.compress.xz),
1113-
.@"tar.zst" => try unpackTarballCompressed(f, tmp_directory.handle, resource, ZstdWrapper),
1113+
.@"tar.zst" => {
1114+
const gpa = f.arena.child_allocator;
1115+
const window_size = std.compress.zstd.DecompressStreamOptions.default_window_size_max;
1116+
const window_buffer = gpa.create([window_size]u8) catch |err| {
1117+
return f.fail(f.location_tok, try eb.printString(
1118+
"unable to decompress tarball: {s}",
1119+
.{@errorName(err)},
1120+
));
1121+
};
1122+
defer gpa.destory(window_buffer);
1123+
1124+
try unpackTarballCompressed(
1125+
f,
1126+
tmp_directory.handle,
1127+
resource,
1128+
ZstdWrapper{ .window_buffer = window_buffer },
1129+
);
1130+
},
11141131
.git_pack => unpackGitPack(f, tmp_directory.handle, resource) catch |err| switch (err) {
11151132
error.FetchFailed => return error.FetchFailed,
11161133
error.OutOfMemory => return error.OutOfMemory,
@@ -1125,12 +1142,14 @@ fn unpackResource(
11251142
// due to slight differences in the API of std.compress.(gzip|xz) and std.compress.zstd, zstd is
11261143
// wrapped for generic use in unpackTarballCompressed: see github.com/ziglang/zig/issues/14739
11271144
const ZstdWrapper = struct {
1145+
window_buffer: *[std.compress.zstd.DecompressStreamOptions.default_window_size_max]u8,
1146+
11281147
fn DecompressType(comptime T: type) type {
11291148
return error{}!std.compress.zstd.DecompressStream(T, .{});
11301149
}
11311150

1132-
fn decompress(allocator: Allocator, reader: anytype) DecompressType(@TypeOf(reader)) {
1133-
return std.compress.zstd.decompressStream(allocator, reader);
1151+
fn decompress(self: ZstdWrapper, reader: anytype) DecompressType(@TypeOf(reader)) {
1152+
return std.compress.zstd.decompressStream(reader, self.window_buffer);
11341153
}
11351154
};
11361155

0 commit comments

Comments
 (0)