Replacement for GMP: Update

Thorkil Naur naur at
Fri Dec 29 08:32:33 EST 2006


Thanks a lot for your reply. Here are some comments to this. As is customary, 
I must apologise for the late reply (the response time for this conversation 
seems to increase exponentially with time) which also may very well make some 
of the comments totally out-dated.

On Friday 01 September 2006 06:43, Peter Tanski wrote:
> ...
> > For a brief overview of the speed of the libraries I looked at   
> carefully, see
> (I added a few charts to show the speed of ARPREC, OpenSSL's BN, GMP  
> and LibTomMath.  ....)  I tested GMP and OpenSSL's BN for reference.

I must confess that it took me a while to understand what you were doing here 
and I am still uncertain: For example, how many multiplications were actually 
performed for bit size 4096? In addition, for "Powers", the markings on the 
horizontal axis ("256 pow(n,3)", "512 pow(n,4)", "1024 pow(n5)" (missing a 
"," here?), ...) on your graph seem to indicate that you are changing two 
different variables (the bit size and the exponent) at the same time. I would 
suggest that you also quoted the original measurements directly. And perhaps 
(especially in case of the "Powers" and "Division") some more details about 
what was actually measured.

It is distinctly odd that squaring seems to be more expensive than 
multiplication for some bit sizes in 3 of the 4 measured cases. Also, I 
wonder what divisions these libraries are capable of carrying out so much 
faster than comparable multiplications. For the division measurements, I may 
again simply be presuming something about your experiments that simply isn't 

> ...
> I keep talking about ARPREC--why?  For three reasons:
> (1) I trust its level of precision--this has been tested, see FFTW's  
> benchmark page for accuracy: 
> gcc4/

I am not sure I understand here: With respect to Haskell Integers, I would not 
say that there is any concept of a "level of precision": If Integer 
computations are not accurate, they are wrong.

> (2) if you look at the charts, although ARPREC is bad in division it  
> simply blows GMP, OpenSSL and LibTomMath away: at 4096 bits (85  
> doubles--each double has conservatively only 48.3 or so bits of  
> integer precision), ARPREC can take a full random number to pow(n,7)  
> in .98 seconds, compared to 77.61 or so seconds for the leader of the  
> Integer-based libraries, GMP.  (I ran the tests many times to make  
> sure readings weren't flukes.)

Again, some additional details about these measurements would be most welcome.

> (3) of all the FFT-based arbitrary precision libraries available,  
> ARPREC is the only BSD-licensed one--Takuya Ooura's library (used in  
> MAPM) is only a FFT algorithm and not necessarily either fast or  
> accurate.  The rest of the FFT libraries available are essentially  
> GMP-licensed.
> So I am in an unenviable position: I intend to fulfill my promise and  
> get a replacement for GHC (and maybe more), but I have to essentially  
> build better functionality into the front-runner, ARPREC.  At present  
> I have been working with vector-based algorithms that would enable me  
> to use hardware-tuned code for Single Instruction Multiple Data  
> (SIMD) chipsets.  Currently I am researching algorithms based on  
> operating-system supplied vector libraries.  Part of this  
> modification involves a fast transformation between a vector of large  
> integers and an array of doubles, without loss of precision (although  
> vectors of doubles are also working well, they do not have the same  
> library support.)  I am also working on enhancing ARPREC's division  
> algorithm.

I looked briefly at ARPREC: It seems that it works with an explicitly set 
maximum bound on the number of digits desired. Although it also appears that 
it is possible to change this bound as computations proceed, such additional 
administration seems difficult to embrace.

> This is the problem I face: GHC unfortunately does not use Integer as  
> a mathematical operation but as a primitive type, complete with  
> bitwise operations.  

I do not understand what problem you are referring to here.

> ...
> On Aug 24, 2006, at 2:39 PM, Thorkil Naur wrote:
> > I am truly unable to tell what I would consider the ideal  
> > situation. On the
> > one hand, I value greatly the freedom of choosing my circumstances  
> > without
> > restraints. And I appreciate the desire of noble people like the GHC
> > developers to be equally free of such restraints. This would point  
> > in the
> > direction of basing Integer computations on a fairly generic  
> > implementation.
> > Which insightful consideration would then force us to admit would  
> > probably
> > cost a factor of, say, 2 or more in performance in some cases.
> The problem I face is: performance at which level?  The low-bit-size  
> (low precision) range or the high precision range?
> > Two problems seem to be lurking here: The first is that, although  
> > taking a
> > copy of some existing library and massaging it to become working  
> > under some
> > presently available circumstances may be fine, what is really  
> > needed is
> > something that keeps on working, even under changing and future
> > circumstances. The second is that if I wish to mix Haskell and non- 
> > Haskell
> > code that processes the same data, I may find myself in need of a  
> > way to
> > convert between whatever is required by this copy of some existing  
> > library
> > that has been created and the presently available, but potentially  
> > quite
> > different, version of that same library.
> Absolutely correct.  As you may have observed from your own  
> experience, there are advantages to having GHC's RunTime System (RTS)  
> handle the operations, either because garbage collected memory is  
> faster to allocate or because the natural laziness between normally  
> eager mathematical operations (mathematical operators are strict in  
> their arguments but Haskell functions wrapping those operators may be  
> lazy) means you can use GHC to strategically cache results of higher  
> level polynomial operations.  What I am working on is intended to be  
> an independent library, designed to be stable for the long-term and  
> interoperable with non-Haskell programs.  Modifying ARPREC will  
> result in a dynamic library (or DLL) that outputs simple arrays of  
> doubles--this is how ARPREC currently functions; there is no need to  
> wrap a C++ class in an opaque C pointer you cannot operate on  
> independently.  The huge downside to ARPREC and one of the least  
> trivial of my problems is how to efficiently convert an array of  
> doubles to precise integers, especially for cryptographic purposes.

Could you explain exactly what this problem is? If it still makes sense to 
spend time on it, of course.

> > I better be specific here: What I do is integer factorization  
> > (splitting
> > integers into their prime factors). And, for example, I have  
> > implemented a
> > version of the ECM (Elliptic Curve Method) in Haskell. Having done  
> > that
> > (independently and mainly to learn what it was about), I eventually  
> > compared
> > it in performance to GMP-ECM which Paul Zimmermann wrote. And I was  
> > initially
> > disappointed to find that GMP-ECM was about 7 times faster than my  
> > ECM code.
> > Fortunately, various sessions of "hand-waving" brought this factor  
> > down to
> > about 1.5. So, whenever GMP-ECM used 2 minutes, my program would use 3
> > minutes.
> One of the reasons I am working on a library written in C++ is that  
> Haskell may be too slow for such things.

I am not sure what you mean here. I would happily accept a price of, say, 10 
or 20 percent reduced performance, if I got the ease of programming (mostly) 
in Haskell in return. And I am still not convinced that, in a computation 
that involves heavy Integer work, multiplications, divisions, greatest common 
divisor computations with, say, 200-1000 digit numbers, that the Haskell 
reduced performance price could not be kept as low as this.

> ...
> My best question is: are you using pure mathematical formulas for  
> this? 

I am not sure what this question means. Please elaborate.

> GMP, LibTomMath and of course OpenSSL's BN, the pure-integer   
> based libraries, at the polynomial level mostly use modular  
> arithmetic algorithms, Karatsuba multiplication, Montgomery  or  
> Barrett reduction.  On the low-level they use bitwise operations  
> (say, right shift and add for multiplication--the gcc compiler seems  
> to use this as the default).  If you are using pure mathematical  
> functions, ARPREC is fine (at least it is precise).  Mathematically,  
> there are alternative algorithms for floating point numbers that are  
> not available for integers.  Floating point numbers may use division  
> by inverse (converting division into a multiplication operation by  
> taking the inverse of the divisor (1/d) at a low precision and using  
> addition, multiplication and subtraction until you reach the desired  
> precision.

Again, I am not sure what you mean by this. It is certainly possible to 
compute useful inverses of integers without using a floating point 
representation. To be sure, the inverse (reciprocal) of an integer is not an 
integer, but it could still be computed in a suitable fixed point 
representation. See, for example, Knuth's "Seminumerical Algorithms" ("The 
art of computer programming", vol 2), end of section 4.3.1 and algorithm R in 
section 4.3.3.

> Integer-based numbers may use Montgomery reduction (which   
> you are no doubt already familiar with.)
> > Some additional experience is this: To gain some potentially easier- 
> > to-compare
> > measurements between Haskell-with-GMP and C-with-GMP, I implemented  
> > the GMP
> > function mpz_powm (raising a number to a power modulo a third  
> > number) as a
> > primitive operation in GHC. And compared it to my own power-raising  
> > operation
> > that was implemented in Haskell.
> >
> > Overall, the GMP implementation of the power operation was 2 times  
> > faster than
> > my Haskell implememntation. But, to make sure, using some  
> > techniques that my
> > code certainly didn't use.
> >
> > An interesting thing happened, however, for very large numbers: The  
> > GMP power
> > operation started using a lot of memory, thrashing, and then,  
> > eventually,
> > with sufficiently large operands, crashed for lack of memory. Where my
> > Haskell-implemented version continued to provide usable results.
> >
> > The explanation of this should undoubtably be found in the method  
> > (some
> > FFT-thing) used for the power operation by GMP for very large  
> > numbers: My
> > guess is that GMP when using this method issues a large number of
> > allocate/free requests. And, again I am guessing, assuming that each
> > allocate request causes the GHC runtime to allocate, whereas each  
> > free only
> > causes a "we'll handle this at the next GC", then such large  
> > computations
> > may very well display such behaviour.
> Agreed.  This is one of those cases where I think Haskell's laziness  
> may have cached your intermediate results. I couldn't tell without    
> looking at the source code.  I do not think GMP uses an FFT for  
> multiplication; I have not investigated this extensively.

I still believe that the excessive memory usage was caused by GMP allocating a 
lot of storage using the GHC allocator during a single mpz_powm call, so that 
the GHC garbage collector never got the opportunity to actually free the 
allocated storage. The mpz_powm function of gmp-4.2 that I am looking at 
calls mpn_sqr_n inside its main loop and that function itself allocates 
storage in some cases. In other cases, it calls mpn_mul_fft_full which, 
again, allocates storage.

> -Pete
> ...

Elsewhere (, 
you wrote:
> ...
> I have  been working on an arbitrary precision integer library from scratch 
> but I have a version of GHC without any GMP in it right now 
> ...

so it may very well be that some of the above remarks are not relevant any 
more. In addition, you wrote 

> ...
> Integers are mostly a convenience.
> ...

I fully agree with that. Therefore, as I outline what I now consider the ideal 
situation with respect to Haskell and Integer support, keep in mind that I 
have very specific desires that may not be shared by any other Haskell users 
and which should be prioritized accordingly.

My highest priority is still correctness. The thought that there might, in 
some special case, have crept an error into my basic arithmetic operators
makes me shudder. In the present case of multiprecision integer operations, 
what worries me is the complications and the many special cases that 
implementors deal with. And not least special cases that address some 
particular architecture: This opens the possibility that code working on one 
machine stops working on another.

It seems that my level of tolerance of such risks is not as high as most other 
people's. Personally, if I were to write a multiprecision library myself, I 
would build in all sorts of possibilities for verifying that everything 
remains in place and results computed were indeed correct.

For example, I understand that memory errors occur, not often, but often 
enough to be significant for such longwinding computations (days, months, 
years) that I envision. Other people are doing long computations (e.g. and they exercise some care in the form of test 
programs that are intended to verify that a particular machine is 
sufficiently stable. I would probably carry this even further, by including, 
even in "production" runs, various checks (individual arithmetic operations 
verified by checks modulo some number, say).

Another thing that bothers me is the unpredictability of the performance of 
these multiprecision packages. When I look at the GMP code, there are 
countless special cases that are taken into account. Each of them, of course, 
represents the implementor's desire to provide me, the user, with the best 
service (performance) possible. But it makes measuring performance a 
nightmare, because you never know, for sure, whether some special case has 
been taken into account in a particular situation.

This was brought home to me most forcefully some months back: I have 
GHC-compiled programs running on a couple of Linux machines (not the same 
Linux distribution), but had used some precompiled GMP package on both of 
these machines. After looking at the GMP code, I realised (as I should have 
much earlier, of course) that I should use a package that was compiled for 
the specific machine I was working on to ensure that all the various special 
performance tricks could come into play. So I did. And performance improved 
on one machine, but degraded on another. I am still not sure what the 
explanation of this behaviour really is. But one thing I am certainly 
concerned about is whether, in fact, the GMP build process is actually able 
to guess some machine architecture correctly and ensure that its potential is 
used to the fullest extent.

One thing that seems missing in many packages is the possibility to experiment 
with the tuning parameters more dynamically. For example, GMP includes a 
tuning program that is able to suggest values for certain parameters. But 
these parameters then have to be compiled into the package for final use. Not 
particularly elegant and something which, surely, could be changed, without 
significant overhead.

The conclusion, for me at least, is this: If you are a naive user of Integers, 
you are much better off with a completely simple, no-special-cases, solid and 
working, multiprecision package. Which might even be fairly portable.

For heavy users (such as me) with special requirements, it seems that they can 
never get the ultimate performance without having considerable insight into 
the details of the matter anyway. And, hence, they need to do some complex 
work themselves to get the desired performance.

For Haskell, this would mean ensuring that the interface between GHC-compiled 
code and some multiprecision package was reasonably comprehensible and 
allowed the multiprecision support to be replaced. And here I try to avoid 
the usual dream of "easy replacement", that seems entirely unrealistic. But 
something reasonable that the rest of GHC (and not least: The GHC developers) 
can live with without too much work. And that heavy users, with sufficient 
insight and probably a significant dose of hard work (such as that you have 
put into this) would be able to use for their own purposes.

Best regards

More information about the Glasgow-haskell-users mailing list