aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorGravatar Jarred Sumner <jarred@jarredsumner.com> 2023-06-10 15:38:09 -0700
committerGravatar GitHub <noreply@github.com> 2023-06-10 15:38:09 -0700
commit02eafd5019150357012ebb7a39f1c264ba73599e (patch)
tree1db72d24320147dc0660534576c33ac7f27dc457
parent04cd6a82b880b38c9fe17501f306c5f116e219ce (diff)
downloadbun-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.zig54
-rw-r--r--src/install/npm.zig51
-rw-r--r--src/string_mutable.zig7
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(