diff options
author | 2023-06-10 15:38:09 -0700 | |
---|---|---|
committer | 2023-06-10 15:38:09 -0700 | |
commit | 02eafd5019150357012ebb7a39f1c264ba73599e (patch) | |
tree | 1db72d24320147dc0660534576c33ac7f27dc457 | |
parent | 04cd6a82b880b38c9fe17501f306c5f116e219ce (diff) | |
download | bun-02eafd5019150357012ebb7a39f1c264ba73599e.tar.gz bun-02eafd5019150357012ebb7a39f1c264ba73599e.tar.zst bun-02eafd5019150357012ebb7a39f1c264ba73599e.zip |
Make cold `bun install` use 2x less memory (#3271)
* Make cold `bun install` use 2x less memory
In this benchmark: https://github.com/orogene/orogene/blob/main/BENCHMARKS.md
This brings us from around 2.7 GB to 1.2 GB of memory
* Address comments
---------
Co-authored-by: Jarred Sumner <709451+Jarred-Sumner@users.noreply.github.com>
-rw-r--r-- | src/install/install.zig | 54 | ||||
-rw-r--r-- | src/install/npm.zig | 51 | ||||
-rw-r--r-- | src/string_mutable.zig | 7 |
3 files changed, 69 insertions, 43 deletions
diff --git a/src/install/install.zig b/src/install/install.zig index 2c4e9404f..37aafcc85 100644 --- a/src/install/install.zig +++ b/src/install/install.zig @@ -72,6 +72,37 @@ pub fn initializeStore() void { JSAst.Stmt.Data.Store.create(default_allocator); } +/// The default store we use pre-allocates around 16 MB of memory per thread +/// That adds up in multi-threaded scenarios. +/// ASTMemoryAllocator uses a smaller fixed buffer allocator +pub fn initializeMiniStore() void { + const MiniStore = struct { + heap: bun.MimallocArena, + memory_allocator: JSAst.ASTMemoryAllocator, + + pub threadlocal var instance: ?*@This() = null; + }; + if (MiniStore.instance == null) { + var mini_store = bun.default_allocator.create(MiniStore) catch @panic("OOM"); + mini_store.* = .{ + .heap = bun.MimallocArena.init() catch @panic("OOM"), + .memory_allocator = undefined, + }; + mini_store.memory_allocator = .{ .allocator = mini_store.heap.allocator() }; + mini_store.memory_allocator.reset(); + MiniStore.instance = mini_store; + mini_store.memory_allocator.push(); + } else { + var mini_store = MiniStore.instance.?; + if (mini_store.memory_allocator.stack_allocator.fixed_buffer_allocator.end_index >= mini_store.memory_allocator.stack_allocator.fixed_buffer_allocator.buffer.len -| 1) { + mini_store.heap.reset(); + mini_store.memory_allocator.allocator = mini_store.heap.allocator(); + } + mini_store.memory_allocator.reset(); + mini_store.memory_allocator.push(); + } +} + const IdentityContext = @import("../identity_context.zig").IdentityContext; const ArrayIdentityContext = @import("../identity_context.zig").ArrayIdentityContext; const NetworkQueue = std.fifo.LinearFifo(*NetworkTask, .{ .Static = 32 }); @@ -558,10 +589,16 @@ const Task = struct { switch (this.tag) { .package_manifest => { var allocator = bun.default_allocator; + const body = this.request.package_manifest.network.response_buffer.move(); + + defer { + this.package_manager.resolve_tasks.writeItem(this.*) catch unreachable; + bun.default_allocator.free(body); + } const package_manifest = Npm.Registry.getPackageMetadata( allocator, this.request.package_manifest.network.http.response.?, - this.request.package_manifest.network.response_buffer.toOwnedSliceLeaky(), + body, &this.log, this.request.package_manifest.name.slice(), this.request.package_manifest.network.callback.package_manifest.loaded_manifest, @@ -575,7 +612,6 @@ const Task = struct { this.err = err; this.status = Status.fail; this.data = .{ .package_manifest = .{} }; - this.package_manager.resolve_tasks.writeItem(this.*) catch unreachable; return; }; @@ -584,7 +620,6 @@ const Task = struct { .fresh => |manifest| { this.status = Status.success; this.data = .{ .package_manifest = manifest }; - this.package_manager.resolve_tasks.writeItem(this.*) catch unreachable; return; }, .not_found => { @@ -593,14 +628,20 @@ const Task = struct { }) catch unreachable; this.status = Status.fail; this.data = .{ .package_manifest = .{} }; - this.package_manager.resolve_tasks.writeItem(this.*) catch unreachable; return; }, } }, .extract => { + const bytes = this.request.extract.network.response_buffer.move(); + + defer { + this.package_manager.resolve_tasks.writeItem(this.*) catch unreachable; + bun.default_allocator.free(bytes); + } + const result = this.request.extract.tarball.run( - this.request.extract.network.response_buffer.toOwnedSliceLeaky(), + bytes, ) catch |err| { if (comptime Environment.isDebug) { if (@errorReturnTrace()) |trace| { @@ -611,13 +652,11 @@ const Task = struct { this.err = err; this.status = Status.fail; this.data = .{ .extract = .{} }; - this.package_manager.resolve_tasks.writeItem(this.*) catch unreachable; return; }; this.data = .{ .extract = result }; this.status = Status.success; - this.package_manager.resolve_tasks.writeItem(this.*) catch unreachable; }, .git_clone => { const manager = this.package_manager; @@ -1690,6 +1729,7 @@ pub const PackageManager = struct { pub fn sleep(this: *PackageManager) void { if (this.wait_count.swap(0, .Monotonic) > 0) return; + bun.Mimalloc.mi_collect(false); _ = this.waiter.wait() catch 0; } diff --git a/src/install/npm.zig b/src/install/npm.zig index 0e26a5588..283d20b39 100644 --- a/src/install/npm.zig +++ b/src/install/npm.zig @@ -11,7 +11,7 @@ const PackageManager = @import("./install.zig").PackageManager; const ExternalStringMap = @import("./install.zig").ExternalStringMap; const ExternalStringList = @import("./install.zig").ExternalStringList; const ExternalSlice = @import("./install.zig").ExternalSlice; -const initializeStore = @import("./install.zig").initializeStore; +const initializeStore = @import("./install.zig").initializeMiniStore; const logger = @import("root").bun.logger; const Output = @import("root").bun.Output; const Integrity = @import("./integrity.zig").Integrity; @@ -768,15 +768,6 @@ pub const PackageManifest = struct { const ExternalStringMapDeduper = std.HashMap(u64, ExternalStringList, IdentityContext(u64), 80); - threadlocal var string_pool_: String.Builder.StringPool = undefined; - threadlocal var string_pool_loaded: bool = false; - - threadlocal var external_string_maps_: ExternalStringMapDeduper = undefined; - threadlocal var external_string_maps_loaded: bool = false; - - threadlocal var optional_peer_dep_names_: std.ArrayList(u64) = undefined; - threadlocal var optional_peer_dep_names_loaded: bool = false; - /// This parses [Abbreviated metadata](https://github.com/npm/registry/blob/master/docs/responses/package-metadata.md#abbreviated-metadata-format) pub fn parse( allocator: std.mem.Allocator, @@ -789,7 +780,14 @@ pub const PackageManifest = struct { ) !?PackageManifest { const source = logger.Source.initPathString(expected_name, json_buffer); initializeStore(); - const json = json_parser.ParseJSONUTF8(&source, log, allocator) catch return null; + defer bun.JSAst.Stmt.Data.Store.memory_allocator.?.pop(); + var arena = std.heap.ArenaAllocator.init(allocator); + defer arena.deinit(); + const json = json_parser.ParseJSONUTF8( + &source, + log, + arena.allocator(), + ) catch return null; if (json.asProperty("error")) |error_q| { if (error_q.expr.asString(allocator)) |err| { @@ -800,31 +798,12 @@ pub const PackageManifest = struct { var result = PackageManifest{}; - if (!string_pool_loaded) { - string_pool_ = String.Builder.StringPool.init(default_allocator); - string_pool_loaded = true; - } - - if (!external_string_maps_loaded) { - external_string_maps_ = ExternalStringMapDeduper.initContext(default_allocator, .{}); - external_string_maps_loaded = true; - } - - if (!optional_peer_dep_names_loaded) { - optional_peer_dep_names_ = std.ArrayList(u64).init(default_allocator); - optional_peer_dep_names_loaded = true; - } - - var string_pool = string_pool_; - string_pool.clearRetainingCapacity(); - var external_string_maps = external_string_maps_; - external_string_maps.clearRetainingCapacity(); - var optional_peer_dep_names = optional_peer_dep_names_; - optional_peer_dep_names.clearRetainingCapacity(); - - defer string_pool_ = string_pool; - defer external_string_maps_ = external_string_maps; - defer optional_peer_dep_names_ = optional_peer_dep_names; + var string_pool = String.Builder.StringPool.init(default_allocator); + defer string_pool.deinit(); + var external_string_maps = ExternalStringMapDeduper.initContext(default_allocator, .{}); + defer external_string_maps.deinit(); + var optional_peer_dep_names = std.ArrayList(u64).init(default_allocator); + defer optional_peer_dep_names.deinit(); var string_builder = String.Builder{ .string_pool = string_pool, diff --git a/src/string_mutable.zig b/src/string_mutable.zig index e6738ae7f..cb8e1633e 100644 --- a/src/string_mutable.zig +++ b/src/string_mutable.zig @@ -235,6 +235,13 @@ pub const MutableString = struct { return self.list.items; } + /// Clear the existing value without freeing the memory or shrinking the capacity. + pub fn move(self: *MutableString) []u8 { + const out = self.list.items; + self.list = .{}; + return out; + } + pub fn toOwnedSentinelLeaky(self: *MutableString) [:0]u8 { if (self.list.items.len > 0 and self.list.items[self.list.items.len - 1] != 0) { self.list.append( |