Hi, using cl_UP_MI I get very strange effects that look like problems with the memory management (the contents of one polynomial is suddenly vanishing while operating on another for example). So far, I figured out that having the leading coefficient set to zero via set_coeff() causes trouble even if I call finalize(). The manual is kind of sparse on that topic so I am not quite sure whether this is a bug or a feature. But even when I try to avoid that case, call finalize everywhere, and only do assignments (hope they are safe ...), then polynomials aside from the ones involved in the operations change. I will investigate this case further but it would be helpful to know whether the cl_UP_MI has problems/bugs? Regards, Jens
Hi, my problems with cl_UP_MI seem to boil down to what happens in the following code fragment: cl_modint_ring R = find_modint_ring(11); cl_univpoly_modint_ring UPR = find_univpoly_ring(R); umod a = UPR->create(0); a.set_coeff(0, cl_MI(R, 1)); a.finalize(); cout << "a " << a << endl; // gives: a (1 mod 11) umod c = a; c.set_coeff(0, cl_MI(R, 10)); c.finalize(); cout << "c " << c << endl; // gives: c (10 mod 11) cout << "a " << a << endl; // gives: a (10 mod 11) So, my question to the developers is now: WTF?!?! Regards, Jens
Dear Jens, On Fri, Sep 26, 2008 at 07:44:09PM +0200, Jens Vollinga wrote:
my problems with cl_UP_MI seem to boil down to what happens in the following code fragment:
umod c = a;
cl_UP is actually a pointer to a coefficient vector. This assignment creates the pointer c which points to the same data as a. That's probably not what you want. Perhaps you meant c = a.ring()->create(degree(a)); for (std::size_t i = 0; i <= degree(a); ++i) c.set_coeff(i, a.coeff(i)); c.finalize();
So, my question to the developers is now: WTF?!?!
Wrong expectations from the user side. @ developers: Actually the semantics is a bit weird. Should we implement operator= which "just works"? Or at least document the current behaviour? Best regards, Alexei -- All science is either physics or stamp collecting.
Alexei Sheplyakov wrote:
cl_UP is actually a pointer to a coefficient vector. This assignment creates the pointer c which points to the same data as a. That's probably not what you want. Perhaps you meant
c = a.ring()->create(degree(a)); for (std::size_t i = 0; i <= degree(a); ++i) c.set_coeff(i, a.coeff(i)); c.finalize();
Correct.
Actually the semantics is a bit weird. Should we implement operator= which "just works"? Or at least document the current behaviour?
Now, this is a design question. One of the basic principles of CLN is that assignments are fast. Assignments copy only pointers, and reference counts are used to handle the memory allocations. Other designs, such as copying entire arrays upon '=', just lead to slow code. I see two options. 1) The dilemma that Jens encountered is a classical C++ classes problem, often seen in C++ classes that represent strings, vectors, etc. For strings, polynomials, etc. the user expects to have no side effects on earlier copies of an object. At the same time he also expects to be able to mutate elements inside the object. This problem was solved in Java by separating the side-effect-free and the mutating behaviours into two different classes. See the Java classes for String [1] - immutable - and StringBuffer [2] - mutable, but used only to construct a String. Because of this, working with strings in Java is both easier and more efficient than in C++. You can introduce a new class, say, cl_UP_coefficients or cl_UP_embryo, that has a set_coeff method and a finalize method that returns a cl_UP. cl_UP will lose these methods, but have a constructor that takes a cl_UP_coefficients argument. This is an API change, for the better. 2) The other option is to keep the current design and have set_coeff and finalize throw an exception when the reference count is > 1. And, of course, document the behaviour and provide a separate function for "deep copying" of a polynomial. My vote goes for 1). Bruno [1] http://java.sun.com/j2se/1.5.0/docs/api/java/lang/String.html [2] http://java.sun.com/j2se/1.5.0/docs/api/java/lang/StringBuffer.html
Hello! On Sat, Sep 27, 2008 at 04:48:03PM +0200, Bruno Haible wrote:
You can introduce a new class, say, cl_UP_coefficients or cl_UP_embryo, that has a set_coeff method and a finalize method that returns a cl_UP. cl_UP will lose these methods, but have a constructor that takes a cl_UP_coefficients argument.
Changing the coefficients is a common operation (think of division, GCD, etc). So this crippled^W immutable cl_UP is next to useless.
2) The other option is to keep the current design and have set_coeff and finalize throw an exception when the reference count is > 1. And, of course, document the behaviour
I think this is the only sane option.
and provide a separate function for "deep copying" of a polynomial.
What about a copy constructor? Best regards, Alexei -- All science is either physics or stamp collecting.
Hi! Alexei Sheplyakov wrote:
You can introduce a new class, say, cl_UP_coefficients or cl_UP_embryo, that has a set_coeff method and a finalize method that returns a cl_UP. cl_UP will lose these methods, but have a constructor that takes a cl_UP_coefficients argument.
Folks, please correct me, if I am wrong: Aren't C++ developers aware of the costs of deep copying and are accustomed to passing arguments as references, do acrobatic exercises with const_iterator's and all this? Many such idioms are becoming the de-facto lingua franca, precisely because of the costs of operator= and the copy ctor. It may not be as syntactically elegant as it could be, but only a few percent of C++ programmers are aware of this. (Case in point: GiNaC has shallow copying and COW semantics. I once did an experiment and passed all arguments as const ex instead of const ex&. It made it noticably slower. The reason is the overhead induced by maintaining the refcount.)
Changing the coefficients is a common operation (think of division, GCD, etc). So this crippled^W immutable cl_UP is next to useless.
2) The other option is to keep the current design and have set_coeff and finalize throw an exception when the reference count is > 1. And, of course, document the behaviour
I think this is the only sane option.
and provide a separate function for "deep copying" of a polynomial.
What about a copy constructor?
I do not really understand: Are we really discussing the option to have deep copying, but name it strangely, ie. other than operator=? (Of course, we should implement operator= and the copy ctor in a symmetrical way. If they are really frowned upon, then we should just declare them private and not implement them. This should guarantee that the refcount is never incremented. But I'm far from convinced this is a good idea.) What do you think? Bye! -richy. -- Richard B. Kreckel <http://www.ginac.de/~kreckel/>
Hello Alexei,
You can introduce a new class, say, cl_UP_coefficients or cl_UP_embryo, that has a set_coeff method and a finalize method that returns a cl_UP. cl_UP will lose these methods, but have a constructor that takes a cl_UP_coefficients argument.
Changing the coefficients is a common operation (think of division, GCD, etc). So this crippled^W immutable cl_UP is next to useless.
Indeed, division and GCD can become a lot slower if in-place modification of polynomial is impossible. Then I agree with you, for option 2.
and provide a separate function for "deep copying" of a polynomial.
What about a copy constructor?
The copy-constructor and the assignment operator should always do the same thing in C++. People won't understand if cl_UP a = ...; cl_UP b = a; and cl_UP a = ...; cl_UP b; b = a; behave differently. And in CLN these simple assignments are only copying a simple reference, not the deep contents (see the other mail). Bruno
Hi, On Sat, Sep 27, 2008 at 09:24:06PM +0200, Bruno Haible wrote:
The copy-constructor and the assignment operator should always do the same thing in C++.
AFAIR the standard does not require this.
People won't understand if
cl_UP a = ...; cl_UP b = a;
and
cl_UP a = ...; cl_UP b; b = a;
behave differently.
Yes, this is a bit unexpected, especially if it is not documented properly. Current behaviour is even more confusing, though.
And in CLN these simple assignments are only copying a simple reference, not the deep contents (see the other mail).
In C++ "deep copying" is typically called operator=. Calling some other operation `operator=' is a bit surprising, although there might be valid reasons to do so. But sometimes it's just plain inappropriate (case in point: cl_UP). Best regards, Alexei -- All science is either physics or stamp collecting.
Hello Alexei,
And in CLN these simple assignments are only copying a simple reference, not the deep contents (see the other mail).
In C++ "deep copying" is typically called operator=.
Rather, in C++, "deep copying" is the default for operator= and copy constructor. In those areas where this is not appropriate, people use "smart pointers". I did not invent this idiom. It is widely used. The reasons why CLN uses smart pointers for everything are: 1) Its main purpose is to allow the formulation of mathematical algorithms directly in C++. As in a scripting language. Without requiring the programmer to think in terms of memory allocation. The programming model of a scientist in this case is to manipulate opaque values that are elements of rings. CLN brings the philosophy of computer algebra languages into C++. 2) When you do computations with polynomials, you need a maximum of sharing of the polynomial representations. This is the basic implementation principle of Maple. In systems where deep copying of polynomials is the default, you cannot do computations as large as those possible in Maple - because you run out of memory earlier. In this light, set_coeff and finalize must be implemented as copy-on-write operations: create a deep copy implicitly when the reference count is > 1. No explicit method for deep copying is then needed at all. Bruno
Dear Bruno, On Sun, Sep 28, 2008 at 11:28:37AM +0200, Bruno Haible wrote:
The reasons why CLN uses smart pointers for everything are:
1) Its main purpose is to allow the formulation of mathematical algorithms directly in C++. As in a scripting language. Without requiring the programmer to think in terms of memory allocation.
First of all, smart pointers or not, but thinking about complexity of operations (in particular, memory allocation) is unavoidable. Smart pointers make some operations cheap, but other operations suddenly become expensive. Case in point: people expect operator+= to be efficient, but in fact it's not (because it does a copy), so they get very confused. See e.g. http://www.ginac.de/pipermail/ginac-list/2008-August/001396.html Secondly, those who don't want to think about memory allocation should not use a low level programming language (such as C++).
The programming model of a scientist in this case is to manipulate opaque values that are elements of rings. CLN brings the philosophy of computer algebra languages into C++.
Unfortunately the reality is a bit different from this model. One need to learn that some common operations are (unexpectedly) expensive for no obvious reason. (Of course, the reason becomes clear as one learns about memory allocation, smart pointers, and all that).
2) When you do computations with polynomials, you need a maximum of sharing of the polynomial representations.
Not really. Sharing makes data immutable. Thus any simple modification (such as multiplication by a constant, changing a single coefficient, taking a derivative) ends up doing a copy. Making a copy (even a shallow one) requires maintaining reference counts which is prohibitively expensive w.r.t. the cost of the modification itself. As a result a correctly written user code needs tons of RAM and/or works way too slow for no "obvious" reason. For example, the same univariate GCD code works about 3 -- 10 times faster (and uses less memory) if I use std::vector<cl_I> instead of cl_UP. That's why I think "smart pointers everywhere" approach is wrong.
In systems where deep copying of polynomials is the default, you cannot do computations as large as those possible in Maple - because you run out of memory earlier.
I'm afraid your theory is a bit observationally challenged. Those Ma... and Ma......... things often run out of memory while reading the input expressions (which can be process with either GiNaC or FORM), let alone doing something useful.
In this light, set_coeff and finalize must be implemented as copy-on-write operations: create a deep copy implicitly when the reference count is > 1. No explicit method for deep copying is then needed at all.
I don't think copy-on-write (univariate) polynomials is a good idea. Best regards, Alexei -- All science is either physics or stamp collecting.
Dear Alexei, Alexei Sheplyakov schrieb:
cl_UP is actually a pointer to a coefficient vector. This assignment creates the pointer c which points to the same data as a. That's probably not what you want. Perhaps you meant
c = a.ring()->create(degree(a)); for (std::size_t i = 0; i <= degree(a); ++i) c.set_coeff(i, a.coeff(i)); c.finalize();
now I understand, thanks for the help! Regards, Jens
participants (4)
-
Alexei Sheplyakov
-
Bruno Haible
-
Jens Vollinga
-
Richard B. Kreckel