Skip to content

bpo-27535: Fix memory leak with warnings ignore #4489

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

Merged
merged 1 commit into from
Nov 27, 2017
Merged

bpo-27535: Fix memory leak with warnings ignore #4489

merged 1 commit into from
Nov 27, 2017

Conversation

vstinner
Copy link
Member

@vstinner vstinner commented Nov 21, 2017

The warnings module doesn't leak memory anymore in the hidden
warnings registry for the "ignore" action of warnings filters.

The warn_explicit() function doesn't add the warning key to the
registry anymore for the "ignore" action.

https://bugs.python.org/issue27535

Copy link
Member

@serhiy-storchaka serhiy-storchaka left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I want to play with the code more.

else if (_PyUnicode_EqualToASCIIString(action, "ignore"))
}

if (_PyUnicode_EqualToASCIIString(action, "ignore"))
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Isn't this already tested above?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I removed it. I kept it for tests, but then I forgot to remove it.

/* Store in the registry that we've been here, *except* when the action
is "always". */
rc = 0;
if (!_PyUnicode_EqualToASCIIString(action, "always")) {
if (registry != NULL && registry != Py_None &&
PyDict_SetItem(registry, key, Py_True) < 0)
PyDict_SetItem(registry, key, Py_True) < 0) {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If you want to add braces, place the opening brace after a multiline condition on a separate line.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The PEP 7 only shows "if (test) {", it says nothing about multiline condition. Until a newline is required by the PEP 7, I prefer to put "{" on the same line.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

* When you break a long expression at a binary operator, the
  operator goes at the end of the previous line, and braces should be
  formatted as shown.  E.g.::

      if (type->tp_dictoffset != 0 && base->tp_dictoffset == 0 &&
          type->tp_dictoffset == b_size &&
          (size_t)t_size == b_size + sizeof(PyObject *))
      {
          return 0; /* "Forgive" adding a __dict__ only */
      }

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Oh nice, it's specified in the PEP 7. In that case, ok to reformat the code as the PEP 7 requires ;-)

Lib/warnings.py Outdated
re.compile(module), lineno, append=append)

if message:
regex = re.compile(message, re.I)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This could be written shorter.

regex = re.compile(message, re.I) if message else None
module = re.compile(module) if module else None
_add_filter(action, regex, category, module, lineno, append=append)

@@ -125,6 +125,8 @@ def test_ignore(self):
self.module.filterwarnings("ignore", category=UserWarning)
self.module.warn("FilterTests.test_ignore", UserWarning)
self.assertEqual(len(w), 0)
self.assertEqual(list(__warningregistry__.keys()),
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This line doesn't look too long for splitting it. Especially if remove redundant .keys().

@vstinner
Copy link
Member Author

Ooops, previously, I pushed optimization commits in this PR by mistake. It's now fixed. Again, this PR is only the change to leave the registry unchanged for the "ignore" action.

The warnings module doesn't leak memory anymore in the hidden
warnings registry for the "ignore" action of warnings filters.

The warn_explicit() function doesn't add the warning key to the
registry anymore for the "ignore" action.
@vstinner
Copy link
Member Author

I reformatted the if with multiline condition and rebased my PR.

@vstinner
Copy link
Member Author

Latest reliable (using isolated CPUs) benchmark:

vstinner@apu$ ./python -m perf compare_to master.json ignore.json 
Mean +- std dev: [master] 705 ns +- 24 ns -> [ignore] 838 ns +- 18 ns: 1.19x slower (+19%)

So this PR adds a slowdown of +133 ns on warning.warn() when the warning is ignored.

This slowdown can be removed using a "C cache" of warnings.filters: the optimization implemented in PR #4502. The problem is that I'm not sure that it's ok to cache warnings.filters and/or make warnings.filters "immutable" (convert it to a tuple).

@serhiy-storchaka
Copy link
Member

The overhead depends on the number and kind of filters. For warnings emitted in a C code the relative slowdown will be larger.

@vstinner
Copy link
Member Author

@serhiy-storchaka: Are you ok to merge this PR even with the "1.2x slower" (+19%)? Or do you prefer to wait until a decision can be made on a "C cache" for warnings.filters?

@serhiy-storchaka
Copy link
Member

Could you please make a benchmark for warnings emitted in a tight loop in the C code. It would be nice to know what is the worst case. If it is less than 2x slower I will prefer this simpler PR.

@vstinner
Copy link
Member Author

Could you please make a benchmark for warnings emitted in a tight loop in the C code. It would be nice to know what is the worst case. If it is less than 2x slower I will prefer this simpler PR.

It's not 2x slower, it's between 1.03x slower (+21 ns) and 1.09x slower (+79 ns):
https://bugs.python.org/issue27535#msg306919

@vstinner vstinner merged commit c975878 into python:master Nov 27, 2017
@vstinner vstinner deleted the warn_ignore branch November 27, 2017 15:57
@miss-islington
Copy link
Contributor

Thanks @vstinner for the PR 🌮🎉.. I'm working now to backport this PR to: 3.6.
🐍🍒⛏🤖

miss-islington pushed a commit to miss-islington/cpython that referenced this pull request Nov 27, 2017
The warnings module doesn't leak memory anymore in the hidden
warnings registry for the "ignore" action of warnings filters.

The warn_explicit() function doesn't add the warning key to the
registry anymore for the "ignore" action.
(cherry picked from commit c975878)
@bedevere-bot
Copy link

GH-4587 is a backport of this pull request to the 3.6 branch.

@vstinner
Copy link
Member Author

I backported manually the change to Python 2.7: PR #4588.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

5 participants