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

Better Async performance with many concurrent logger threads (up to 500%+) - Async attribute and AsyncWrapper #2650

Merged
merged 1 commit into from Aug 14, 2018

Conversation

snakefoot
Copy link
Contributor

@snakefoot snakefoot commented Apr 7, 2018

ConcurrentQueue works very well in NetCore2. But not sure when the improvements will get to NetFramework.

When 8 threads are attacking the same async-wrapper, then the ConcurrentRequestQueue performs twice as fast on NetCore2. This should help fix #2627

@snakefoot snakefoot force-pushed the AsyncConcurrentRequestQueue branch from f4f44d0 to 4a358b5 Compare April 7, 2018 00:14
@codecov
Copy link

codecov bot commented Apr 7, 2018

Codecov Report

Merging #2650 into master will decrease coverage by <1%.
The diff coverage is 81%.

@@           Coverage Diff           @@
##           master   #2650    +/-   ##
=======================================
- Coverage      81%     81%   -<1%     
=======================================
  Files         326     327     +1     
  Lines       24414   24564   +150     
  Branches     3107    3135    +28     
=======================================
+ Hits        19689   19793   +104     
- Misses       3863    3920    +57     
+ Partials      862     851    -11

@snakefoot
Copy link
Contributor Author

snakefoot commented Apr 9, 2018

After making some performance tests, then it looks like the allocations caused by ConcurrentQueue are dwarfed by the ones coming from NLog. Decided to enable the ConcurrentRequestQueue by default.

This is the performance results when combined together with #2653

Logger Name Messages Size Args Threads Loggers
Logger 10.000.000 16 0 1 1
Test Name Time (ms) Msgs/sec GC2 GC1 GC0 CPU (ms) Alloc (MB)
Master 11.522 867.848 0 20 41 16.156 6.256,8
PR (NetCore2) 11.431 874.762 0 22 46 14.296 Unknown
PR (Net471) 11.355 880.667 0 13 27 17.234 6.451,8
Logger Name Messages Size Args Threads Loggers
Logger 10.000.000 16 0 8 1
Test Name Time (ms) Msgs/sec GC2 GC1 GC0 CPU (ms) Alloc (MB)
Master 30.595 326.845 0 13 26 112.328 6.329,7
PR (NetCore2) 9.723 1.028.384 0 14 28 19.062 Unknown
PR (Net471) 11.213 891.809 0 9 18 18.281 6.539,0
Logger Name Messages Size Args Threads Loggers
Logger 10.000.000 16 0 16 1
Test Name Time (ms) Msgs/sec GC2 GC1 GC0 CPU (ms) Alloc (MB)
Master 41.264 242.337 0 13 26 158.953 6.338,1
PR (NetCore2) 9.669 1.034.186 0 14 28 17.109 Unknown
PR (Net471) 10.881 918.971 0 9 19 20.359 6.549,8
Logger Name Messages Size Args Threads Loggers
Logger 10.000.000 16 0 32 1
Test Name Time (ms) Msgs/sec GC2 GC1 GC0 CPU (ms) Alloc (MB)
Master 55.762 179.330 0 13 26 218.984 6.343,9
PR (NetCore2) 9.531 1.049.191 0 13 27 20.750 Unknown
PR (Net471) 10.956 912.693 0 9 18 23.406 6.556,8

@snakefoot snakefoot force-pushed the AsyncConcurrentRequestQueue branch from efe6874 to 7b972de Compare April 9, 2018 21:05
@snakefoot
Copy link
Contributor Author

snakefoot commented Apr 9, 2018

Looks like the Net40 ConcurrencyQueue can seem dangerous:

https://blogs.msdn.microsoft.com/pfxteam/2012/05/08/concurrentqueuet-holding-on-to-a-few-dequeued-elements/
https://blog.mischel.com/2011/03/22/memory-leak-in-concurrentqueue/

Will only have it enabled by default on NETSTANDARD2_0 to avoid random complaints.

@304NotModified
Copy link
Member

Wow! nice improvements

The queue is used for the async and buffering target wrapper, isn't? (Now on mobile)

@304NotModified 304NotModified added this to the 4.5.3 milestone Apr 9, 2018
@snakefoot
Copy link
Contributor Author

The queue is used for the async and buffering target wrapper, isn't?

Nope it is only used by the AsyncTargetWrapper. The BufferingTargetWrapper uses a container called LogEventInfoBuffer.

@304NotModified 304NotModified changed the title ConcurrentRequestQueue depends on .NET ConcurrentQueue Async wrapper/attribute, 300-500% performance improvement in .net 4.5+ and .net standard 2+ Apr 9, 2018
@304NotModified
Copy link
Member

I changed the title to make it more clear for outsiders :)

@snakefoot
Copy link
Contributor Author

snakefoot commented Apr 9, 2018

I changed the title to make it more clear for outsiders :)

Well you only see the performance improvement if you are very aggressive against one async-target. Normal users will probably see a very small performance drop (See my test with 1 thread). But it is a better out-of-the-box-experience for those special cases.

Also the performance improvement only works when ALL layout-renderers are thread-safe or thread-agnostic. See also #2653

src/NLog/Targets/Wrappers/AsyncTargetWrapper.cs Outdated Show resolved Hide resolved
get { return _requestQueue is AsyncRequestQueue; }
set
{
if (value != _requestQueue is AsyncRequestQueue)
Copy link
Member

Choose a reason for hiding this comment

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

I prefer the pattern matching syntax her, eg is AsyncRequestQueue queue (c# 7)

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Not sure I understand, but will add some parentheses.

Copy link
Member

Choose a reason for hiding this comment

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

When pattern matching then the cast (line below) is not needed.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Still don't understand

/// <summary>
/// Gets the number of requests currently in the queue.
/// </summary>
public int Count => (int)_count;
Copy link
Member

Choose a reason for hiding this comment

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

Isn't this tricky?

Copy link
Contributor Author

@snakefoot snakefoot Apr 9, 2018

Choose a reason for hiding this comment

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

The Count is only there for debugging. IsEmpty is the one used for decisions. Think the container (or computer) will break if more than two billion items.

Copy link
Member

Choose a reason for hiding this comment

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

OK clear, maybe we should set that in the <remarks>

src/NLog/Targets/Wrappers/AsyncTargetWrapper.cs Outdated Show resolved Hide resolved
@snakefoot snakefoot force-pushed the AsyncConcurrentRequestQueue branch from 7b5bb24 to 7c9c8b8 Compare July 29, 2018 10:42
@304NotModified 304NotModified removed their assignment Aug 2, 2018
Copy link
Member

@304NotModified 304NotModified left a comment

Choose a reason for hiding this comment

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

Looks good, some small questions about the structure of the code.

If preferred, I could also push a commit on this.

get { return _requestQueue is AsyncRequestQueue; }
set
{
if (value != (_requestQueue is AsyncRequestQueue))
Copy link
Member

Choose a reason for hiding this comment

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

I would prefer having a backend boolean for this (e.g. _forceLockingQueue), and and reset _requestQueue if value != _forceLockingQueue .

That would be more logical IMO. What do you think?

/// <summary>
/// Gets the number of requests currently in the queue.
/// </summary>
public int Count => (int)_count;
Copy link
Member

Choose a reason for hiding this comment

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

OK clear, maybe we should set that in the <remarks>

@304NotModified
Copy link
Member

PS @snakefoot

As you may have noticed, we have now also a "dev" branch. I'm switching to the gitflow-workflow because:

  • PRs are too long open
  • There are too many features in a patch release, that should be in a minor release. (see semver

I use it also at work at some larger projects, it work pretty well.

This means that important bugfixes should be to "master", and need a "backport" to "dev".

@snakefoot
Copy link
Contributor Author

snakefoot commented Aug 6, 2018 via email

@304NotModified
Copy link
Member

Think it is okay that complicated PRs can stay pending for a long time. Kind of inspires you to make small and simple PRs. Something that I need to practise :)

yeah I like also small and simple PRs. Easier to review/test/understand.

@snakefoot
Copy link
Contributor Author

@304NotModified Updated the PR with the requested changes.

@304NotModified 304NotModified self-assigned this Aug 12, 2018
@304NotModified 304NotModified changed the title Async wrapper/attribute with better performance when many concurrent logger threads Better Async performance with many concurrent logger threads (up to 500%+) - Async attribute and AsyncWrapper Aug 14, 2018
@304NotModified
Copy link
Member

nice!!

@304NotModified 304NotModified merged commit 523e8da into NLog:master Aug 14, 2018
@snakefoot
Copy link
Contributor Author

snakefoot commented Aug 14, 2018

@304NotModified You merged this into NLog:master and not NLog:dev

@304NotModified
Copy link
Member

304NotModified commented Aug 14, 2018

ooops. thx

@304NotModified
Copy link
Member

now on dev

@snakefoot
Copy link
Contributor Author

Updated documentation: https://github.com/NLog/NLog/wiki/AsyncWrapper-target

@304NotModified 304NotModified added the documentation done all docs done (wiki, api docs, lists on nlog-project.org, xmldocs) label Feb 22, 2019
@304NotModified
Copy link
Member

thanks!

@snakefoot snakefoot deleted the AsyncConcurrentRequestQueue branch April 4, 2020 13:09
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
documentation done all docs done (wiki, api docs, lists on nlog-project.org, xmldocs) needs documentation on wiki performance size/L
Projects
None yet
Development

Successfully merging this pull request may close these issues.

None yet

2 participants