-
Notifications
You must be signed in to change notification settings - Fork 107
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
refactor SBProfile classes to be immutable and/or use shared_ptr #107
Comments
One further issue to consider here: I'm not saying we shouldn't do what you say, but it's another factor to weigh in the calculation. |
I agree. I definitely wouldn't want to jump into this without having Gary fully behind it. |
It looks like the main non-const method that would require some thought is setFlux. Every SBProfile class has it, and it does seem to be used in non-trivial ways (e.g. by SBParse). On the other hand, it probably wouldn't be too bad to have a new class that represents a scaled SBProfile (SBMult or SBScaled perhaps) that just keeps a scaling factor and a pointer to another SBProfile object. The other non-const methods are add (SBConvolve and SBAdd), setRd and setFWHM (both SBMoffat). But none of these seem to be used in ways that couldn't be incorporated into a constructor. At least in the code in the GalSim repository, that is. There is also an op= to check into, but that didn't grep so well, so I'm not sure how much that gets used. @gbernstein Gary, do you use the non-const methods very much in other code that you have? Would you be averse to this idea of making all SBProfile objects immutable? I agree with Jim that this can make the code a lot cleaner and avoid the duplications that happen now in things like SBAdd. |
It may make for some painful constructors for SBProfiles that have many degrees of freedom, and maybe some loss of speed in calculations with objects whose flux has been rescaled with the new adapter class. In particular the SBPixel (which now has some other name?) would need to be passed all of its pixelized data plus all of the information about interpolation in the constructor. But the concept is very do-able, if anyone wants to invest the time to re-coding in exchange for having later use be less prone to user error. On Apr 16, 2012, at 11:57 AM, rmjarvis wrote:
|
Pinging @barnabytprowe @rmjarvis @TallJimbo @gbernstein @pmelchior @joezuntz @joergdietrich @reikonakajima This issue + the connected #76 (SBProfile immutability, shared_ptr) is probably worth resolving before we work on the next milestone. (For the record, the other issues that I'd love to see done during this time are #134 on shearing conventions; #4, which Barney is working on now; and #141, which Mike is working on now. If this turns out to be too ambitious then we can reprioritize and/or make some of these part of the next milestone itself.) First question: If so, then the work involved would be:
|
A potentially relevant comment: the photon shooting (pull request coming today or tomorrow) will attach a lot of precomputed information some kinds of SBProfiles, namely some lookup-table kind of things. The time to compute these will probably be equal to the time it takes to shoot many thousands of photons. So it will be wasteful to recalculate them by creating new instances of class, let's say, SBMoffat, every time you make an object. It will be better to make copies of a single parent object to avoid the recomputations. Also the code will be set up to do these calculations only when needed. I don't think this will interfere with making objects conceptually immutable - but it would be good for others to think about how this would be implemented. And also how the immutability will interact with our desire to have multithreaded code. Is the pointer sharing going to be thread-safe? |
We don't need to lose setFlux at the python level. It would be a wrapper around an SBScale class just like applyRotation is really a wrapper around an SBRotate class. So as long as we're ok with losing setFlux at the C++ level, this would not be hard to do at all. I'm fine with taking it on. |
Yes, I agree, we can have setFlux in python, just not in C++ (I've used it in both). |
I don't think the precomputed stuff should be a problem. I suspect photon shooting will be merged in before this issue, so I'm fine with including making that work correctly as part of this issue. |
If there's a pull request for photon shooting this week then it should be in before this issue gets dealt with. Gary's question about the impact on multi-threaded code is a good one that we should think about... |
The other big changes that would be required aside from setFlux would be the classes that are based on lists of other SBProfiles, like SBAdd and SBConvolve. In one sense, those would get a lot nicer because they wouldn't have to copy their elements, but in many cases it's useful to append to those in-place. We might be able to retain that by making a free function or static member function that appends to something given a shared_ptr, and only does a copy if the pointer is non-unique:
This is a slightly ugly interface in C++ because we can't make it a regular member function, but it would be perfectly natural in Python. |
I have no opinions on C++ code. |
I don't think it's a hardship to always construct Add and Convolve with the full list. Especially at the python level, it is easy to append to a list, and then when ready, make the container object with the final list. In C++, we can retain the add function, but make it private and used only by the class's constructor. |
Jim raises a good point, which is that it will no longer be possible to build SBAdd or SBConvolve one term at a time using the append() method without generating a new object every time. I would hope that the existence of the shared pointers could be completely private in the C++ code whereas Jim's code fragment returns them. We'll see how the implementation goes. Jim, when you say there is a lock on the reference count of shared_ptr, what multithreading library (libraries) does this lock work with? |
Actually, it looks like I may have lied about the lock, but the thread safety guarantee is the same as what I described. I'm not really sure how it works, but here's a link to the documentation: http://www.boost.org/doc/libs/1_48_0/libs/smart_ptr/shared_ptr.htm#ThreadSafety |
Okay. I'm getting the sense that this is probably worthwhile but will take more work than I initially expected to handle things like the photon-shooting, the classes like I had initially proposed this for during our cleanup period because I thought it is better done in a quieter period rather than when we're in the midst of lots of coding for the milestone. But given the amount of work, I think it's a bit much to expect someone to do this in just one week. If there is someone who is willing to do this in the next ~2 weeks I suspect that will be okay... that's getting into the start of the next milestone period, but it's still early enough that people can merge in the new code to their branches relatively early on without things being too chaotic. Or, we could think about whether we want to adjust the next milestone period to allow for this to all take place first. Does someone particularly want to take this on? |
I don't mind. |
Thank you - but, given that it does seem like a fair amount of quite technical work, perhaps we should have a discussion on the phone call (Tues) as to whether people really want this to happen now, or whether there might be some value in waiting? On May 18, 2012, at 10:41 AM, Mike Jarvis wrote:
Rachel Mandelbaum |
I think I'll start working on this next. I've gone through the SBProfile class in detail, and here are the public member functions that are currently not
We also need to think about anything that is marked
So no problem for any of them. They are all the kind of thing that could in principal be setup in the constructor, but we defer until later for possible efficiency reasons. (Especially the shoot stuff, since we often won't need it.) And they are all updated in only one location, which makes it easy to put mutex guards around them if we every decide that we want these to be thread safe to make sure that only one thread updates them. In other words, none of them are the kind of mutable variables that cause problems for an officially "immutable" object. Anyone have any objections to any of this? I'll talk about implementation details next, but wanted to get all of this vetted first. |
OK, implementation details. Here is what I was thinking of doing:
So the user interface would basically be equivalent to the way you use things in python. Everywhere we currently use This would also allow us to shift pretty much all of the implementation details from the .h file into the .cpp file. Which means that SBProfile.cpp would get even larger. So I think we should split it up into more separate files corresponding to the different derived classes. However, as far as I can tell, there is no git analog to Comments on this plan? |
When you change how |
I suppose, but is this desirable? It would mean that to have a Gaussian with a particular flux, you would specify this in two steps:
rather than just
We could do it that way, but I guess I don't see any advantage to it. |
I think I like the virtual pimpl approach, but it's not clear to me how the outer classes would work. Would there be a full hierarchy of nonpolymorphic classes that mirrors the polymorphic impl hierarchy? Or just one outer Here are the advantages of the impl approach that I can think of (relative to just making the public classes polymorphic and putting them in shared_ptrs):
There are some disadvantages, however:
I'm not convinced we'd get a lot of value from pimpl unless we do want to use copy-on-write instead of pure immutability, and I think Mike's made a pretty good case that we can get by with pure immutability. But I also think a pimpl with a single outer class is also a very clean design that I wouldn't have any problem with, if we can live with removing the derived-class-specific behavior (which I don't think we're using at present). A pimpl approach with a nonpolymorphic outer hierarchy worries me a little more; it might be fine, but we should think about return types and casting before pushing forward with it. |
On git: there is some support for "move detection". If two files are similar enough, git will notice that one is a copy/rename of the other. So it might be worth doing the refactor into separate files separately from any editing of the files to make that work better. But I'm not sure how well that works when splitting one file up into several anyhow, and I don't think it's a major issue. The history will still be there, it just won't quite be associated with the new code. |
My idea was that there is only one pimpl, which is in the SBProfile class. Derived classes are polymorphic with SBProfile, but they are very simple. The constructor makes the (SBProfile) pimpl with the correct derived Impl class that actually does all the work. If there are any class-specific things, they are also defined, since they know they can static_cast the pimpl to their particular Impl class and use it how they want. SBProfiles are generally passed around by value, since the shared_ptr is internal. I can't think of any reason we would need a conversion between outer classes, so I don't think your concern about the dynamic casting issue is going to bite us at all. We will have copy constructors for the various derived classes that work just like the base class. So I was planning on having shear() et all return an SBDistort by value rather than an SBProfile. This could be true for anything else that returns an SBProfile object. If you are really making one of the derived classes, you can return that instead. Then if people want to use it as the derived class, they just do so. If they don't care what it is, they can receive it as an SBProfile, which would also work. The only downside I see is your first point that we will effectively have a second shared_ptr from python on top of the one that we have in C++ rather than being able to join them up into the same thing. But I can't really see any reason why that would be a problem. |
To give a bit more concreteness to my proposal, I started the conversion so people can see what it would look like. I converted SBProfile, SBAdd and SBDistort to the pimpl idiom. You can peruse the code here. Jim, maybe we could do a design review for this to discuss the different issues that you brought up. Maybe Monday or Tuesday? I'll set up a doodle poll if you think it would be worthwhile. |
…aseDeviate, and added more usability options to all Deviate classes. (#107)
I think I've finished the last important item that could really benefit from shared_ptr. The latest one is the Random classes. I rearranged this a bit in a way that I think is pretty nice. The old syntax still works:
However, if you don't actually need
There is also a version that will automatically create the RNG using the time:
Furthermore, the constructor that used to take a The way this works is that the base class now holds the I also added reset methods that sever the connection of a particular deviate from any other deviate that had been sharing the rng with it, and then later reconnect it if you want. I don't know if we would ever need this, but it seemed like a neat feature that was easy to add, so I did. |
Also, I mentioned this on the telecon, but for Gary's benefit, I've also finished implementing option 2 that we talked about for SBInterpolatedImage. There is a new class MultiImageHelper that encapsulates all the information about the multiple images. Then you can create an SBInterpolatedImage from that and a vector of weights. So I think that gets all the functionality that we would need for this. There are a couple more places in the code that use bare pointers. SBParse, ProbabilityTree and Pset. Should I tackle those too? I don't think any of them are that important to change. (i.e. no issues about custodian and ward or similar) And I don't think there are any memory bugs in them, so it would be purely for cosmetic reasons I think. |
Nice! On Jun 5, 2012, at 7:01 PM, Mike Jarvis wrote:
Rachel Mandelbaum |
Conflicts: galsim/base.py galsim/real.py
Branch #107 Added shared_ptr wrapping to a lot of current code that used bare pointers or references.
Merged into master. |
Great, thanks Mike...
Barnaby Rowe Jet Propulsion Laboratory Department of Physics & Astronomy |
Fantastic, thanks! On Jun 11, 2012, at 7:31 PM, Mike Jarvis wrote:
Rachel Mandelbaum |
A bug Mike just fixed in SBAdd reminded me of my nervousness about the raw pointers used in SBProfile. Now that we've got boost::shared_ptr available to us, I think we could make the code a lot safer and easier to maintain by using that in many cases.
Before doing that, however, I think we should take a look and see if we can make the SBProfile classes (or perhaps just most of them) immutable. They have very few mutators currently; the main ones we'd have to worry about are
SBProfile::setFlux
,SBAdd::add
, andSBConvolve::add
. We'd have to replace these with functions that created a new, appropriately modified SBProfile. If we can make everything immutable, we can also get rid of theduplicate()
member functions, and we'll be able to get even more out of usingshared_ptr
.Even if it turns out that immutability is not the right design choice, I think we should still start using shared_ptr in a number of places. This would also provide a nice solution for #76, as we could use shared_ptr to ensure the lifetime of the interpolant is at least that of the
SBPixel
that relies on it.The text was updated successfully, but these errors were encountered: