Asm code selection made hard

Torbjörn Granlund tg at gmplib.org
Sat Sep 5 15:55:38 CEST 2026


The most important reason for why GMP is fast is its highly optimised
asm code and that GMP provides many variants of the asmcode for
different CPUs within an ISA.

Choosing the right asm code is not easy, the GMP developers need to be
able to access every CPU family for running timing tests, and then
either write new asm loops or (at a minimum) choose the best already
existing asm loops.

When new CPUs come out, it usually takes more than a year before we get
CPU access, find time to optimize for that CPU, and make a GMP release.

Currently, we're lagging behing several X86 CPU generations.

Right, to let's address that then!

A new problem has emerged for Intel CPUs.  Most of them come with
performance, or "P" cores and efficiency, or "E".  For example, an
"Intel Core Ultra 5 235" has "Lion Cove" performance cores and "Skymont"
efficiency cores.  To my surprise, these cores have VERY different
characteristics.

For example, most of our mpn_add_n implementations runs at 1 cycle/limb
on the P cores, but vary from 1 to 5 cycle/limb on the E cores.

Ok, so for that CPU we can let the E core choose its favourite
mpn_add_n.  Great!

For mpn_addlsh1_n the worst asm loop need a whopping 12 cycles/limb on E
cores, and 1 cycle/limb on P cores.  Fortunately, here too the P cores
run all mpn_addlsh1_n code well.

For the really important mpn_mul_1, mpn_addmul_1, mpn_mul_2,
mpn_addmul_2 (and thereby mpn_mul_basecase, mpn_sqr_basecase) it is
getting uglier.

The best mpn_addmul_1 code for the P cores is coreibwl/addmul_1.asm; it
runs at 1 cycle/limb.  But that code runs at 3 cycles/limb on the E
cores.  The best code for the E cores is zen/addmul_1.asm, which runs at
about 2.25 cycles/limb.  However, zen/addmul_1.asm runs at about 1.65
cycles/limb on the P cores.  There seem to be no good compromise.

The best compromise seems to be to choose coreihwl/addmul_1 and get 25%
worse P core performance and 10% worse E core performance.

For mpn_submul_1, it is even trickier.


It is quite surprising that Intel went down this route.  It is
impossible to optimise any code for these CPUs.  In certain cases, there
is a 10x slowdown from going from P core to an E core, if one stumbles
over extra slow E core instructions.


I am at loss what to do in GMP.  Should we,

1. choose the mpn code vectors as if the E cores don't exist (and then
get perhaps 2x extra performance penalty there, in addition to their
lower frequency), or

2. choose some compromise code vectors which runs sub-optimally on both
P and E cores.

We could also allow for core names like "skymont" and "lioncove" and
let interested users use command like tasksel to run the optimal code on
each core type.  I suppose this is fairly straightforward.

Most Intel CPUs have more E cores than P cores, so one alternative would
be to choose code vectors for total CPU throughput.  But that means that
we need much more complicated CPU recognition where the P and E core
countss become part of the configuration tripple.

My next question is: How about fat builds?  :-)

-- 
Torbjörn
Please encrypt, key id 0xC8601622


More information about the gmp-devel mailing list