Notifications
Clear all

[Closed] MXS 2017 'For Loop' performance

Well, I have a bit of an embarrassing admission to make… the slowdown in my script was down to some nasty old code I hadn’t optimised. After fixing this, the iteration slowdown is no longer an issue.
(I was using try()catch() to capture failing execute() commands… which is slower in 2017 than 2016… but not an issue as you should never really have code like this in the first place!)

2 Replies
(@denist)
Joined: 11 months ago

Posts: 0

do you notice that after all your code runs better in 2016 than in 2017?

(@reform)
Joined: 11 months ago

Posts: 0

Currently, no, but that’s because there is other code that also needs cleaned up.

boo!

probably it’s because the way how bitarrayvalue is created by the system.

1 Reply
(@polytools3d)
Joined: 11 months ago

Posts: 0

Nah, I don’t think so.

Hi guys, I posted an explanation for the loop performance behavior in the autodesk forums here: http://forums.autodesk.com/t5/3ds-max-programming/slow-for-loop-in-3dsmax-2017/m-p/6400380 .
Let us know over there if you have performance issues that significantly impact the code.

1 Reply
(@polytools3d)
Joined: 11 months ago

Posts: 0

Hello Kevin,

Thank you for your explanation. It is much appreciated to have the word of a developer in order to understand what is going on under the hood.

Although you stated that the slowdown is noticeable because we were measuring an empty loop, and that this additional overhead should become insignificant when doing some actual work inside the loop, the following testing code shows that it might not always the case.

Perhaps there are some new coding rules we should follow in order to take full advantage of the new implementation that we are not aware of?

On the other hand, it is hard to define the meaning of “significantly impact”. The 37% difference in this test won’t change my life at all. The purpose of the thread was not to complain, but to find out if this was caused by a bug or a feature and to be aware of it when debugging.

Thanks!

(
	gc()
	delete objects

	with undo off
	(
		obj = (geosphere radius:50 segs:128 basetype:2)
		addmodifier obj (turbosmooth())
		converttomesh obj

		st = timestamp(); sh = heapfree
			
		for j = 1 to obj.numverts do
		(
			p = getvert obj j
			p += random -[1,1,1] [1,1,1]
			setvert obj j p
		)
		
		GetFaceCenter = meshop.getfacecenter
		for j = 1 to obj.numfaces do
		(
			center = GetFaceCenter obj j
			normal = getfacenormal obj j
			face = getface obj j
			setvert obj face[random 1 3] (center + (normal*random -1 1))
		)
		
		format "time:% ram:% verts:%
" (timestamp()-st) (sh-heapfree) obj.numverts
	)
)

-- MAX 2016
-- time:10223 ram:184L verts:983042
-- time:10220 ram:184L verts:983042
-- time:10261 ram:184L verts:983042

-- MAX 2017	- 37% slower
-- time:14120 ram:196L verts:983042
-- time:14126 ram:196L verts:983042
-- time:14004 ram:196L verts:983042

Autodesk responded with a detailed response : http://forums.autodesk.com/t5/3ds-max-programming/slow-for-loop-in-3dsmax-2017/m-p/6403184#M14389

I also saw similar results, but given that the loop is not doing anything, it’s hard to say it is significantly slower. For example, on my machine the 1 million loop takes .489 second on 2017, and .260 second on 2016, so neither one is taking a significant amount of time for the looping itself.

But to see if there was a reason, I discussed this with our engineering team, and turns out this is intentional and was evaluated to be a benign performance issue, but added stability and improved error handling. The difference in this case, is due to new overhead per loop iteration that was added in 2017. In the internal implementation, what used to be a pair of inlined push/pop methods is now handled by an object being created and destroyed. Yes, there is a small amount of overhead due to this, but based on real tests the hit is insignificant compared to any operations that might be run inside the loop.

The reported results seem “big” because almost nothing else is going on, but as soon as you start doing actual work within the loop, this additional overhead should become insignificant.

For 3ds Max 2017, almost all the internal implementation of mxs push/pop code was replaced with class instances that do these operations in their ctor/dtors. This ensures that all code is doing things correctly, and that the code is exception safe. This leads us to increased stability and error handling.

Please let us know if you feel there are other performance problems not related to the actual looping code. Again, when testing these areas, we did not see any significant impact for real-world operations (ie. for looping code, the operations being performed are carried out in basically the same amount of time when adding the slight overhead of the 2017 changes).

i don’t have max 2017, but can anyone check the difference for ‘nested loops’, please?

(
    t = timestamp()
    for n=1 to 50 do for k=1 to 50 do for j=1 to 50 do for i=1 to 50 do ()
    timestamp() - t
)
 
2 Replies
(@swordslayer)
Joined: 11 months ago

Posts: 0

So fo a summary:

-- used to leak in recent versions and now doesn't
fn fun1 = (local x = x_axis * 10)

-- used to leak in recent versions and now doesn't (variable re-assignment in the same scope)
fn fun2 = (local x = x_axis; x *= 10)

-- used to leak in recent versions and now doesn't
fn multByVal x = x * 10
fn fun4 = (local x = x_axis; x = multByVal x)

-- still leaks (variable re-assignment in a different scope)
fn multByRef &x = x *= 10
fn fun3 = (local x = x_axis; multByRef &x) -- still leaks

-- never leaked (same scope without re-assigning)
fn fun5 = (local x = x_axis; do x *= 10 while 0 > 1)

-- never leaked (different scope without re-assigning)
fn fun6 = (local x = x_axis; for i = 1 to 1 do x * 10)

-- still leaks (variable re-assignment in a different scope)
fn fun7 = (local x = x_axis; for i = 1 to 1 do x *= 10)

There’s not much of a real-life usecase to go with those functions, as you may note, they’re only good to show a difference in behavior. Also, with this particular benchmark I get the same results in max 9 and max 2017 – in terms of memory use, not speed, obviously (just for fun, in gmax, the only one leaking is fun7).

A real-life would be for example… summing some vectors:

count = 50000
gc()
(
	local st = timeStamp()
	local sh = heapFree
	local sum = [0, 0, 0]

	for j = 1 to count do sum += x_axis

	format "for:	time:%	heap:%
" (timeStamp() - st) (sh - heapFree)
)
gc()
(
	local st = timeStamp()
	local sh = heapFree
	local sum = [0, 0, 0]
	local j = 0

	while j < count do (sum += x_axis; j += 1)

	format "while:	time:%	heap:%
" (timeStamp() - st) (sh - heapFree)
)

And as you may expect, this used to leak and it still leaks all the same.

953 ms in gmax, 1943 ms in 2016, 2933 ms in 2017

(@polytools3d)
Joined: 11 months ago

Posts: 0

MAX 2016
1270

MAX 2017
2103

  • This soup was better yesterday than today, wasn’t it?
  • Correct. But it was cooked wrong. Now it’s cooked just right, Sir.

I’ve benchmarked some of my scripted geometry/modifier plugins and in general, the slowdown was by a factor of 1.2 to 1.5 (from 2016->2017).

As an example, the numbers with http://www.scriptspot.com/files/quadsphere_max42.ms are :

(
    t = timeStamp()
    quadSphere sides:1000 isHidden:true
    timestamp() - t
)

gmax: 4648 ms
max 2016: 7872 ms
max 2017: 10086 ms

For a modifier, let’s take a really simple one such as Vladislav Bodyul’s Boxify:

(
	t = timeStamp()
	local sph = Sphere segments:1000 isHidden:true
	addModifier sph (BoxifyMod())
	classOf sph
	timeStamp() - t
)

gmax: 440 ms (372 with unrolled for loop)
2016: 450 ms (375 with unrolled for loop)
2017: 580 ms (456 with unrolled for loop)

now we can see that the difference is more than 1.5-1.75
.
in my geometry plugins i usually give to user three types of update to choose – automatic, on mouse up, and manual. the first one is most productive of course. it means now that my users might stop using it at for example 100,000 points mesh, what my plugins was designed for.
it significantly impacts the pipeline and the way of using my tools.

(i know about leaking, and it’s very good that we have it less now. but if i could find a workaround to minimize it, now i can’t do anything to help myself with performance)

just curious, does python can completely replace mxs in 2017, and work better and faster?

1 Reply
(@polytools3d)
Joined: 11 months ago

Posts: 0

We see this since day 1, but you are too skeptical.

Leaking you say? 1.5-1.75 drop?
I am working on another test. I hope I am wrong because the numbers I am seeing are nothing good.

i was not just skeptical. i just haven’t max 2017 to prove. you know i easier believe that next max version worse than better

please don’t frighten us. my heart is already in my mouth …

1 Reply
(@polytools3d)
Joined: 11 months ago

Posts: 0

Well, you may want to start digesting it before the next post.

Page 3 / 9