Cannot find where the lag is coming

I was making some barrage effects in jojo game, and thought my effect causes lag, so I tried disabling effect and lag was gone. so I tried to find which line is laggy so I added tick() thing to check performance, and it just printed 0.001 ~ 0.0015, which will just take 0.03 second if I make 30 punch effects. what causes lag?

local function barrage(param, larm, rarm)
							if lowQuality == false then
								coroutine.wrap(function()
									local startTick = tick()
									local newArm = nil
									if param == 0 then
										newArm = larm:Clone()
									else
										newArm = rarm:Clone()
									end
									newArm.CFrame = CFrame.new(stand.sTorso.Position + stand.sTorso.CFrame.UpVector * 0.5) * (humanoid.RootPart.CFrame * CFrame.Angles(math.rad(90), 0, 0) - humanoid.RootPart.Position)
									if param == 0 then --left punch
										newArm.CFrame = newArm.CFrame + stand.sHumanoidRootPart.CFrame.RightVector * math.random(3, 20) / -10
									else --right punch
										newArm.CFrame = newArm.CFrame + stand.sHumanoidRootPart.CFrame.RightVector * math.random(3, 20) / 10
									end
									local atcm0 = Instance.new("Attachment")
									atcm0.Position = Vector3.new(0, 0.9, 0)
									atcm0.Parent = newArm
									local atcm1 = Instance.new("Attachment")
									atcm1.Position = Vector3.new(0, 0.6, 0)
									atcm1.Parent = newArm
									local newTrail = game:GetService("ServerStorage").objects.barrageTrail:Clone()
									newTrail.Attachment0 = atcm0
									newTrail.Attachment1 = atcm1
									newTrail.Parent = newArm

									local fragCount = 10
									local currentAngle = math.random(10, 100)
									local yAngle = math.random(-70, 70)
									if param == 0 then
										currentAngle *= -1
									end
									local anglePer = currentAngle * 1.5 / fragCount
									local yAnglePer = yAngle * 1.8 / fragCount
									newArm.CFrame = newArm.CFrame * CFrame.Angles(math.rad(yAngle), 0, math.rad(currentAngle))

									local armJoint = Instance.new("Motor6D")
									armJoint.Name = "armJoint"
									armJoint.C0 = newArm.CFrame:inverse()
									armJoint.C1 = stand.sHumanoidRootPart.CFrame:inverse()
									armJoint.Part0 = newArm
									armJoint.Part1 = stand.sHumanoidRootPart
									armJoint.Parent = newArm

									newArm.Anchored = true
									newArm.Parent = stand

									game:GetService("TweenService"):Create(newArm, TweenInfo.new(fragCount * foundSkill.spd.Value * 2, Enum.EasingStyle.Linear), {Transparency = 1}):Play()
									for _, armPart in pairs(newArm:GetDescendants()) do
										if armPart:IsA("BasePart") or armPart:IsA("Texture") or armPart:IsA("Decal") then
											game:GetService("TweenService"):Create(armPart, TweenInfo.new(fragCount * foundSkill.spd.Value * 2, Enum.EasingStyle.Linear), {Transparency = 1}):Play()
										end
									end
									local moveSpeed = foundSkill.spd.Value
									local waitTicks = 0
									for count = 1, fragCount do
										local startTick = tick()
										local moveArm = newArm.CFrame * CFrame.Angles(math.rad(-yAnglePer), 0, math.rad(-anglePer))
										moveArm = moveArm + moveArm.UpVector * -0.5
										game:GetService("TweenService"):Create(armJoint, TweenInfo.new(moveSpeed, Enum.EasingStyle.Linear), {C0 = moveArm:inverse() * armJoint.Part1.CFrame * armJoint.C1}):Play()
										
										local waitStart = tick()
										wait(foundSkill.spd.Value)
										waitTicks += tick() - waitStart --remove wait time
									end
									newArm:Destroy()
									print(tick() - startTick - waitTicks) --yes performance checker
								end)()
							end
						end

How often is this function called? Is it possible it’s creating and executing too many isolated threads at once? I’ve noticed the coroutine.wrap() call.

Effect function called 37 times with 0.05 second wait, is it enough to lower performance?

37 functions running synchronously is quite a lot, is it necessary? Do you need to use coroutine.wrap()? Can you not wait for one execution cycle of the function to finish before starting another?

Currently, yes. since multiple effects should exists to perform original significance.

this is main line that calls function, any other way to reduce coroutine?

for count = 1, foundSkill.maxhit.Value do
    barrage(count % 2, larm, rarm)
    wait(foundSkill.tph.Value)
    if foundSkill.cancelled.Value == true then
        break
    end
end