Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Fix: discussions #248 memory leak #252

Merged
merged 1 commit into from
Feb 17, 2025

Conversation

KaJIbI4
Copy link

@KaJIbI4 KaJIbI4 commented Feb 17, 2025

Description

Added a cache for dynamic libraries to eliminate the constant loading of new ones into the AppDomain, fix memory leak #248 and increase the speed of operation in entity framework compatibility mode.

When the EntityFrameworkCompatibilityLayer = true and filters are used, new assemblies are created on each request and loaded into the current AppDomain. There is no unloading of these assemblies from the domain, and the unmanaged memory grows over time. When creating a dynamic assembly, you can use AssemblyBuilderAccess.RunAndCollect and then .net sort of cleans up the memory, but there are still overhead costs for constant creation and loading into the domain. Therefore, it was decided to add a cache. In addition, the cache would speed up (if there was a flag) the old version of the work.

QueryBuilderBuildBenchmark:

  1. EntityFrameworkCompatibilityLayer = false, part of the code is not used
| Method                   | Mean           | Error         | StdDev        | Ratio    | RatioSD | Gen0    | Gen1   | Allocated | Alloc Ratio |
|------------------------- |---------------:|--------------:|--------------:|---------:|--------:|--------:|-------:|----------:|------------:|
| BuildFilteringExpression |       429.8 ns |     8.05 ns   |      7.53 ns  |     1.00 |    0.00 | 0.0114  |      - |      48 B |        1.00 |
| BuildCompiled            |       431.6 ns |     7.97 ns   |      7.06 ns  |     1.00 |    0.03 | 0.0114  |      - |      48 B |        1.00 |
| BuildWithPagingCompiled  |       784.1 ns |    11.70 ns   |      9.13 ns  |     1.82 |    0.03 | 0.1297  |      - |     544 B |       11.33 |
| Build                    |   401,934.9 ns | 7,997.12 ns   | 16,515.43 ns  |   928.28 |   45.09 | 4.3945  | 3.9063 |   19207 B |      400.15 |
| BuildWithPaging          |   753,641.3 ns | 7,054.67 ns   |  5,890.97 ns  | 1,750.90 |   31.67 | 6.8359  | 5.8594 |   31301 B |      652.10 |
  1. EntityFrameworkCompatibilityLayer = true, no cache
| Method                   | Mean           | Error         | StdDev        | Ratio    | RatioSD | Gen0    | Gen1   | Allocated | Alloc Ratio |
|------------------------- |---------------:|--------------:|--------------:|---------:|--------:|--------:|-------:|----------:|------------:|
| BuildFilteringExpression |       503.5 ns |       8.73 ns |      10.72 ns |     1.00 |    0.00 |  0.0114 |      - |      48 B |        1.00 |
| BuildCompiled            |       559.8 ns |      11.12 ns |       9.86 ns |     1.11 |    0.03 |  0.0114 |      - |      48 B |        1.00 |
| BuildWithPagingCompiled  |       880.3 ns |      17.32 ns |      24.84 ns |     1.75 |    0.06 |  0.1297 |      - |     544 B |       11.33 |
| Build                    | 2,122,313.1 ns | 186,513.94 ns | 549,940.56 ns | 2,762.89 |  379.36 |  7.8125 | 5.8594 |   35576 B |      741.17 |
| BuildWithPaging          | 3,060,274.8 ns | 224,933.92 ns | 663,222.75 ns | 4,331.13 |  681.37 | 11.7188 | 9.7656 |   48996 B |    1,020.75 |
  1. EntityFrameworkCompatibilityLayer = true, with cache
| Method                   | Mean           | Error         | StdDev        | Ratio    | RatioSD | Gen0    | Gen1   | Allocated | Alloc Ratio |
|------------------------- |---------------:|--------------:|--------------:|---------:|--------:|--------:|-------:|----------:|------------:|
| BuildFilteringExpression |       504.1 ns |      4.14 ns  |      3.45 ns  |     1.00 |    0.00 | 0.0114  |      - |      48 B |        1.00 |
| BuildCompiled            |       530.7 ns |      6.06 ns  |      5.06 ns  |     1.05 |    0.01 | 0.0114  |      - |      48 B |        1.00 |
| BuildWithPagingCompiled  |       901.5 ns |     18.02 ns  |     28.05 ns  |     1.81 |    0.07 | 0.1297  |      - |     544 B |       11.33 |
| Build                    |   553,918.3 ns |  8,665.93 ns  |  7,236.44 ns  | 1,098.95 |   15.22 | 4.8828  | 3.9063 |   20551 B |      428.15 |
| BuildWithPaging          | 1,099,447.2 ns | 20,052.01 ns  | 17,775.58 ns  | 2,182.02 |   41.31 | 7.8125  | 5.8594 |   33815 B |      704.48 |

Type of change

  • Bug fix (non-breaking change which fixes an issue)
  • New feature (non-breaking change which adds functionality)
  • Breaking change (fix or feature that would cause existing functionality to not work as expected)

Checklist

  • I have performed a self-review of my code
  • I have added tests that prove my fix is effective or that my feature works
  • I have made corresponding changes to the documentation
  • I have commented my code, particularly in hard-to-understand areas
  • New and existing unit tests pass locally with my changes

- added a cache for dynamic libraries to eliminate the constant loading of new ones into the AppDomain (memory leak)
- add test for this case
Copy link

what-the-diff bot commented Feb 17, 2025

PR Summary

  • Improved Performance with Cache Mechanism
    Added a caching system to store compiled type information, significantly enhancing the performance of our software.

  • Efficient Object Creation
    Updated how new objects are created by checking a cache for existing information. This prevents unnecessary compilations, making the operation more efficient.

  • Code Refactor
    Cleaned up some elements of the project by adding a common utility to the imports.

  • New Helper Function
    Introduced a helper function to count the dynamic assemblies related to a particular class. This helps to understand the usage or reliance of the system on dynamic assemblies.

  • Implemented New Testing Method
    Created a new test to ensure that dynamic assemblies for each type are stored in the cache and no unnecessary assemblies are created during repeated queries. This test helps to ensure our caching system is working effectively, catching bugs early, and promoting a stable, efficient system.

@KaJIbI4 KaJIbI4 changed the title Fix: /~https://github.com/alirezanet/Gridify/discussions/248 memory leak Fix: discussions #248 memory leak Feb 17, 2025
Copy link
Owner

@alirezanet alirezanet left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hey @KaJIbI4, great catch and nice improvement! 👏

Before merging, I'd love to hear your thoughts on cache management. Since it's a static class, the cache object never gets cleared. Do you think that's fine as-is, or would it be better to provide a way for users to dispose of it when needed?

@alirezanet
Copy link
Owner

alirezanet commented Feb 17, 2025

I was thinking maybe we should consider implementing one of these suggestions.

  1. Cache Eviction Strategy:

    • Right now, the cache grows indefinitely. If the number of unique expressions is large, memory usage could become an issue over time.
      • Size Limit: Restrict the number of stored expressions (e.g., keep only the last N entries).
  2. Manual Disposal Option:

    • Provide a way to clear the cache when needed (e.g., a ClearCache() method).
    • This could be useful in long-running applications where memory needs to be freed periodically.

@KaJIbI4
Copy link
Author

KaJIbI4 commented Feb 17, 2025

In the case of classical caching, this would be an absolutely correct remark. In my opinion, in this particular case, there is not much point in forcibly cleaning anything, because the dictionary will contain only the main types used in filtering entities, and there are not many such types.

@alirezanet alirezanet merged commit da65336 into alirezanet:master Feb 17, 2025
3 checks passed
@KaJIbI4 KaJIbI4 deleted the CachedDynamicAssembly branch February 17, 2025 18:17
@hershi9
Copy link
Contributor

hershi9 commented Feb 19, 2025

@KaJIbI4 I have no words to thank you for your fix, I was about to give up on my memory leak after spending days trying to figure it out - now the leak is gone like magic. Thanks a million!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants