So first what I did I run a local script like this:

for n = 1, 10000 do
local i = Instance.new("Part", workspace)
i.Anchored = true
i.CanCollide = false
end

What create for me 10000 parts. The FPS in the scene was around 40 and GPU load was 99.99%. So 10 000 x 6 sides x 2 (2 triangles for every face) makes 120 000 triangles to render.

Then I created and textured the default blender Monkey and subdivided it to 8700 triangles (near the limit of single mesh). I exported it into RB and created 20 copies. That makes 174 000 triangles to render. The result was stables 60 FPS and 30% GPU load.

Now I donâ€™t understand what makes the Parts so expensive to render in compare with a high vertex polygon? And in general this is Key problem to one project of mine, where I have a lots of proceduraly created parts

P.S. I know this should go into â€śPlatform Feedbackâ€ť, but Iâ€™m not allowed to Post there (no idea why) so this is my probably best option to get some answers and start and discussion.

I would bet that it is due to parts having rounded corners. If you want to do a more controlled test, export a part, re-upload it as a mesh, and use 10,000 of the basic mesh vs 10,000 of the basic part to figure out for yourself if parts or meshes have any performance difference.

Parts havenâ€™t had rounded corners for a long time now

But yeah, this result is very strange. In theory parts should render faster than meshes in Roblox. Perhaps itâ€™s due inefficiencies in having to render 10 000 Instances vs. 20

Please note that with with your code all parts will be created in the exact same location and with the same rotation which decreases your performance exponentially as you increase the part count for presumably because of all the Z-Fighting that occurs due to all the overlaping faces.

Just offsetting or rotating all your parts a tiny bit so the faces donâ€™t overlap anymore should already be enough to fix those performance issues:

for n = 1, 10000 do
local i = Instance.new("Part")
i.CFrame = CFrame.Angles(math.random()*math.pi*2,math.random()*math.pi*2,math.random()*math.pi*2)
i.Anchored = true
i.CanCollide = false
i.Parent = workspace
end

What if you avoid these part singularities altogether?

local parts = 10000
local base = math.floor(parts ^(1/3))
local n = 0
for x = 1, base do
for z = 1, base do
for y = 1, math.ceil(parts/base^2) do
if n == parts then
break
end
local p = Instance.new("Part")
p.Anchored = true
p.Size = Vector3.new(1,1,1)
p.Position = Vector3.new(x*1.1, y*1.1, z*1.1)
p.Parent = workspace
n = n + 1
end
end
end