From d99ac7adb4bbffab6fa5dd50c01670453670c1d1 Mon Sep 17 00:00:00 2001 From: Jeongseop Lim Date: Fri, 30 Oct 2026 03:07:42 +0900 Subject: [PATCH] Speed up math.perm() for large k calculatePerm multiplied the k factors one at a time into a growing product, so each step was a big-by-small multiplication and the whole loop was quadratic in the result size. math.perm(615913, 286885) took about 28 s on a JVM build against about 1 s on CPython. Splitting the range in halves keeps the operands of each multiplication balanced, so BigInteger can use its faster algorithms; the same call now takes 0.3 s. Signed-off-by: Jeongseop Lim --- .../src/tests/test_math_perm.py | 51 +++++++++++++++++++ .../builtins/modules/MathModuleBuiltins.java | 17 +++---- 2 files changed, 58 insertions(+), 10 deletions(-) create mode 100644 graalpython/com.oracle.graal.python.test/src/tests/test_math_perm.py diff --git a/graalpython/com.oracle.graal.python.test/src/tests/test_math_perm.py b/graalpython/com.oracle.graal.python.test/src/tests/test_math_perm.py new file mode 100644 index 0000000000..0edf8d928a --- /dev/null +++ b/graalpython/com.oracle.graal.python.test/src/tests/test_math_perm.py @@ -0,0 +1,51 @@ +# Copyright (c) 2026, Oracle and/or its affiliates. All rights reserved. +# DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. +# +# The Universal Permissive License (UPL), Version 1.0 +# +# Subject to the condition set forth below, permission is hereby granted to any +# person obtaining a copy of this software, associated documentation and/or +# data (collectively the "Software"), free of charge and under any and all +# copyright rights in the Software, and any and all patent rights owned or +# freely licensable by each licensor hereunder covering either (i) the +# unmodified Software as contributed to or provided by such licensor, or (ii) +# the Larger Works (as defined below), to deal in both +# +# (a) the Software, and +# +# (b) any piece of software and/or hardware listed in the lrgrwrks.txt file if +# one is included with the Software each a "Larger Work" to which the Software +# is contributed by such licensors), +# +# without restriction, including without limitation the rights to copy, create +# derivative works of, display, perform, and distribute the Software and make, +# use, sell, offer for sale, import, export, have made, and have sold the +# Software and the Larger Work(s), and to sublicense the foregoing rights on +# either these or other terms. +# +# This license is subject to the following condition: +# +# The above copyright notice and either this complete permission notice or at a +# minimum a reference to the UPL must be included in all copies or substantial +# portions of the Software. +# +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +# SOFTWARE. + +import math + + +def test_perm_matches_product(): + assert math.perm(10, 0) == 1 + assert math.perm(10, 1) == 10 + assert math.perm(10, 2) == 90 + assert math.perm(10, 3) == 720 + assert math.perm(10, 7) == 604800 + assert math.perm(10, 10) == 3628800 + assert math.perm(10, 11) == 0 + assert math.perm(10**20, 3) == 999999999999999999970000000000000000000200000000000000000000 diff --git a/graalpython/com.oracle.graal.python/src/com/oracle/graal/python/builtins/modules/MathModuleBuiltins.java b/graalpython/com.oracle.graal.python/src/com/oracle/graal/python/builtins/modules/MathModuleBuiltins.java index 029f6ac3cf..8fb09debf7 100644 --- a/graalpython/com.oracle.graal.python/src/com/oracle/graal/python/builtins/modules/MathModuleBuiltins.java +++ b/graalpython/com.oracle.graal.python/src/com/oracle/graal/python/builtins/modules/MathModuleBuiltins.java @@ -533,19 +533,16 @@ private BigInteger calculatePerm(BigInteger n, BigInteger k) { if (k.equals(BigInteger.ZERO)) { return BigInteger.ONE; } + return permProduct(n, k); + } + + // P(n, k) = P(n, j) * P(n - j, k - j) keeps the operands balanced. + private static BigInteger permProduct(BigInteger n, BigInteger k) { if (k.equals(BigInteger.ONE)) { return n; } - - BigInteger result = n; - BigInteger factor = n; - BigInteger i = BigInteger.ONE; - while (i.compareTo(k) < 0) { - factor = factor.subtract(BigInteger.ONE); - result = result.multiply(factor); - i = i.add(BigInteger.ONE); - } - return result; + BigInteger j = k.shiftRight(1); + return permProduct(n, j).multiply(permProduct(n.subtract(j), k.subtract(j))); } @Specialization