Repository: sympy/sympy (Python)

Issue:
ccode excessively slow when using user_functions
I am using `ccode` with `user_functions`. This is very slow. This code

```Python
from sympy import *
expr = Symbol('a')
for i in range(0, 18):
    expr = Function('muladd')(Rational(1, 2), Symbol(f'a{i}'), expr)

expr
ccode(expr, user_functions={'muladd':'muladd'})
```
takes more than 10 seconds to run. When I increase the `18` to `20`, the time grows exponentially. In my actual application I have to wait for many minutes for `ccode` to finish.

The issue goes away when I use only built-in functions.

I am aware of https://github.com/sympy/sympy/issues/15377. The issue here seems similar. I am using SymPy 1.10.1 with Python 3.9.12, both installed via Anaconda, on macOS.



Discussion:
Thank you for reporting this, and thanks for the reproducer, it's very helpful. I'm looking at the output of `python -m cProfile` through `python -m pstats` right now and I'm trying to figure out why this is causes an exponential runtime.

A work-around for your example in the meantime would be:
`ccode(expr, allow_unknown_functions=True)`
which does not suffer from the exponential runtime, not sure if it helps for your full problem though.
Thank you for the quick fix (which I haven't tried yet), and for the pointer to `allow_unknown_functions=True` (which works for me).
I spoke too soon. The `allow_unknown_functions=True` work-around does not work for me. (It doesn't properly output the arguments to `muladd`; there are e.g. terms such as `AtildeUD00**2` in the (supposed) C output.) Time to update `SymPy`.
The fix is only on SymPy's master branch so you'd need to install from git.
> I spoke too soon. The `allow_unknown_functions=True` work-around does not work for me. (It doesn't properly output the arguments to `muladd`; there are e.g. terms such as `AtildeUD00**2` in the (supposed) C output.) Time to update `SymPy`.

Oh, that doesn't sound right. Would you mind opening a separate issue showcasing the `**2` in the output from ccode?
I am successfully using a git checkout.

I cannot reproduce the problem with `allow_unknown_functions=True`.

Thank you very much again!



Patch:
diff --git a/sympy/printing/codeprinter.py b/sympy/printing/codeprinter.py
index ce9c7b623df9..1523552ba30a 100644
--- a/sympy/printing/codeprinter.py
+++ b/sympy/printing/codeprinter.py
@@ -427,18 +427,17 @@ def _can_print(self, name):
     def _print_Function(self, expr):
         if expr.func.__name__ in self.known_functions:
             cond_func = self.known_functions[expr.func.__name__]
-            func = None
             if isinstance(cond_func, str):
-                func = cond_func
+                return "%s(%s)" % (cond_func, self.stringify(expr.args, ", "))
             else:
                 for cond, func in cond_func:
                     if cond(*expr.args):
                         break
-            if func is not None:
-                try:
-                    return func(*[self.parenthesize(item, 0) for item in expr.args])
-                except TypeError:
-                    return "%s(%s)" % (func, self.stringify(expr.args, ", "))
+                if func is not None:
+                    try:
+                        return func(*[self.parenthesize(item, 0) for item in expr.args])
+                    except TypeError:
+                        return "%s(%s)" % (func, self.stringify(expr.args, ", "))
         elif hasattr(expr, '_imp_') and isinstance(expr._imp_, Lambda):
             # inlined function
             return self._print(expr._imp_(*expr.args))


Test patch:
diff --git a/sympy/printing/tests/test_c.py b/sympy/printing/tests/test_c.py
index 33691e15ab7d..f724b12f2693 100644
--- a/sympy/printing/tests/test_c.py
+++ b/sympy/printing/tests/test_c.py
@@ -1,5 +1,5 @@
 from sympy.core import (
-    S, pi, oo, symbols, Rational, Integer, Float, Mod, GoldenRatio, EulerGamma, Catalan,
+    S, pi, oo, Symbol, symbols, Rational, Integer, Float, Function, Mod, GoldenRatio, EulerGamma, Catalan,
     Lambda, Dummy, nan, Mul, Pow, UnevaluatedExpr
 )
 from sympy.core.relational import (Eq, Ge, Gt, Le, Lt, Ne)
@@ -180,6 +180,15 @@ def test_ccode_user_functions():
     assert ccode(Abs(x), user_functions=custom_functions) == "fabs(x)"
     assert ccode(Abs(n), user_functions=custom_functions) == "abs(n)"
 
+    expr = Symbol('a')
+    muladd = Function('muladd')
+    for i in range(0, 100):
+        # the large number of terms acts as a regression test for gh-23839
+        expr = muladd(Rational(1, 2), Symbol(f'a{i}'), expr)
+    out = ccode(expr, user_functions={'muladd':'muladd'})
+    assert 'a99' in out
+    assert out.count('muladd') == 100
+
 
 def test_ccode_boolean():
     assert ccode(True) == "true"