Repository: vercel/next.js
Pull request: perf(turbopack): Apply small-vector optimization for `TaskId`s

### What?

Reduce memory allocations by applying small vector optimization

### Why?

`sizeof(SmallVec<[TaskId; 4]>)` == `sizeof(Vec<TaskId>)` so it's an simple optimization.

Reducing memory allocation may yield performance improvements. Let's wait for codspeed.

Closes PACK-4878

Changed files:
filename: turbopack/crates/turbo-tasks-backend/src/backend/operation/update_output.rs
patch: @@ -2,6 +2,7 @@ use std::mem::take;
 
 use anyhow::Result;
 use serde::{Deserialize, Serialize};
+use smallvec::SmallVec;
 use turbo_tasks::{RawVc, TaskId, backend::TurboTasksExecutionError};
 
 #[cfg(feature = "trace_task_dirty")]
@@ -26,12 +27,12 @@ pub enum UpdateOutputOperation {
     MakeDependentTasksDirty {
         #[cfg(feature = "trace_task_dirty")]
         task_id: TaskId,
-        dependent_tasks: Vec<TaskId>,
-        children: Vec<TaskId>,
+        dependent_tasks: SmallVec<[TaskId; 4]>,
+        children: SmallVec<[TaskId; 4]>,
         queue: AggregationUpdateQueue,
     },
     EnsureUnfinishedChildrenDirty {
-        children: Vec<TaskId>,
+        children: SmallVec<[TaskId; 4]>,
         queue: AggregationUpdateQueue,
     },
     AggregationUpdate {

filename: turbopack/crates/turbo-tasks-backend/src/backend/storage.rs
patch: @@ -7,6 +7,7 @@ use std::{
 
 use bitfield::bitfield;
 use rayon::iter::{IndexedParallelIterator, IntoParallelRefIterator, ParallelIterator};
+use smallvec::SmallVec;
 use turbo_tasks::{FxDashMap, TaskId};
 
 use crate::{
@@ -670,7 +671,7 @@ impl Storage {
             .with_max_len(1)
             .map(|shard| {
                 let mut direct_snapshots: Vec<(TaskId, Box<InnerStorageSnapshot>)> = Vec::new();
-                let mut modified: Vec<TaskId> = Vec::new();
+                let mut modified: SmallVec<[TaskId; 4]> = SmallVec::new();
                 {
                     // Take the snapshots from the modified map
                     let guard = shard.write();
@@ -1100,7 +1101,7 @@ impl Drop for SnapshotGuard<'_> {
 
 pub struct SnapshotShard<'l, PP, P, PS> {
     direct_snapshots: Vec<(TaskId, Box<InnerStorageSnapshot>)>,
-    modified: Vec<TaskId>,
+    modified: SmallVec<[TaskId; 4]>,
     storage: &'l Storage,
     guard: Option<Arc<SnapshotGuard<'l>>>,
     process: &'l P,

filename: turbopack/crates/turbo-tasks/src/manager.rs
patch: @@ -16,6 +16,7 @@ use anyhow::{Result, anyhow};
 use auto_hash_map::AutoMap;
 use rustc_hash::FxHasher;
 use serde::{Deserialize, Serialize};
+use smallvec::SmallVec;
 use tokio::{runtime::Handle, select, sync::mpsc::Receiver, task_local};
 use tokio_util::task::TaskTracker;
 use tracing::{Instrument, Level, Span, info_span, instrument, trace_span};
@@ -399,7 +400,7 @@ struct CurrentTaskState {
     /// Affected tasks, that are tracked during task execution. These tasks will
     /// be invalidated when the execution finishes or before reading a cell
     /// value.
-    tasks_to_notify: Vec<TaskId>,
+    tasks_to_notify: SmallVec<[TaskId; 4]>,
 
     /// True if the current task has state in cells
     stateful: bool,
@@ -429,7 +430,7 @@ impl CurrentTaskState {
         Self {
             task_id,
             execution_id,
-            tasks_to_notify: Vec::new(),
+            tasks_to_notify: SmallVec::new(),
             stateful: false,
             cell_counters: Some(AutoMap::default()),
             local_tasks: Vec::new(),
@@ -1514,7 +1515,7 @@ impl<B: Backend + 'static> TurboTasksBackendApi<B> for TurboTasks<B> {
             let CurrentTaskState {
                 tasks_to_notify, ..
             } = &mut *cell.write().unwrap();
-            tasks_to_notify.extend(tasks.iter());
+            tasks_to_notify.extend(tasks.iter().copied());
         });
         if result.is_err() {
             let _guard = trace_span!("schedule_notify_tasks", count = tasks.len()).entered();
@@ -1529,7 +1530,7 @@ impl<B: Backend + 'static> TurboTasksBackendApi<B> for TurboTasks<B> {
             let CurrentTaskState {
                 tasks_to_notify, ..
             } = &mut *cell.write().unwrap();
-            tasks_to_notify.extend(tasks.iter());
+            tasks_to_notify.extend(tasks.iter().copied());
         });
         if result.is_err() {
             let _guard = trace_span!("schedule_notify_tasks_set", count = tasks.len()).entered();

filename: turbopack/crates/turbo-tasks/src/persisted_graph.rs
patch: @@ -1,5 +1,6 @@
 use anyhow::Result;
 use serde::{Deserialize, Serialize, ser::SerializeSeq};
+use smallvec::SmallVec;
 
 use crate::{
     CellId, RawVc, TaskId,
@@ -21,8 +22,8 @@ impl Default for TaskCell {
 
 #[derive(Serialize, Deserialize, Debug)]
 pub struct TaskData {
-    pub children: Vec<TaskId>,
-    pub dependencies: Vec<RawVc>,
+    pub children: SmallVec<[TaskId; 4]>,
+    pub dependencies: SmallVec<[RawVc; 1]>,
     pub cells: TaskCells,
     pub output: RawVc,
 }
@@ -130,25 +131,27 @@ pub struct ActivateResult {
 
     /// Further tasks that need to be activated that
     /// didn't fit into that batch
-    pub more_tasks_to_activate: Vec<TaskId>,
+    pub more_tasks_to_activate: SmallVec<[TaskId; 4]>,
 }
 
 #[derive(Debug)]
 pub struct PersistResult {
     /// Tasks that need to be activated
-    pub tasks_to_activate: Vec<TaskId>,
+    pub tasks_to_activate: SmallVec<[TaskId; 4]>,
 
     /// Tasks that need to be deactivated
-    pub tasks_to_deactivate: Vec<TaskId>,
+    pub tasks_to_deactivate: SmallVec<[TaskId; 4]>,
 }
 
 #[derive(Debug)]
 pub struct DeactivateResult {
     /// Further tasks that need to be deactivated that
     /// didn't fit into that batch
-    pub more_tasks_to_deactivate: Vec<TaskId>,
+    pub more_tasks_to_deactivate: SmallVec<[TaskId; 4]>,
 }
 
+pub type TypeIds = SmallVec<[TaskId; 4]>;
+
 pub trait PersistedGraph: Sync + Send {
     /// read task data and state for a specific task.
     fn read(
@@ -216,8 +219,10 @@ pub trait PersistedGraph: Sync + Send {
     /// Removes all external keep alives that were not renewed this round.
     /// This is usually called after the initial build has finished and all
     /// external keep alives has been renewed.
-    fn remove_outdated_externally_active(&self, api: &dyn PersistedGraphApi)
-    -> Result<Vec<TaskId>>;
+    fn remove_outdated_externally_active(
+        &self,
+        api: &dyn PersistedGraphApi,
+    ) -> Result<SmallVec<[TaskId; 4]>>;
 
     /// update the dirty flag for a stored task
     /// Returns true, when the task is active and should be scheduled
@@ -228,23 +233,27 @@ pub trait PersistedGraph: Sync + Send {
 
     /// make all tasks that depend on that vc dirty and
     /// return a list of active tasks that should be scheduled
-    fn make_dependent_dirty(&self, vc: RawVc, api: &dyn PersistedGraphApi) -> Result<Vec<TaskId>>;
+    fn make_dependent_dirty(
+        &self,
+        vc: RawVc,
+        api: &dyn PersistedGraphApi,
+    ) -> Result<SmallVec<[TaskId; 4]>>;
 
     /// Get all tasks that are active, but not persisted.
     /// This is usually called at beginning to create and schedule
     /// tasks that are missing in the persisted graph
-    fn get_active_external_tasks(&self, api: &dyn PersistedGraphApi) -> Result<Vec<TaskId>>;
+    fn get_active_external_tasks(
+        &self,
+        api: &dyn PersistedGraphApi,
+    ) -> Result<SmallVec<[TaskId; 4]>>;
 
     /// Get all tasks that are dirty and active.
     /// This is usually called at the beginning to schedule these tasks.
-    fn get_dirty_active_tasks(&self, api: &dyn PersistedGraphApi) -> Result<Vec<TaskId>>;
+    fn get_dirty_active_tasks(&self, api: &dyn PersistedGraphApi) -> Result<SmallVec<[TaskId; 4]>>;
 
     /// Get tasks that have active update pending that need to be continued
     /// returns (tasks_to_activate, tasks_to_deactivate)
-    fn get_pending_active_update(
-        &self,
-        api: &dyn PersistedGraphApi,
-    ) -> Result<(Vec<TaskId>, Vec<TaskId>)>;
+    fn get_pending_active_update(&self, api: &dyn PersistedGraphApi) -> Result<(TypeIds, TypeIds)>;
 
     /// Stop operations
     #[allow(unused_variables)]
@@ -350,8 +359,8 @@ impl PersistedGraph for () {
     fn remove_outdated_externally_active(
         &self,
         _api: &dyn PersistedGraphApi,
-    ) -> Result<Vec<TaskId>> {
-        Ok(Vec::new())
+    ) -> Result<SmallVec<[TaskId; 4]>> {
+        Ok(Default::default())
     }
 
     fn make_dirty(&self, _task: TaskId, _api: &dyn PersistedGraphApi) -> Result<bool> {
@@ -366,22 +375,28 @@ impl PersistedGraph for () {
         &self,
         _vc: RawVc,
         _api: &dyn PersistedGraphApi,
-    ) -> Result<Vec<TaskId>> {
-        Ok(Vec::new())
+    ) -> Result<SmallVec<[TaskId; 4]>> {
+        Ok(Default::default())
     }
 
-    fn get_active_external_tasks(&self, _api: &dyn PersistedGraphApi) -> Result<Vec<TaskId>> {
-        Ok(Vec::new())
+    fn get_active_external_tasks(
+        &self,
+        _api: &dyn PersistedGraphApi,
+    ) -> Result<SmallVec<[TaskId; 4]>> {
+        Ok(Default::default())
     }
 
-    fn get_dirty_active_tasks(&self, _api: &dyn PersistedGraphApi) -> Result<Vec<TaskId>> {
-        Ok(Vec::new())
+    fn get_dirty_active_tasks(
+        &self,
+        _api: &dyn PersistedGraphApi,
+    ) -> Result<SmallVec<[TaskId; 4]>> {
+        Ok(Default::default())
     }
 
     fn get_pending_active_update(
         &self,
         _api: &dyn PersistedGraphApi,
-    ) -> Result<(Vec<TaskId>, Vec<TaskId>)> {
-        Ok((Vec::new(), Vec::new()))
+    ) -> Result<(SmallVec<[TaskId; 4]>, SmallVec<[TaskId; 4]>)> {
+        Ok((Default::default(), Default::default()))
     }
 }