-
-
Notifications
You must be signed in to change notification settings - Fork 5.5k
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
RFC: a sampling profiler #1865
RFC: a sampling profiler #1865
Conversation
This is awesome. @JeffBezanson may have to say more about the backtrace and the approach here, but this would incredibly improve julia's usability. |
This will be great to have! A few comments:
|
Thanks for the pointers, Jameson. Along those lines, let me just say I'm very excited about your work in #1831. If you beat me to it, maybe I'll even use some of that functionality in this problem. This is definitely a case where "zero-copy" C struct support could be a big help. I eventually realized that I misunderstood how As I've read more, this is looking like a pretty tricky problem. I'm on Linux, and in the short term that's the only platform I can imagine pulling this off for (and even there it will stretch my abilities a fair bit). I now think the basic architecture needs to be something along the lines of:
My main concern is that technically the julia process will become the child of the ptracing process, until the ptracing process issues a PTRACE_DETACH. In particular, I'm wondering how much trouble we'll get from the fact that any signal sent to the julia process will cause it to stop and wait for a PTRACE_CONT from its parent. Ideally I'd like the ptracing process to also be a julia process, so I can use Dicts and other goodness, but I'm wondering if the remote_call_fetch functions work using signals. If so, writing much of the parent tracing process in Julia will be a non-starter. I can definitely write the ptracing stuff in C or C++ (C++ STL's map class would be a good substitute for Dicts), but in general less C code seems preferable. But this is getting down to some pretty low-level plumbing, so maybe it's the right way to do it. |
@vtjnash, I will also look at SIGEV_SIGNAL one more time and see if that can work. Maybe if all memory is pre-allocated and it's all in C, it can work. It would be easier to avoid |
OK, thanks to @vtjnash's suggestion to use SIGEV_SIGNAL, this now seems to be working on Linux. If OSX implements the same signal and timer functions, this should work there too. On Windows, I've simply tried to make sure it won't break compilation. To test it, after doing a "make" (there's C code to compile here) do the following:
Several notes:
The key question now is how much of this should go in Julia, and how much into a package? Basically there are three options: (i) 100% package, (ii) 100% Julia, and (iiii) merge the task.c changes into Julia but put everything else in a package. Option (i) will require a certain amount of duplication of code in task.c. There's a little bit of motivation to customize the backtracing code (although one could further extend task.c to do this), because it might be nice to truncate the backtraces at any point higher than the @sprofile call, getting rid, for example, of the lines in client.jl. In a simple test, I got a median backtrace length of 24, and ~10 of those were at client.jl or above and hence "useless"; being able to discard those wouldn't be a huge memory savings, but might be nice nonetheless (at the cost of slightly greater complexity). |
I suggest keeping it in extras for now, as it is developed. |
At this point basically everything else is "analytics," the core is ready to use (on Linux, at least). If the "right" way to navigate the data is via a browser, that will probably be an "up for grabs" issue as I'm an HTML noob. I can presumably write a text-based tree viewer. |
@ViralBShah, I guess the bigger issue is the C code, not the Julia code. Question is, should the C code be compiled when we build Julia? |
I think it is fine to build the C code when we build julia. Is there a reason not to do so? |
Only if we don't want to ship a profiler with Julia by default, but instead want to farm it out to a package. And, I want someone on Mac, at least, to see whether they can compile Julia with this pull request. |
I lean in favor of making Task as first-class as possible so that someone else could do the same sort of thing as easily as possible. So that's a vote for (iii) but making the change to task.c in a way that they could be generally useful, not just specific to this one use case (awesome though this use case happens to be). |
Unfortunately this does not compile yet on MacOSX 10.7.5
|
Thanks, Avik. Knowing that is a big help. |
Should we use the timers from libuv? |
I had the same thought. I'll first explore how hard it would be to put profile.c (where the timer is) into a package. |
timers from libuv are synchronous and will be of no help here. i'll see if I can find those definitions (for the missing symbols are Mac), so you can put this in task.c |
@vtjnash, I put in a candidate implementation of BSD timers in the code at https://github.com/timholy/Profile.jl. Once my linking issue gets resolved, I can test them on Linux (which supports both) and then it might be worth a try on OSX. |
Also cc @aviks. |
I can confirm that the c code now compiles on OSX 10.7. One minor change to the build file: -lrt is not available/required on osx afaik. Which leaves me with the linker error that Tim is working on.
Also, FWIW, I vote for option (iii) above |
Thanks for letting me know about -lrt; that should now be fixed. As you can probably guess from the existence of the package, I've gone with option (iii). @aviks, you may be getting a slightly different variant of the linker problem. I had the definition of rec_backtrace only on my machine, because I wasn't pushing changes to core Julia until I could test everything. But I haven't seen anything break, so I just pushed. ...and, miraculously, something I did between a while ago and now changed something. I suspect it was going with support/dtypes.h for all the type definitions, but really I have no clue. But...closing and packaging! |
Success, libprofile.so is created. I can now load Profile, and use the @sprofile macro on the mac. I then saw a
but changing
Yay!! Awesome @timholy ! |
I found this too, already in the pkg. See the README for a demo. |
@timholy I think we should bring the sampling profiler into the julia repo, if you are ok with it. I think a profiler is an essential part of the language stack, and should be part of base. Unless there is a good reason to keep it in a package, a pull request can get us moving towards having a profiler in base. |
I'd be fine with that, but since I think I got the opposite advice during its initial development I guess I'm wondering how universal this feeling is. |
That was from me and I've been convinced that this should be included. |
I've started working on a sampling profiler. The basic idea is very simple: as you run your julia code, periodically take a "backtrace snapshot" that captures the current line # and calling stack. By analyzing this information, one gets a statistical sense for the bottlenecks in code, since the same functions or lines show up repeatedly.
This is intended as a supplement to our existing "intrumenting profiler". A sampling profiler does not give the kind of detailed line-by-line information obtained from an instrumenting profiler, but in principle it does have a couple of its own advantages:
The catch, of course, is that it doesn't yet work. The biggest problem is that the backtrace seems to always come back the same. I'm worried that the backtrace is effectively being run in a separate thread and that I'm just backtracing the profiling code, rather than what's currently running in Julia; assuming that's the problem, I'm not quite sure how to solve it (libunwind-ptrace?). Avoiding this outcome is the entire reason I did this in C rather than trying an implementation in terms of Tasks, but it seems this did not suffice.
A smaller issue is that the backtrace parsing is yielding NULL function pointers, and again I'm not sure why. (I separated out the parsing and the storage of the pointers to try to improve efficiency during the run.)
The long-term idea is that much of this might live in a separate package, but for ease of development I'm coding it inside the Julia repository for now (this simplifies the Makefile issues). It's also possible that this would need to be so tightly integrated with Julia that it shouldn't be a separate package.
If you want to play with this, you'll need this gist: https://gist.github.com/4434156
Demo (uses the gist):