Skip to content

specialized_material_pipeline_cache grows forever, causing performance to greatly degrade over time #22065

@Satellile

Description

@Satellile

Bevy 0.17.3

Features: file_watcher, mp3, serialize, dynamic_linking

Windows 10

This "performance leak" began occurring for me in Bevy 0.16, but persists to Bevy 0.17.3.

The Bug

Over time, specialized_material_pipeline_cache.len() grows, increasing with no limit.

Each level in my game has a ton of mesh entities, which are all despawned upon unloading the level. Leaving and reloading one level a handful of times can drop FPS from ~230 to below 60, even though the entity count remains the same. Performance continues dropping the more I do it.

It seems very similar to #21526, but the issue wasn't fixed for me with 0.17.3. When I print specialized_material_pipeline_cache.len() in the specialize_material_meshes system, I can witness it growing in a similar manner, and the functions that actually start taking up frametime is similarly specialize_material_meshes and queue_material_meshes.

My game uses two cameras, but removing the second camera didn't stop this from occurring.

Workaround

I'm using a local clone of Bevy 0.17.3, where I made a change to bevy_pbr\src\material.rs around line ~870:

Before:

        entity_specialization_ticks.remove(&MainEntity::from(entity));
        for view in views {
            if let Some(cache) =
                specialized_material_pipeline_cache.get_mut(&view.retained_view_entity)
            {
                cache.remove(&MainEntity::from(entity));
            }
            if let Some(cache) = specialized_prepass_material_pipeline_cache
                .as_mut()
                .and_then(|c| c.get_mut(&view.retained_view_entity))
            {
                cache.remove(&MainEntity::from(entity));
            }
           [...]
        }

After:

        for (_, cache) in specialized_material_pipeline_cache.iter_mut() {
            cache.remove(&MainEntity::from(entity));
        }

        entity_specialization_ticks.remove(&MainEntity::from(entity));
        for view in views {
            if let Some(cache) = specialized_prepass_material_pipeline_cache
                .as_mut()
                .and_then(|c| c.get_mut(&view.retained_view_entity))
            {
                cache.remove(&MainEntity::from(entity));
            }
           [...]
        }

This seems to have fully fixed the issue.

It caused me no immediately apparent performance loss.

However I have no idea what cold specialization is, nor do I know what exactly I've done. Just kinda seemed like the growth was a problem with it not being cleaned out so I thought iterating through it all and deleting everything unconditionally seemed good and wouldnt have consequences, um, so that's why I didn't make this a PR.

In my game, most of the times I'll despawn a mesh/entity is when I'm despawning almost everything and loading a new map, and I'm not sure if I ever modify a mesh/material after creating it, so perhaps that's why this workaround isn't causing me immediate issues.

Metadata

Metadata

Assignees

No one assigned

    Labels

    A-RenderingDrawing game state to the screenC-BugAn unexpected or incorrect behaviorC-PerformanceA change motivated by improving speed, memory usage or compile timesD-ModestA "normal" level of difficulty; suitable for simple features or challenging fixesS-Needs-DesignThis issue requires design work to think about how it would best be accomplished

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions