-
-
Notifications
You must be signed in to change notification settings - Fork 2k
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
issue 2176 Time service removal #2182
Conversation
The main bug was that ceil was called on time returned by loop.time() before being used to schedule the next iteration of the callback that updated the TimeService. The TimeService was then incrementing its stored time by one second. This resulted in the time being updated by one second about every 1.5 seconds, quickly causing it to fall behind. There was code to reset TimeService's internal time every 10 minutes, but that has been removed. Benchmarking has shown that just calling time.time() has no statistically valid performance difference than the code that checked for the need to reset. Even if it were slightly slower, it is only called once per second. Also, the counter that was used to compare to the reset limit was never incremented, so the reset never actually ever happened. I was tempted to remove the properties for accessing time and loop time, as time.time() and loop.time() are not expensive calls. But I didn't want to break the API for anyone using this. The only significant performance gain I was able to find was with strtime. My use case doesn't push aiohttp very hard, so I can't replicate whatever performance issue TimeService was intended to solve.
More simplification of TimeService, only used to cache formatted date
slightly faster and cleaner Date formatting code
removed TimeService in favor of a simple strftime call
-1 we should cache date value |
Codecov Report
@@ Coverage Diff @@
## master #2182 +/- ##
==========================================
- Coverage 97.12% 97.11% -0.01%
==========================================
Files 39 39
Lines 7884 7864 -20
Branches 1366 1368 +2
==========================================
- Hits 7657 7637 -20
Misses 101 101
Partials 126 126
Continue to review full report at Codecov.
|
@fafhrd91 If my benchmarking is correct, caching the formatted date string saves approximately |
tests/test_helpers.py
Outdated
assert time_service._strtime is None | ||
assert time_service._time > time | ||
assert time_service._count == 0 | ||
def time_service(): |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
What's this ?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I missed some dead code in tests, have removed it.
oh, so that means all my assumptions about performance was wrong. all my performance related work is probably wrong as well, nice :) @asvetlov comments? |
@fafhrd91 Do you have any notes on the benchmarking/profiling you did? If you do, I can try to replicate your results. There might be some case where time.time() or time.gmtime() is slow, in which case TimeService/caching would make sense. |
I don't have any notes, that was relatively long time ago. but @asvetlov came to similar conclusion, he added TimeService. |
If it matters, the strftime format I'm using is more performant than the one used in TimeService. Don't have the numbers handy right now, but it ran about 30-40% faster. After I tried that, I noticed that the same format was used elsewhere, so I made it into a constant. hdrs.RFC822_FORMAT |
I am fine with removing cache. just waiting for @asvetlov to approve. |
IIRC adding time service reduced response time by at about 10% for cases like
Your number is 0.00000142 sec per request for time service disabled. It's much less than 10% but still significant degradation. But your proposal to use Maybe we need resurrection of #2180 but I still don't like global vars, we could rewrite |
I think global var is fine, it is private. Removing time service reduces amount of doc we need |
We don't mention Anyway, if @fafhrd91 @socketpair and other committers prefer globals in this particular case -- I can live with it. |
it is part of WebRequest api, technically we should document it. |
@greg-barnett Could you benchmark old and new code in same environment ? |
@asvetlov time_service is public attribute. I'd prefer to replace class with one function if we touch it in any case. Amount of functionality is too small for whole class. But I am fine with rather decision |
@fafhrd91 I see your point now. Nevermind, the property should be deleted. Fortunately it's not documented. I'm fine with rather decision too. @socketpair please reopen #2180 |
It's better to fix current PR (add caching) than edit mine. Well, I will mix them up tomorrow. |
I'm probably not going to have time to work on this for at least a few days. If the solution is going to involve global variables, I'd also prefer that someone else do the commit. I don't want someone to see a global, do a a git blame, and then go away with a bad opinion of me :) @asvetlov I missed that it was using Locale's names. So, in addition to TimeService, the existing code for headers where I replaced format string with the constant will also need to be fixed. I suspect that the proper way to do this is something like the following (completely untested)
|
@greg-barnett I like your sketch. |
I have nechmarked
|
I updated my PR with code borrowed from @greg-barnett. This PR probably could be closed. |
@socketpair your PR doesn't cover the additional issue we've discovered: the locale dependent format being used for headers was already being used for last-modified headers in aiohttp/webresponse.py This is what I believe to be the requirements to fix this issue:
These are not requirements, but they should probably be followed anyway:
My current plan, which I hope to have completed within the next 2 days. Write benchmarking code that tests:
Run the benchmarking code twice, once in en_US, once in a locale where "%a" breaks rfc822 format. I think my solution is going to be similar to the TimeService I posted in my last comment, but make the class a singleton (with a different name, probably RFC822DateFormatter), and have one public method in helpers.py that calls it. I'm thinking a singleton is less ugly than private global variables. |
I created a very simple server, and ran apache bench against it. All variations tried came in around 2,000 requests per second, with a fairly large amount of variation. Code that attempted to pick a formatting strategy based on locale did not increase performance noticeably. My latest version uses class variables, which are probably just as bad as global variables. @socketpair's code, if expanded to fix Last-Modified headers, is probably the way to go. If my current approach is the way you want to go, I'll add some unit tests. |
I believe it's fixed by #2180 |
Yes, TimeService is removed, so bug is closed. But I did not check if caching of datetime may be removed completely. Maybe it saves nano-seconds. I don't know. |
This thread has been automatically locked since there has not been any recent activity after it was closed. Please open a [new issue] for related bugs. |
removed TimeService, which had bugs causing it to return very incorrect dates.
It has been mentioned that TimeService was written as a performance optimization, but I have not been able to recreate any conditions in which the very small performance improvement would be noticeable.