Curious to see if anyone here can think of more ways to optimize this code

This code generates frame data for animating parts (3d vfx), its a bit of a mess but im not here for looks, this function gets called 10-100s of times per frame depending on the situation, so its important that it is as fast as possible regardless of readability.
I have spent a long time looking at this trying little things to make it faster, with very little success. It could simply be that it is as fast as it can be, however I would like to see some others take a look at it and maybe come up with a few ideas I haven’t thought of.

Currently it runs quite well on its own
image
but once you add many of these up it gets slow
image

Here’s the actual code

-- these are at the top of the script
local v3 = Vector3.new
local cf = CFrame.new
local angles = CFrame.Angles
local rad = math.rad
local clamp = math.clamp
local floor = math.floor
local c3 = Color3.new

local FrameSkip = 1 -- for adjustable frame rate of effects, used for quality settings

-- actual frame generation
Effect = function(Type: string,StartData,...)
	debug.profilebegin("Generate Frames")
	local newEff: BasePart = GetFromPool(Type) or Effects:FindFirstChild(Type):Clone()
	if newEff then
		local daCF: CFrame = StartData.CFrame
		newEff.CFrame = StartData.CFrame
		newEff.Transparency = StartData.Transparency1
		newEff.Size = StartData.Size1
		newEff.Color = StartData.Color
		newEff.Material =  StartData.Material or Neon
		newEff.Parent = EffFolder
		
		local Frames = {
			{
				StartData.CFrame,
				StartData.Size1,
				StartData.Transparency1,
				StartData.Color
			},
		}

		local TotalLife: number = 1
		for i,Data in pairs({StartData,...}) do
			debug.profilebegin("Frame Data")
			local daCF = newEff.CFrame
			local tr = Data.Transparency1 or 0
			local siz = Data.Size1
			local col = Data.Color

			local Life: number = math.ceil((Data.Life or 16)/FrameSkip)
			local TransparencyDif = ((Data.Transparency2 or 1) - tr)/Life
			local SizeDif = ((Data.Size2 or siz) - siz)/Life
			local Velocity = Data.Velocity or {0,0,0}
			local Rotation = Data.Rotation or {0,0,0}
			Velocity = cf(Velocity[1]/Life,Velocity[2]/Life,Velocity[3]/Life)
			Rotation = CFA(Rotation[1]/Life,Rotation[2]/Life,Rotation[3]/Life)
			local MoveTo = Data.MoveTo
			if MoveTo then
				local StartPos = newEff.CFrame.Position
				local MovePos = MoveTo.Position
				MoveTo = v3((MovePos.X-StartPos.X),(MovePos.Y-StartPos.Y),(MovePos.Z-StartPos.Z))/Life
			end
			local FirstFrame = true
			local colDif
			if Data.Color2 then
				colDif = {(col.R-Data.Color2.R)/Life,(col.G-Data.Color2.G)/Life,(col.B-Data.Color2.B)/Life}
			end
			debug.profileend()

			debug.profilebegin("Generate Frame "..i)
			for i = TotalLife,(TotalLife-1)+Life do
				local FrameData = {}

				if FirstFrame then
					FrameData[1] = daCF
					FrameData[2] = siz
					FrameData[3] = tr
					FrameData[4] = col
					FirstFrame = false
				else
					local PrevFrame = Frames[i]
					FrameData[1] = PrevFrame[1]*Velocity*Rotation
					FrameData[2] = PrevFrame[2]+SizeDif
					FrameData[3] = PrevFrame[3]+TransparencyDif
					if colDif then
						local PrevCol = PrevFrame[4]
						FrameData[4] = c3(clamp(PrevCol.R-colDif[1],0,1),clamp(PrevCol.G-colDif[2],0,1),clamp(PrevCol.B-colDif[3],0,1))
					else
						FrameData[4] = col
					end
				end
				
				if MoveTo then
					FrameData[1] = cf(MoveTo)*FrameData[1]
				end
				
				Frames[i+1] = FrameData
			end
			debug.profileend()
			TotalLife+=Life
		end

		table.insert(EpicEffect2.ActiveEffects,{newEff,TotalLife,Frames,1})
	else
		warn("EpicEffect2: Effect '"..Type.."' Not Found!")
	end
	debug.profileend()
end

I don’t expect much can be done to make it faster, but if anyone wants to suggest optimizations I open to suggestions.

If you’re just calculating frames you may want to check out using Actors. This use case seem similar to their example of procedurally generating terrain.

I tried using this many times, the cost to send data (bindable events/functions) from the scripts under actors back to the main script makes the implementation of this less efficient not to mention how slow it is to desynchronize() and synchronize(). I might be missing something when it comes to Parallel Luau but as far as I can tell its slower than not using it for this use case, as the frame generation also modifies a few properties at the start.

batching it up across maybe like 4 actors might yield some good results but im still thinking of how I would compile the data together efficiently.

Edit: After testing this,
image
The overhead added from using Parallel Luau is greater than the gain from splitting the task into different threads, this is the biggest issue with Parallel Luau and is why it hasn’t helped me with anything so far. It’s far too limited and having to switch between parallel and serial to make this work ends up making it alot slower. If I could modify the parts in these threads that would be alot more helpful. Since they are only for visual effect, only one script needs access to them anyway. However, that’s a conversation for another day.

not sure enough with whats going on with the code to give anything solid, but ill throw out some ideas

  • may be able to use ipairs in place of pairs at for i,Data in pairs({StartData,...}) do
  • you seem to be calling cf(MoveTo) in an iterator at the bottom despite MoveTo seemingly not changing (block below). can probably move the CFrame creation outside the iteration block
-- calculate the `cf(MoveTo)` in this part outside of the block
for i = TotalLife,(TotalLife-1)+Life do
	-- blah blah
	if MoveTo then
		FrameData[1] = cf(MoveTo)*FrameData[1]
	end
end
  • maybe initializing values would be faster than setting them? not sure on this tbh
-- instead of:
local FrameData = {}
if FirstFrame then
	FrameData[1] = daCF
	FrameData[2] = siz
	FrameData[3] = tr
	FrameData[4] = col
	FirstFrame = false
end
-- do:
local FrameData = nil
if FirstFrame then
	FrameData = {daCF, siz, tr, col}
	FirstFrame = false
end
  • textbook microoptimization but it may faster to multiply than to divide by Life

id imagine the most considerable performance costs come from iterating over & asserting all of the Data, and/or all the CFrame instantiations

oh yeah cf(MoveTo) can totally be moved out of the loop, that’s a good catch. as for the table changes, as far as I could see, it didn’t change anything performance wise.