-
Notifications
You must be signed in to change notification settings - Fork 176
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Simplify the codebase by avoiding metaprogramming #696
Comments
That seems reasonable. However,
Are you suggest ctx attribute as an instance property? I think we could use (Another issue with
I doubt that the second is something wrong. Why do you think so?
See #702. |
Effectively it already is an instance attribute but it is implemented by having a dynamically created class with a class attribute. I'm suggesting not to have the dynamically generated class and just have ctx as an instance attribute. Currently it is like this: class _mpf:
def __init__(self, value):
self.value = value
def __repr__(self):
return "mpf(%s) (%s)" % (self.value, self.ctx)
class Context:
def __init__(self, name):
self.name = name
self.mpf_type = type("mpf", (_mpf,), {})
self.mpf_type.ctx = self
def mpf(self, value):
return self.mpf_type(value)
def __repr__(self):
return "Context(%s)" % self.name
ctx1 = Context("default")
ctx2 = Context("other")
a = ctx1.mpf(1)
b = ctx2.mpf(2)
print("a =", a)
print("b =", b) I would instead write it like: class mpf:
def __init__(self, value, ctx):
self.value = value
self.ctx = ctx
def __repr__(self):
return "mpf(%s) (%s)" % (self.value, self.ctx)
class Context:
def __init__(self, name):
self.name = name
def mpf(self, value):
return mpf(value, self)
def __repr__(self):
return "Context(%s)" % self.name
ctx1 = Context("default")
ctx2 = Context("other")
a = ctx1.mpf(1)
b = ctx2.mpf(2)
print("a =", a)
print("b =", b) |
One problem with the decorator approach as currently implemented is that it is slow when creating a context object (see the profile above). Another problem is just that it makes the codebase complicated to navigate and understand. Many different files and functions need to be inspected just to understand exactly how one function calls another. Usually I would follow things like that by jumping from one function to another in my editor using "go to definition" (gd in vim) but that fails as does all static analysis like type checking code completion etc when the methods are assigned dynamically. |
I agree with this. I often find it hard to figure out where stuff is because of this. Especially if the only point of the metaprogramming is to avoid some code duplication, it would be better to either use standard methods of avoiding duplication (like factoring things into functions or subclasses), or to just duplicate the code. |
Another example is multiple pickling issues with the current code base.
On another hand, this is a huge change in semantics: right now we have different mpf types per context. You suggest instead something like gmpy2.mpfr. I did some experiments in https://github.com/skirpichev/mpmath/tree/simple-mpf branch. Not sure it worth a draft pr, let me know. But I'm surprised there are only ~20 test failures with a decent part of this - broken |
Is that a huge change in semantics? Do you think that code outside of mpmath really relies on these dynamic types? Probably something in SymPy would break but that is just because SymPy reaches into the internals of mpmath too much.
The commit looks good to me.
I'm not so surprised because the dynamically created classes do not really achieve anything in terms of behaviour. |
Maybe. Don't get me wrong, I like this idea. In a long term, with this, probably we even could use (optionally) gmpy's mpfr/mpc instead of mpmath's mpf/mpc. But this will be not a backward compatible change.
Lets test that: diofant/diofant#1356 (I think the situation will be more or less same for the SymPy). UPD: And yes, it does.
WIP: #719
There are other issues (e.g. pickling), which aren't simple to fix. |
Ok, now there are only pickling-related failures. |
Now pickling works. |
Following on from #683 (comment) concerning the speed of creating a context object and see the past discussions in gh-178.
The mpmath codebase uses a number of metaprogramming features that increase complexity for what I suspect is relatively little benefit (mostly DRY and micro-optimisation). These make the codebase hard to follow and also defeat static code analysis which is increasingly integrated in modern Python editors. I propose to remove these and move towards ordinary Python code with plain functions, methods and attributes.
Specifically I suggest:
_mpf_
andctx
attributes.One example of this is dynamically created classes like:
mpmath/mpmath/ctx_mp_python.py
Lines 584 to 596 in 29b47ce
There are also functions and methods created from code strings at runtime:
mpmath/mpmath/ctx_mp_python.py
Lines 280 to 293 in 29b47ce
We also have methods that are dynamically added to classes:
mpmath/mpmath/functions/functions.py
Lines 16 to 20 in 29b47ce
Some of these happen at import time and others happen when a context object is created and add measurable overhead to the cost of creating a context:
Ideally the cost of creating a new context object would be small enough that it could be worthwhile to create one just for the purposes of a single function call. The context object only has a handful of genuine attributes so it should not be this expensive to create:
I think that it would be better to rewrite these things using ordinary Python code with a primary emphasis on keeping the code simple and easy to understand and maintain. Future versions of CPython will also boost the performance of much Python code but mpmath will potentially not be able to benefit as much from these optimisations because they are generally targeted at simple straight-forward code.
With a simpler codebase it would also be easier to aim for bigger performance improvements by making it easier to swap different backends in and out and to leverage e.g. python_flint if available. The small performance benefits that are achieved by this metaprogramming could be made a lot greater just by having an optional C/Cython backend at least for basic types like mpf but it any such backend in a non dynamically typed language would not be able to replicate the metaprogramming. The python_flint module could house a faster version of mpf and MPContext if it was to be used as an optional backend for mpmath.
The text was updated successfully, but these errors were encountered: