-
-
Notifications
You must be signed in to change notification settings - Fork 30.8k
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
gh-90370: Avoid temporary varargs
tuple creation in argument passing
#30312
Conversation
Hello, and thanks for your contribution! I'm a bot set up to make sure that the project can legally accept this contribution by verifying everyone involved has signed the PSF contributor agreement (CLA). CLA MissingOur records indicate the following people have not signed the CLA: For legal reasons we need all the people listed to sign the CLA before we can look at your contribution. Please follow the steps outlined in the CPython devguide to rectify this issue. If you have recently signed the CLA, please wait at least one business day You can check yourself to see if the CLA has been received. Thanks again for the contribution, we look forward to reviewing it! |
…keep backward compatibility
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Just a couple of comments here.
I wonder if it would be simpler to restrict the scope of such a change to only signatures like def f(*args, kw1=<default1>, kw2=<default2>, ...)
, like print()/min()/max()
have.
Lib/test/clinic.test
Outdated
@@ -3341,16 +3342,16 @@ test_vararg_and_posonly(PyObject *module, PyObject *const *args, Py_ssize_t narg | |||
for (Py_ssize_t i = 0; i < nargs - 1; ++i) { | |||
PyTuple_SET_ITEM(__clinic_args, i, args[1 + i]); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This seems suspicious -- isn't __clinic_args a pointer-to-PyObject-pointer, not a pointer-to-tuple?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
My fault. The tuple doesn't need to be created.
Lib/test/clinic.test
Outdated
if (!args) { | ||
goto exit; | ||
} | ||
a = args[0]; | ||
__clinic_args = args[1]; | ||
return_value = test_vararg_impl(module, a, __clinic_args); | ||
__clinic_args = (PyObject *const *)args[1]; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It would be nice to avoid such unsafe casts. Maybe passing a PyObject ***
into _PyArg_UnpackKeywordsWithVarargFast
instead?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This copy and cast are unnecessary.
Maybe __clinic_args = args + 1;
is enough, note the args
refers to the 2nd argument of function test_vararg, assuming it's not assigned in line 3389.
Python/getargs.c
Outdated
buf[vararg] = (PyObject *)&args[vararg]; | ||
|
||
/* copy required positional args */ | ||
for (i = 0; i < vararg; i++) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Isn't it possible to avoid copying at all by directly passing some sub-buffer of the *args
buffer into the ..._impl
function?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
You are right. None of the positional args or variable-length args need to be copied. I will let _PyArg_UnpackKeywordsWithVarargFast parse default and keyword args only.
Thanks for the PR and benchmarks @colorfulappl, I am current blocked a bit but plan to review this soon! |
Thank you @isidentical ! |
Sure, then just ping me whenever the new patch ready and I'll try to take a look at it then! |
# Conflicts: # Python/clinic/bltinmodule.c.h
Sorry for such a long delay. When generate parsing code for function with varargs, this PR:
I'd really appreciate it if you could take a look and give me some advice @isidentical . |
Benchmark Result Benchmark: Environment:
|
I found there are some bugs when argument clinic processes varargs and made a fix in #32092. |
This comment was marked as outdated.
This comment was marked as outdated.
Thanks for the advice; I will pay attention to it. I added some test cases for the class method e.g. # Signature of this function:
#
# @classmethod
# _testclinic.TestClassVarargOnly.__new__(cls, *args)
test_class_new_vararg_only(kw=1)
# Here we expect an error like "TypeError: TestClassVarargOnly.__new__() got an unexpected keyword argument 'kw'",
# but nothing raised. We may need to fix it in another issue. |
This comment was marked as outdated.
This comment was marked as outdated.
This comment was marked as outdated.
This comment was marked as outdated.
Tools/clinic/clinic.py
Outdated
max_pos | ||
), indent=4)) | ||
p.converter.parser_name, | ||
p.name, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I would expect here p.converter.name
instead.
I'm trying your patch for converting some vararg functions in the math module (see the old conversion attempt), and it seems that without this change autogenerated code is broken, because it references a Python parameter name instead of C (i.e., coordinates
, instead of args
):
static PyObject *
math_hypot_impl(PyObject *module, Py_ssize_t varargssize,
PyObject *const *args);
static PyObject *
math_hypot(PyObject *module, PyObject *const *args, Py_ssize_t nargs)
{
PyObject *return_value = NULL;
Py_ssize_t varargssize = nargs;
PyObject *const *__clinic_args;
if (!_PyArg_CheckPositional("hypot", nargs, 0, PY_SSIZE_T_MAX)) {
goto exit;
}
__clinic_args = coordinates + 0;
return_value = math_hypot_impl(module, varargssize, __clinic_args);
exit:
return return_value;
}
See docs. BTW, I think there should be a test for that.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Hardcoding "args" will work too, but I'm not sure it's a good idea.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thanks for the advice.
When the function signature is METH_FASTCALL
, the pass-in parameter is always named args
, so replacing p.name
with args
works.
I made a fix and added two tests. It looks correct now.
def test_vararg_with_default_with_other_name(self): | ||
with self.assertRaises(TypeError): | ||
ac_tester.vararg_with_default_with_other_name() | ||
self.assertEqual(ac_tester.vararg_with_default_with_other_name(1, b=False), (1, (), False)) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
You could add an exception test with _b
.
Not sure, if this is a good idea, but could you consider using nargs instead of varargsize for *_impl() functions? This will reduce needs for changing the body of converted function (take math_gcd as an example to convert from scratch). |
This patch intends to reduce the overhead on passing Before this patch,
@skirpichev Do you mean pass both
|
Indeed, my suggestion only valid for only-vararg case (as gcd, lcm and so on). Not sure if this case worth a special treatment... |
varargs
tuple creation in argument passingvarargs
tuple creation in argument passing
@colorfulappl, could you fix merge conflicts? |
Thanks for your work, but this has merge conflicts. Also, issue seems to be fixed. I'm closing this. |
When "Augument Clinic generated code" are parsing arguments, the args are packed to a tuple before passing to callee. This may be unnecessary.
Pass a raw pointer which points to on-stack varargs, and a varargssize integer to indicate how many varargs are passed, can save the time of tuple creation/destruction and value copy.
varargs
tuple creation in argument passing #90370