1000 times slower API call between in a group and free geometry

[FYI can’t file this as a bug as its now closed]

This is a bug that I believe started in SketchUp 2015 (possibly SketchUp 2014) but its hard to track down so its only in the last few weeks I coincidently got a few bug reports for LightUp that showed the same problem.

Calls to Face#mesh for geometry not in a Group/Component can be 1000 times slower than the same geometry inside a Group.

Here’s some timings for a test model exhibiting the problem:

‘free’ geometry times for calling Face#mesh

ent(0) mesh call takes 30450
ent(1) mesh call takes 27527
ent(2) mesh call takes 27973
ent(3) mesh call takes 29131
ent(5) mesh call takes 26459
ent(6) mesh call takes 30136
ent(7) mesh call takes 27192
ent(8) mesh call takes 29465
ent(9) mesh call takes 32600
ent(11) mesh call takes 29010
ent(12) mesh call takes 25692
ent(13) mesh call takes 27610
ent(15) mesh call takes 24929
ent(16) mesh call takes 24488
ent(18) mesh call takes 26124
ent(23) mesh call takes 24681
ent(24) mesh call takes 24624
ent(27) mesh call takes 26383
ent(28) mesh call takes 25230
ent(29) mesh call takes 24635
ent(34) mesh call takes 26337
ent(35) mesh call takes 28577
ent(36) mesh call takes 28681
ent(39) mesh call takes 26617
ent(46) mesh call takes 28723

Same geometry placed inside a group times for calling Face#mesh

ent(0) mesh call takes 67
ent(3) mesh call takes 20
ent(5) mesh call takes 18
ent(6) mesh call takes 34
ent(7) mesh call takes 18
ent(8) mesh call takes 15
ent(9) mesh call takes 15
ent(14) mesh call takes 37
ent(15) mesh call takes 19
ent(17) mesh call takes 15
ent(20) mesh call takes 16
ent(22) mesh call takes 18
ent(24) mesh call takes 17
ent(25) mesh call takes 20
ent(26) mesh call takes 25
ent(27) mesh call takes 16
ent(28) mesh call takes 24
ent(43) mesh call takes 17
ent(45) mesh call takes 16
ent(49) mesh call takes 20
ent(53) mesh call takes 17
ent(58) mesh call takes 21
ent(60) mesh call takes 17
ent(63) mesh call takes 17
ent(66) mesh call takes 21

If I break into the code, it appears to be constantly throwing away all the normals and topology info every time Face#mesh is called when its not in a Group and re-calculating it. Resulting in horribly slow performance.

Adam

@AdamB, was that on PC or mac or both?

there are some definite bottlenecks on mac even when using groups…

john

Well, that makes total sense. And seems like it’s been that way forever. Think about it. Inside a group, the SketchUp engine need only worry about interaction with the group’s (or component’s) entities collection.

Outside, in the model at large, SketchUp would have to check for interaction with the entire model’s entities.

I’d think it is wise to do this inside a group, even temporarily, so as to reduce the work SketchUp has to do.


So, specifically to your test, … did the test model have any (and if so how much) model level entities ?

… and how is that compared to the group’s entities (count-wise) ?


ADD: Oh, and you just know that Thomas or Chris will ask for a test script and test model(s).

No, it doesn’t make sense.

If I have 10,000 entities in the top level model container or 10,000 entities in a Group container, its the same amount of work.

In the former it takes 1000 times longer. I can edit a top level model container just as easily as a Group container so what is cached for the group should be cached for the model container too. But apparently its not. :frowning:

And it is a recent-ish behaviour. SketchUp 8 & 2013 don’t appear to have this problem.

Adam

Hm… this might be related to a fix for another issue where face.mesh would not yield correct vertex normals. I believe a change was made to force a vertex normal recalculation whenever face.mesh was called.

That being said - I see no reason the same call on similar mesh to be different depending on it being in model.entities or in a group/component.

Can you share a model and snippet for reproduction?

haha! You know us too well! :smiley:

Sure, its a model stripped down to have just top-level entities. If you grep all the faces and run face.mesh on them and time it you’ll see the problem. Grouping everything and re-running and it goes away.

I will need to check with the customer if its OK to share. However, it appears to be a generic problem, not a model-specific one, so any model with a few thousand top-level faces should exhibit this. FYI This model had 14,000 entities.

Adam

I believe you, but you were not this specific in the initial post. (Thanks for the clarification.)

I’m guessing the client didn’t allow the model to be shared? Can you share a code snippet to reproduce?

I had a try myself, but I’m not seeing the same as what you describe. (Though I tested in SU2016.)

My test model has a mesh with 40K+ faces in the model, and identical mesh in a definition:

My code snippet:

module MeshPerformanceTests

  def self.run(iterations = 1)
    iterations.times {
      self.run_test
    }
  end

  def self.run_test
    puts "\n#{self}"
    model = Sketchup.active_model
    definition = model.definitions.first

    puts "\n> Testing model.entities:"
    result = self.time(model.entities)
    puts "> Time: #{result}s"

    puts "\n> Testing definition.entities:"
    result = self.time(definition.entities)
    puts "> Time: #{result}s"
  end

  def self.time(entities)
    start = Time.now

    num_triangles = 0
    entities.grep(Sketchup::Face) { |face|
      mesh = face.mesh
      num_triangles += mesh.count_polygons
    }
    puts "#{entities} contains #{num_triangles} triangles"

    Time.now - start
  end

end # module

My results:

MeshPerformanceTests.run(10)

MeshPerformanceTests

> Testing model.entities:
#<Sketchup::Entities:0x0000000b986790> contains 45076 triangles
> Time: 0.321s

> Testing definition.entities:
#<Sketchup::Entities:0x00000030c2c8f8> contains 45076 triangles
> Time: 0.332s

MeshPerformanceTests

> Testing model.entities:
#<Sketchup::Entities:0x0000000b986790> contains 45076 triangles
> Time: 0.267s

> Testing definition.entities:
#<Sketchup::Entities:0x00000030c2c8f8> contains 45076 triangles
> Time: 0.28s

MeshPerformanceTests

> Testing model.entities:
#<Sketchup::Entities:0x0000000b986790> contains 45076 triangles
> Time: 0.266s

> Testing definition.entities:
#<Sketchup::Entities:0x00000030c2c8f8> contains 45076 triangles
> Time: 0.277s

MeshPerformanceTests

> Testing model.entities:
#<Sketchup::Entities:0x0000000b986790> contains 45076 triangles
> Time: 0.266s

> Testing definition.entities:
#<Sketchup::Entities:0x00000030c2c8f8> contains 45076 triangles
> Time: 0.277s

MeshPerformanceTests

> Testing model.entities:
#<Sketchup::Entities:0x0000000b986790> contains 45076 triangles
> Time: 0.323s

> Testing definition.entities:
#<Sketchup::Entities:0x00000030c2c8f8> contains 45076 triangles
> Time: 0.276s

MeshPerformanceTests

> Testing model.entities:
#<Sketchup::Entities:0x0000000b986790> contains 45076 triangles
> Time: 0.267s

> Testing definition.entities:
#<Sketchup::Entities:0x00000030c2c8f8> contains 45076 triangles
> Time: 0.277s

MeshPerformanceTests

> Testing model.entities:
#<Sketchup::Entities:0x0000000b986790> contains 45076 triangles
> Time: 0.266s

> Testing definition.entities:
#<Sketchup::Entities:0x00000030c2c8f8> contains 45076 triangles
> Time: 0.276s

MeshPerformanceTests

> Testing model.entities:
#<Sketchup::Entities:0x0000000b986790> contains 45076 triangles
> Time: 0.267s

> Testing definition.entities:
#<Sketchup::Entities:0x00000030c2c8f8> contains 45076 triangles
> Time: 0.339s

MeshPerformanceTests

> Testing model.entities:
#<Sketchup::Entities:0x0000000b986790> contains 45076 triangles
> Time: 0.286s

> Testing definition.entities:
#<Sketchup::Entities:0x00000030c2c8f8> contains 45076 triangles
> Time: 0.28s

MeshPerformanceTests

> Testing model.entities:
#<Sketchup::Entities:0x0000000b986790> contains 45076 triangles
> Time: 0.274s

> Testing definition.entities:
#<Sketchup::Entities:0x00000030c2c8f8> contains 45076 triangles
> Time: 0.278s
1 Like

I modified your tests to actually get positions, uvs, normals, edges etc to be more realistic and it does increase time, but nothing like what I’m seeing.

If I break into my code, it is permanently in the face.mesh call with this stack frame (does this give any hint to whats going on?)
Explore Adam Billyard

And if its not in that stack frame, its this:

CEnumEdgeUseFace::…
CComponent::ClearVertexNormalData();
CVertexNormalCalculator::Update(CComponent *)

It all looks like something is triggering SketchUp to recalculate all normals - yet I’m not aware I’m modifying the model each time I process a Face. Would there be anything else that would cause this?

Adam

I modified to return all mesh data: mesh = face.mesh(7)

And I do see an increase by about 0.1s on average.

MeshPerformanceTests.run(10)

MeshPerformanceTests

> Testing model.entities:
#<Sketchup::Entities:0x0000000c1c6c70> contains 45076 triangles
> Time: 0.45s

> Testing definition.entities:
#<Sketchup::Entities:0x00000017ea0350> contains 45076 triangles
> Time: 0.452s

MeshPerformanceTests

> Testing model.entities:
#<Sketchup::Entities:0x0000000c1c6c70> contains 45076 triangles
> Time: 0.359s

> Testing definition.entities:
#<Sketchup::Entities:0x00000017ea0350> contains 45076 triangles
> Time: 0.363s

MeshPerformanceTests

> Testing model.entities:
#<Sketchup::Entities:0x0000000c1c6c70> contains 45076 triangles
> Time: 0.412s

> Testing definition.entities:
#<Sketchup::Entities:0x00000017ea0350> contains 45076 triangles
> Time: 0.369s

MeshPerformanceTests

> Testing model.entities:
#<Sketchup::Entities:0x0000000c1c6c70> contains 45076 triangles
> Time: 0.417s

> Testing definition.entities:
#<Sketchup::Entities:0x00000017ea0350> contains 45076 triangles
> Time: 0.357s

MeshPerformanceTests

> Testing model.entities:
#<Sketchup::Entities:0x0000000c1c6c70> contains 45076 triangles
> Time: 0.371s

> Testing definition.entities:
#<Sketchup::Entities:0x00000017ea0350> contains 45076 triangles
> Time: 0.37s

MeshPerformanceTests

> Testing model.entities:
#<Sketchup::Entities:0x0000000c1c6c70> contains 45076 triangles
> Time: 0.361s

> Testing definition.entities:
#<Sketchup::Entities:0x00000017ea0350> contains 45076 triangles
> Time: 0.377s

MeshPerformanceTests

> Testing model.entities:
#<Sketchup::Entities:0x0000000c1c6c70> contains 45076 triangles
> Time: 0.376s

> Testing definition.entities:
#<Sketchup::Entities:0x00000017ea0350> contains 45076 triangles
> Time: 0.443s

MeshPerformanceTests

> Testing model.entities:
#<Sketchup::Entities:0x0000000c1c6c70> contains 45076 triangles
> Time: 0.369s

> Testing definition.entities:
#<Sketchup::Entities:0x00000017ea0350> contains 45076 triangles
> Time: 0.367s

MeshPerformanceTests

> Testing model.entities:
#<Sketchup::Entities:0x0000000c1c6c70> contains 45076 triangles
> Time: 0.364s

> Testing definition.entities:
#<Sketchup::Entities:0x00000017ea0350> contains 45076 triangles
> Time: 0.369s

MeshPerformanceTests

> Testing model.entities:
#<Sketchup::Entities:0x0000000c1c6c70> contains 45076 triangles
> Time: 0.36s

> Testing definition.entities:
#<Sketchup::Entities:0x00000017ea0350> contains 45076 triangles
> Time: 0.441s

There was an issue where some times vertex normals would not be returned correctly. Basically they had been invalidated at some point and face.mesh was called before they’d rebuilt.

I just looked at the code for face.mesh and it trigger an update every time a mesh with vertex normals is requested.

That explains the overall increase we see in my snippet above. But it still doesn’t explain your original issue described. I think we need to have an actual model and some code to reproduce to see what is going on. I don’t see anything here that would warrant a difference between root of model and a definition.

OK, finally tracked this down.

Change your run_test method to being inside an Undo context as show below.

Run this again and its much the same result, uncomment the single line that does set_attribute, and its 1000 times slower.

Why would running inside an Undo block with UI updates disabled do this?

Adam

  def self.run_test
    puts "\n#{self}"
    model = Sketchup.active_model
    definition = model.definitions.first

	Sketchup.active_model.start_operation("MeshTest", true)
	#Sketchup.active_model.set_attribute(:meaning, :life, 42);	

    puts "\n> Testing model.entities:"
    result = self.time(model.entities)
    puts "> Time: #{result}s"

    puts "\n> Testing definition.entities:"
    result = self.time(definition.entities)
    puts "> Time: #{result}s"

	Sketchup.active_model.commit_operation
	
  end

I’m still not seeing the slow-down:

module MeshPerformanceTests

  def self.run(iterations = 1, modify_model = false)
    iterations.times {
      self.run_test(modify_model)
    }
  end

  def self.run_test(modify_model = false)
    puts "\n#{self}"
    model = Sketchup.active_model
    definition = model.definitions.first

    Sketchup.active_model.start_operation("MeshTest", true)
    Sketchup.active_model.set_attribute(:meaning, :life, 42) if modify_model

    puts "\n> Testing model.entities:"
    result = self.time(model.entities)
    puts "> Time: #{result}s"

    puts "\n> Testing definition.entities:"
    result = self.time(definition.entities)
    puts "> Time: #{result}s"

    Sketchup.active_model.commit_operation
  
  end

  def self.time(entities)
    start = Time.now

    num_triangles = 0
    entities.grep(Sketchup::Face) { |face|
      mesh = face.mesh
      num_triangles += mesh.count_polygons
    }
    puts "#{entities} contains #{num_triangles} triangles"

    Time.now - start
  end

end # module

Results of no set_attribute:

MeshPerformanceTests.run(10)

MeshPerformanceTests

> Testing model.entities:
#<Sketchup::Entities:0x00000011231750> contains 45076 triangles
> Time: 0.276015s

> Testing definition.entities:
#<Sketchup::Entities:0x00000020845470> contains 45076 triangles
> Time: 0.334019s

MeshPerformanceTests

> Testing model.entities:
#<Sketchup::Entities:0x00000011231750> contains 45076 triangles
> Time: 0.283016s

> Testing definition.entities:
#<Sketchup::Entities:0x00000020845470> contains 45076 triangles
> Time: 0.274016s

MeshPerformanceTests

> Testing model.entities:
#<Sketchup::Entities:0x00000011231750> contains 45076 triangles
> Time: 0.262015s

> Testing definition.entities:
#<Sketchup::Entities:0x00000020845470> contains 45076 triangles
> Time: 0.334019s

MeshPerformanceTests

> Testing model.entities:
#<Sketchup::Entities:0x00000011231750> contains 45076 triangles
> Time: 0.255015s

> Testing definition.entities:
#<Sketchup::Entities:0x00000020845470> contains 45076 triangles
> Time: 0.270016s

MeshPerformanceTests

> Testing model.entities:
#<Sketchup::Entities:0x00000011231750> contains 45076 triangles
> Time: 0.258015s

> Testing definition.entities:
#<Sketchup::Entities:0x00000020845470> contains 45076 triangles
> Time: 0.272016s

MeshPerformanceTests

> Testing model.entities:
#<Sketchup::Entities:0x00000011231750> contains 45076 triangles
> Time: 0.261015s

> Testing definition.entities:
#<Sketchup::Entities:0x00000020845470> contains 45076 triangles
> Time: 0.328019s

MeshPerformanceTests

> Testing model.entities:
#<Sketchup::Entities:0x00000011231750> contains 45076 triangles
> Time: 0.255015s

> Testing definition.entities:
#<Sketchup::Entities:0x00000020845470> contains 45076 triangles
> Time: 0.269016s

MeshPerformanceTests

> Testing model.entities:
#<Sketchup::Entities:0x00000011231750> contains 45076 triangles
> Time: 0.256015s

> Testing definition.entities:
#<Sketchup::Entities:0x00000020845470> contains 45076 triangles
> Time: 0.268015s

MeshPerformanceTests

> Testing model.entities:
#<Sketchup::Entities:0x00000011231750> contains 45076 triangles
> Time: 0.259015s

> Testing definition.entities:
#<Sketchup::Entities:0x00000020845470> contains 45076 triangles
> Time: 0.326018s

MeshPerformanceTests

> Testing model.entities:
#<Sketchup::Entities:0x00000011231750> contains 45076 triangles
> Time: 0.256015s

> Testing definition.entities:
#<Sketchup::Entities:0x00000020845470> contains 45076 triangles
> Time: 0.269015s

Results of with set_attribute:

MeshPerformanceTests.run(10, true)

MeshPerformanceTests

> Testing model.entities:
#<Sketchup::Entities:0x00000011231750> contains 45076 triangles
> Time: 0.265015s

> Testing definition.entities:
#<Sketchup::Entities:0x00000020845470> contains 45076 triangles
> Time: 0.270015s

MeshPerformanceTests

> Testing model.entities:
#<Sketchup::Entities:0x00000011231750> contains 45076 triangles
> Time: 0.259014s

> Testing definition.entities:
#<Sketchup::Entities:0x00000020845470> contains 45076 triangles
> Time: 0.324018s

MeshPerformanceTests

> Testing model.entities:
#<Sketchup::Entities:0x00000011231750> contains 45076 triangles
> Time: 0.259015s

> Testing definition.entities:
#<Sketchup::Entities:0x00000020845470> contains 45076 triangles
> Time: 0.272015s

MeshPerformanceTests

> Testing model.entities:
#<Sketchup::Entities:0x00000011231750> contains 45076 triangles
> Time: 0.259015s

> Testing definition.entities:
#<Sketchup::Entities:0x00000020845470> contains 45076 triangles
> Time: 0.271015s

MeshPerformanceTests

> Testing model.entities:
#<Sketchup::Entities:0x00000011231750> contains 45076 triangles
> Time: 0.277016s

> Testing definition.entities:
#<Sketchup::Entities:0x00000020845470> contains 45076 triangles
> Time: 0.305017s

MeshPerformanceTests

> Testing model.entities:
#<Sketchup::Entities:0x00000011231750> contains 45076 triangles
> Time: 0.257014s

> Testing definition.entities:
#<Sketchup::Entities:0x00000020845470> contains 45076 triangles
> Time: 0.271015s

MeshPerformanceTests

> Testing model.entities:
#<Sketchup::Entities:0x00000011231750> contains 45076 triangles
> Time: 0.260014s

> Testing definition.entities:
#<Sketchup::Entities:0x00000020845470> contains 45076 triangles
> Time: 0.270015s

MeshPerformanceTests

> Testing model.entities:
#<Sketchup::Entities:0x00000011231750> contains 45076 triangles
> Time: 0.282016s

> Testing definition.entities:
#<Sketchup::Entities:0x00000020845470> contains 45076 triangles
> Time: 0.301017s

MeshPerformanceTests

> Testing model.entities:
#<Sketchup::Entities:0x00000011231750> contains 45076 triangles
> Time: 0.258015s

> Testing definition.entities:
#<Sketchup::Entities:0x00000020845470> contains 45076 triangles
> Time: 0.270015s

MeshPerformanceTests

> Testing model.entities:
#<Sketchup::Entities:0x00000011231750> contains 45076 triangles
> Time: 0.258015s

> Testing definition.entities:
#<Sketchup::Entities:0x00000020845470> contains 45076 triangles
> Time: 0.270015s

What model where you testing with? The one I posted or a different one?

This one: https://light-up.co.uk/resources/BUGTEST_slow_ent.mesh_purged.skp.zip

What are the flags when no argument is passed ?

First of all, a Geom::PolygonMesh object is a virtual object. (It is not a object that exists within a model, nor saved within the model.)

So, creating them, or modifying them, does not effect the model entities, nor fire observers attached to entities collections (because they are not members of any entities collection.)

Likewise, creating or modifying them, should not effect the undo stack as no changes to the model occur. So it serves no purpose, to wrap only their manipulation within a undo operation. (If their manipulation is mixed with other model changes, then an undo block is understandable.)

But to compare one undo block with no “undoable” items, against a block in which a token undoable statement is inserted, is not a valid test, IMO. Of course the one with the “undoable” action should take longer, especially if there is any string conversion going on.

Add to that, the test attribute is being attached to the model object for both tests loops. Why not the model.entities collection, and then the group.entities collection ?


Is it possible some EntitesObserver or EntityObserver is firing that seems to slow things down ?

Dan,

Before getting to the how & why, be good to confirm you can repeat this.

Can you download the model I used, and run the modified test I posted. Do you see the same result?

Adam

I ran the test with your model.

I see no difference between having the token attribute write in the block and not.
The average difference between working upon the model.entities and the definition.entities, is 2 hundredths of a second. Often less, and occasionally up to 4 hundredths of a second.

Also it seems random which of the two is the one that takes longer. In half the tests, processing the model.entities takes the slower of the two, … the other half of the tests, the definition.entities processing is the slower of the two.

But then I do not have LightUp installed, nor any heavy extension except for Dynamic Components.


Have you run your tests with no plugins installed. ie, renaming the “plugins” folder ?

although slower by 75% than TT’s I see NO discernible difference between the two on my iMac…

john