瀏覽代碼

Optimization: Multithreading the SetupVelocityConstraints job (#788)

This was causing a bottleneck in the case that there are a lot of constraints but very little possible collisions.
Jorrit Rouwe 1 年之前
父節點
當前提交
65cb4d23d5

+ 5 - 5
Docs/PhysicsSystemUpdate.drawio

@@ -1,6 +1,6 @@
-<mxfile host="Electron" modified="2023-09-24T13:05:17.254Z" agent="Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) draw.io/21.7.5 Chrome/114.0.5735.289 Electron/25.8.1 Safari/537.36" etag="y67BJ_dSAoWEx4PTNBpq" version="21.7.5" type="device">
+<mxfile host="app.diagrams.net" modified="2023-12-11T20:08:54.047Z" agent="Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36" etag="nJirzh5xXBcsb4evVA8J" version="21.8.2" type="device">
   <diagram id="rLFVS3KHCrdhIcSo5p6n" name="Page-1">
-    <mxGraphModel dx="1548" dy="894" grid="1" gridSize="10" guides="1" tooltips="1" connect="1" arrows="1" fold="1" page="1" pageScale="1" pageWidth="850" pageHeight="1100" background="#FFFFFF" math="0" shadow="0">
+    <mxGraphModel dx="1562" dy="810" grid="1" gridSize="10" guides="1" tooltips="1" connect="1" arrows="1" fold="1" page="1" pageScale="1" pageWidth="850" pageHeight="1100" background="#FFFFFF" math="0" shadow="0">
       <root>
         <mxCell id="0" />
         <mxCell id="1" parent="0" />
@@ -30,7 +30,7 @@
             </Array>
           </mxGeometry>
         </mxCell>
-        <mxCell id="8" value="&lt;div style=&#39;width: 93.0px;height:auto;word-break: break-word;&#39;&gt;&lt;div align=&quot;center&quot;&gt;&lt;span style=&quot;font-size: 12px; font-family: Arial; white-space: pre-wrap; text-decoration: none; line-height: 14px; color: rgb(0, 0, 0);&quot;&gt;Setup Velocity Constraints&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;" style="shape=rect;shadow=0;strokeWidth=2;fillColor=#ffffff;strokeColor=#333333;opacity=100.0;html=1;nl2Br=0;verticalAlign=middle;align=center;spacingLeft=2.0;spacingRight=0;whiteSpace=wrap;gliffyId=30;" parent="1" vertex="1">
+        <mxCell id="8" value="&lt;div style=&#39;width: 93.0px;height:auto;word-break: break-word;&#39;&gt;&lt;div align=&quot;center&quot;&gt;&lt;span style=&quot;font-size: 12px; font-family: Arial; white-space: pre-wrap; text-decoration: none; line-height: 14px; color: rgb(0, 0, 0);&quot;&gt;Setup Velocity Constraints&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;" style="shape=rect;shadow=0;strokeWidth=2;fillColor=#FFF2CC;strokeColor=#333333;opacity=100.0;html=1;nl2Br=0;verticalAlign=middle;align=center;spacingLeft=2.0;spacingRight=0;whiteSpace=wrap;gliffyId=30;" parent="1" vertex="1">
           <mxGeometry x="360.3299865722656" y="197.25" width="100" height="62.75" as="geometry" />
         </mxCell>
         <mxCell id="9" value="&lt;div style=&#39;width: 93.0px;height:auto;word-break: break-word;&#39;&gt;&lt;div align=&quot;center&quot;&gt;&lt;span style=&quot;font-size: 12px; font-family: Arial; white-space: pre-wrap; text-decoration: none; line-height: 14px; color: rgb(0, 0, 0);&quot;&gt;Pre Integrate&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;" style="shape=rect;shadow=0;strokeWidth=2;fillColor=#ffffff;strokeColor=#333333;opacity=100.0;html=1;nl2Br=0;verticalAlign=middle;align=center;spacingLeft=2.0;spacingRight=0;whiteSpace=wrap;gliffyId=48;" parent="1" vertex="1">
@@ -539,10 +539,10 @@
         <mxCell id="2HlbSkl1Hx2XcQlONuJN-140" value="&lt;div style=&#39;width: 13.32px;height:auto;word-break: break-word;&#39;&gt;&lt;div align=&quot;center&quot;&gt;&lt;span style=&quot;font-size: 12px; font-family: Arial; white-space: pre-wrap; text-decoration: none; line-height: 14px; color: rgb(0, 0, 0);&quot;&gt;V&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;" style="shape=rect;shadow=0;strokeWidth=2;fillColor=#ff0000;strokeColor=#333333;gradientColor=#FFAAAA;gradientDirection=north;opacity=100.0;html=1;nl2Br=0;verticalAlign=middle;align=center;spacingLeft=0.34;spacingRight=0;whiteSpace=wrap;gliffyId=198;" parent="1" vertex="1">
           <mxGeometry x="1850" y="371.1" width="17" height="17" as="geometry" />
         </mxCell>
-        <mxCell id="1HMQW9uxuVFfJUHc01B5-122" value="&lt;div style=&#39;width: 13.32px;height:auto;word-break: break-word;&#39;&gt;&lt;div align=&quot;center&quot;&gt;&lt;span style=&quot;font-size: 12px; font-family: Arial; white-space: pre-wrap; text-decoration: none; line-height: 14px; color: rgb(0, 0, 0);&quot;&gt;A&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;" style="shape=rect;shadow=0;strokeWidth=2;fillColor=#ff9900;strokeColor=#333333;gradientColor=#FFFFAA;gradientDirection=north;opacity=100.0;html=1;nl2Br=0;verticalAlign=middle;align=center;spacingLeft=0.34;spacingRight=0;whiteSpace=wrap;gliffyId=438;" vertex="1" parent="1">
+        <mxCell id="1HMQW9uxuVFfJUHc01B5-122" value="&lt;div style=&#39;width: 13.32px;height:auto;word-break: break-word;&#39;&gt;&lt;div align=&quot;center&quot;&gt;&lt;span style=&quot;font-size: 12px; font-family: Arial; white-space: pre-wrap; text-decoration: none; line-height: 14px; color: rgb(0, 0, 0);&quot;&gt;A&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;" style="shape=rect;shadow=0;strokeWidth=2;fillColor=#ff9900;strokeColor=#333333;gradientColor=#FFFFAA;gradientDirection=north;opacity=100.0;html=1;nl2Br=0;verticalAlign=middle;align=center;spacingLeft=0.34;spacingRight=0;whiteSpace=wrap;gliffyId=438;" parent="1" vertex="1">
           <mxGeometry x="2004.0040594482423" y="370" width="17" height="17" as="geometry" />
         </mxCell>
-        <mxCell id="1HMQW9uxuVFfJUHc01B5-123" value="&lt;div style=&#39;width: 13.32px;height:auto;word-break: break-word;&#39;&gt;&lt;div align=&quot;center&quot;&gt;&lt;span style=&quot;font-size: 12px; font-family: Arial; white-space: pre-wrap; text-decoration: none; line-height: 14px; color: rgb(0, 0, 0);&quot;&gt;A&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;" style="shape=rect;shadow=0;strokeWidth=2;fillColor=#ff0000;strokeColor=#333333;gradientColor=#FFAAAA;gradientDirection=north;opacity=100.0;html=1;nl2Br=0;verticalAlign=middle;align=center;spacingLeft=0.34;spacingRight=0;whiteSpace=wrap;gliffyId=341;" vertex="1" parent="1">
+        <mxCell id="1HMQW9uxuVFfJUHc01B5-123" value="&lt;div style=&#39;width: 13.32px;height:auto;word-break: break-word;&#39;&gt;&lt;div align=&quot;center&quot;&gt;&lt;span style=&quot;font-size: 12px; font-family: Arial; white-space: pre-wrap; text-decoration: none; line-height: 14px; color: rgb(0, 0, 0);&quot;&gt;A&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;" style="shape=rect;shadow=0;strokeWidth=2;fillColor=#ff0000;strokeColor=#333333;gradientColor=#FFAAAA;gradientDirection=north;opacity=100.0;html=1;nl2Br=0;verticalAlign=middle;align=center;spacingLeft=0.34;spacingRight=0;whiteSpace=wrap;gliffyId=341;" parent="1" vertex="1">
           <mxGeometry x="2021.0040594482423" y="370" width="17" height="17" as="geometry" />
         </mxCell>
       </root>

File diff suppressed because it is too large
+ 0 - 0
Docs/PhysicsSystemUpdate.svg


+ 29 - 13
Jolt/Physics/PhysicsSystem.cpp

@@ -191,6 +191,9 @@ EPhysicsUpdateError PhysicsSystem::Update(float inDeltaTime, int inCollisionStep
 	// Leave 1 thread for update broadphase prepare and 1 for apply gravity
 	int num_determine_active_constraints_jobs = max(1, min(((int)mConstraintManager.GetNumConstraints() + cDetermineActiveConstraintsBatchSize - 1) / cDetermineActiveConstraintsBatchSize, max_concurrency - 2));
 
+	// Number of setup velocity constraints jobs to run depends on number of constraints.
+	int num_setup_velocity_constraints_jobs = max(1, min(((int)mConstraintManager.GetNumConstraints() + cSetupVelocityConstraintsBatchSize - 1) / cSetupVelocityConstraintsBatchSize, max_concurrency));
+
 	// Number of find collisions jobs to run depends on number of active bodies.
 	// Note that when we have more than 1 thread, we always spawn at least 2 find collisions jobs so that the first job can wait for build islands from constraints
 	// (which may activate additional bodies that need to be processed) while the second job can start processing collision work.
@@ -292,12 +295,14 @@ EPhysicsUpdateError PhysicsSystem::Update(float inDeltaTime, int inCollisionStep
 					}, num_step_listener_jobs > 0? num_step_listener_jobs : previous_step_dependency_count); // depends on: step listeners (or previous step if no step listeners)
 
 			// This job will setup velocity constraints for non-collision constraints
-			step.mSetupVelocityConstraints = inJobSystem->CreateJob("SetupVelocityConstraints", cColorSetupVelocityConstraints, [&context, &step]()
-				{
-					context.mPhysicsSystem->JobSetupVelocityConstraints(context.mStepDeltaTime, &step);
+			step.mSetupVelocityConstraints.resize(num_setup_velocity_constraints_jobs);
+			for (int i = 0; i < num_setup_velocity_constraints_jobs; ++i)
+				step.mSetupVelocityConstraints[i] = inJobSystem->CreateJob("SetupVelocityConstraints", cColorSetupVelocityConstraints, [&context, &step]()
+					{
+						context.mPhysicsSystem->JobSetupVelocityConstraints(context.mStepDeltaTime, &step);
 
-					JobHandle::sRemoveDependencies(step.mSolveVelocityConstraints);
-				}, num_determine_active_constraints_jobs + 1); // depends on: determine active constraints, finish building jobs
+						JobHandle::sRemoveDependencies(step.mSolveVelocityConstraints);
+					}, num_determine_active_constraints_jobs + 1); // depends on: determine active constraints, finish building jobs
 
 			// This job will build islands from constraints
 			step.mBuildIslandsFromConstraints = inJobSystem->CreateJob("BuildIslandsFromConstraints", cColorBuildIslandsFromConstraints, [&context, &step]()
@@ -315,10 +320,10 @@ EPhysicsUpdateError PhysicsSystem::Update(float inDeltaTime, int inCollisionStep
 					{
 						context.mPhysicsSystem->JobDetermineActiveConstraints(&step);
 
-						step.mSetupVelocityConstraints.RemoveDependency();
 						step.mBuildIslandsFromConstraints.RemoveDependency();
 
-						// Kick find collisions last as they will use up all CPU cores leaving no space for the previous 2 jobs
+						// Kick these jobs last as they will use up all CPU cores leaving no space for the previous job, we prefer setup velocity constraints to finish first so we kick it first
+						JobHandle::sRemoveDependencies(step.mSetupVelocityConstraints);
 						JobHandle::sRemoveDependencies(step.mFindCollisions);
 					}, num_step_listener_jobs > 0? num_step_listener_jobs : previous_step_dependency_count); // depends on: step listeners (or previous step if no step listeners)
 
@@ -425,10 +430,10 @@ EPhysicsUpdateError PhysicsSystem::Update(float inDeltaTime, int inCollisionStep
 						context.mPhysicsSystem->JobSolveVelocityConstraints(&context, &step);
 
 						step.mPreIntegrateVelocity.RemoveDependency();
-					}, 3); // depends on: finalize islands, setup velocity constraints, finish building jobs.
+					}, num_setup_velocity_constraints_jobs + 2); // depends on: finalize islands, setup velocity constraints, finish building jobs.
 
-			// Kick find collisions after setup velocity constraints because the former job will use up all CPU cores
-			step.mSetupVelocityConstraints.RemoveDependency();
+			// We prefer setup velocity constraints to finish first so we kick it first
+			JobHandle::sRemoveDependencies(step.mSetupVelocityConstraints);
 			JobHandle::sRemoveDependencies(step.mFindCollisions);
 
 			// Finalize islands is a dependency on find collisions so it can go last
@@ -527,7 +532,8 @@ EPhysicsUpdateError PhysicsSystem::Update(float inDeltaTime, int inCollisionStep
 				handles.push_back(h);
 			if (step.mUpdateBroadphaseFinalize.IsValid())
 				handles.push_back(step.mUpdateBroadphaseFinalize);
-			handles.push_back(step.mSetupVelocityConstraints);
+			for (const JobHandle &h : step.mSetupVelocityConstraints)
+				handles.push_back(h);
 			handles.push_back(step.mBuildIslandsFromConstraints);
 			handles.push_back(step.mFinalizeIslands);
 			handles.push_back(step.mBodySetIslandIndex);
@@ -643,7 +649,7 @@ void PhysicsSystem::JobDetermineActiveConstraints(PhysicsUpdateContext::Step *io
 	for (;;)
 	{
 		// Atomically fetch a batch of constraints
-		uint32 constraint_idx = ioStep->mConstraintReadIdx.fetch_add(cDetermineActiveConstraintsBatchSize);
+		uint32 constraint_idx = ioStep->mDetermineActiveConstraintReadIdx.fetch_add(cDetermineActiveConstraintsBatchSize);
 		if (constraint_idx >= num_constraints)
 			break;
 
@@ -708,7 +714,17 @@ void PhysicsSystem::JobSetupVelocityConstraints(float inDeltaTime, PhysicsUpdate
 	BodyAccess::Grant grant(BodyAccess::EAccess::None, BodyAccess::EAccess::Read);
 #endif
 
-	ConstraintManager::sSetupVelocityConstraints(ioStep->mContext->mActiveConstraints, ioStep->mNumActiveConstraints, inDeltaTime);
+	uint32 num_constraints = ioStep->mNumActiveConstraints;
+
+	for (;;)
+	{
+		// Atomically fetch a batch of constraints
+		uint32 constraint_idx = ioStep->mSetupVelocityConstraintsReadIdx.fetch_add(cSetupVelocityConstraintsBatchSize);
+		if (constraint_idx >= num_constraints)
+			break;
+
+		ConstraintManager::sSetupVelocityConstraints(ioStep->mContext->mActiveConstraints + constraint_idx, min<uint32>(cSetupVelocityConstraintsBatchSize, num_constraints - constraint_idx), inDeltaTime);
+	}
 }
 
 void PhysicsSystem::JobBuildIslandsFromConstraints(PhysicsUpdateContext *ioContext, PhysicsUpdateContext::Step *ioStep)

+ 3 - 0
Jolt/Physics/PhysicsSystem.h

@@ -235,6 +235,9 @@ private:
 	/// Number of constraints to process at once in JobDetermineActiveConstraints
 	static constexpr int		cDetermineActiveConstraintsBatchSize = 64;
 
+	/// Number of constraints to process at once in JobSetupVelocityConstraints, we want a low number of threads working on this so we take fairly large batches
+	static constexpr int		cSetupVelocityConstraintsBatchSize = 256;
+
 	/// Number of bodies to process at once in JobApplyGravity
 	static constexpr int		cApplyGravityBatchSize = 64;
 

+ 8 - 5
Jolt/Physics/PhysicsUpdateContext.h

@@ -62,21 +62,24 @@ public:
 
 		uint32				mNumActiveBodiesAtStepStart;							///< Number of bodies that were active at the start of the physics update step. Only these bodies will receive gravity (they are the first N in the active body list).
 
-		atomic<uint32>		mConstraintReadIdx { 0 };								///< Next constraint for determine active constraints
+		atomic<uint32>		mDetermineActiveConstraintReadIdx { 0 };				///< Next constraint for determine active constraints
 		uint8				mPadding1[JPH_CACHE_LINE_SIZE - sizeof(atomic<uint32>)];///< Padding to avoid sharing cache line with the next atomic
 
 		atomic<uint32>		mNumActiveConstraints { 0 };							///< Number of constraints in the mActiveConstraints array
 		uint8				mPadding2[JPH_CACHE_LINE_SIZE - sizeof(atomic<uint32>)];///< Padding to avoid sharing cache line with the next atomic
 
-		atomic<uint32>		mStepListenerReadIdx { 0 };								///< Next step listener to call
+		atomic<uint32>		mSetupVelocityConstraintsReadIdx { 0 };					///< Next constraint for setting up velocity constraints
 		uint8				mPadding3[JPH_CACHE_LINE_SIZE - sizeof(atomic<uint32>)];///< Padding to avoid sharing cache line with the next atomic
 
-		atomic<uint32>		mApplyGravityReadIdx { 0 };								///< Next body to apply gravity to
+		atomic<uint32>		mStepListenerReadIdx { 0 };								///< Next step listener to call
 		uint8				mPadding4[JPH_CACHE_LINE_SIZE - sizeof(atomic<uint32>)];///< Padding to avoid sharing cache line with the next atomic
 
-		atomic<uint32>		mActiveBodyReadIdx { 0 };								///< Index of fist active body that has not yet been processed by the broadphase
+		atomic<uint32>		mApplyGravityReadIdx { 0 };								///< Next body to apply gravity to
 		uint8				mPadding5[JPH_CACHE_LINE_SIZE - sizeof(atomic<uint32>)];///< Padding to avoid sharing cache line with the next atomic
 
+		atomic<uint32>		mActiveBodyReadIdx { 0 };								///< Index of fist active body that has not yet been processed by the broadphase
+		uint8				mPadding6[JPH_CACHE_LINE_SIZE - sizeof(atomic<uint32>)];///< Padding to avoid sharing cache line with the next atomic
+
 		BodyPairQueues		mBodyPairQueues;										///< Queues in which to put body pairs that need to be tested by the narrowphase
 
 		uint32				mMaxBodyPairsPerQueue;									///< Amount of body pairs that we can queue per queue
@@ -120,7 +123,7 @@ public:
 		JobHandleArray		mApplyGravity;											///< Update velocities of bodies with gravity
 		JobHandleArray		mFindCollisions;										///< Find all collisions between active bodies an the world
 		JobHandle			mUpdateBroadphaseFinalize;								///< Swap the newly built tree with the current tree
-		JobHandle			mSetupVelocityConstraints;								///< Calculate properties for all constraints in the constraint manager
+		JobHandleArray		mSetupVelocityConstraints;								///< Calculate properties for all constraints in the constraint manager
 		JobHandle			mBuildIslandsFromConstraints;							///< Go over all constraints and assign the bodies they're attached to to an island
 		JobHandle			mFinalizeIslands;										///< Finalize calculation simulation islands
 		JobHandle			mBodySetIslandIndex;									///< Set the current island index on each body (not used by the simulation, only for drawing purposes)

Some files were not shown because too many files changed in this diff