view tests/test-import-unknown.t @ 26457:7e81305092a0

demandimport: replace more references to _demandmod instances _demandmod instances may be referenced by multiple importing modules. Before this patch, the _demandmod instance only maintained a reference to its first consumer when using the "from X import Y" syntax. This is because we only created a single _demandmod instance (attached to the parent X module). If multiple modules A and B performed "from X import Y", we'd produce a single _demandmod instance "demandmod" with the following references: X.Y = <demandmod> A.Y = <demandmod> B.Y = <demandmod> The locals from the first consumer (A) would be stored in <demandmod1>. When <demandmod1> was loaded, we'd look at the locals for the first consumer and replace the symbol, if necessary. This resulted in state: X.Y = <module> A.Y = <module> B.Y = <demandmod> B's reference to Y wasn't updated and was still using the proxy object because we just didn't record that B had a reference to <demandmod> that needed updating! With this patch, we add support for tracking which modules in addition to the initial importer have a reference to the _demandmod instance and we replace those references at module load time. In the case of posix.py, this fixes an issue where the "encoding" module was being proxied, resulting in hundreds of thousands of __getattribute__ lookups on the _demandmod instance during dirstate operations on mozilla-central, speeding up execution by many milliseconds. There are likely several other operation that benefit from this change as well. The new mechanism isn't perfect: references in locals (not globals) may likely linger. So, if there is an import inside a function and a symbol from that module is used in a hot loop, we could have unwanted overhead from proxying through _demandmod. Non-global imports are discouraged anyway. So hopefully this isn't a big deal in practice. We could potentially deploy a code checker that bans use of attribute lookups of function-level-imported modules inside loops. This deficiency in theory could be avoided by storing the set of globals and locals dicts to update in the _demandmod instance. However, I tried this and it didn't work. One reason is that some globals are _demandmod instances. We could work around this, but it's a bit more work. There also might be other module import foo at play. The solution as implemented is better than what we had and IMO is good enough for the time being. It's worth noting that this sub-optimal behavior was made worse by the introduction of absolute_import and its recommended "from . import X" syntax for importing modules from the "mercurial" package. If we ever wrote performance tests, measuring the amount of module imports and __getattribute__ proxy calls through _demandmod instances would be something I'd have it check.
author Gregory Szorc <gregory.szorc@gmail.com>
date Sun, 04 Oct 2015 11:17:43 -0700
parents f2719b387380
children 6a454e7053a1
line wrap: on
line source

  $ cat <<EOF >> $HGRCPATH
  > [extensions]
  > purge =
  > EOF

  $ hg init test
  $ cd test
  $ echo a > changed
  $ echo a > removed
  $ echo a > source
  $ hg ci -Am addfiles
  adding changed
  adding removed
  adding source
  $ echo a >> changed
  $ echo a > added
  $ hg add added
  $ hg rm removed
  $ hg cp source copied
  $ hg diff --git > ../unknown.diff

Test adding on top of an unknown file

  $ hg up -qC 0
  $ hg purge
  $ echo a > added
  $ hg import --no-commit ../unknown.diff
  applying ../unknown.diff
  file added already exists
  1 out of 1 hunks FAILED -- saving rejects to file added.rej
  abort: patch failed to apply
  [255]

Test modifying an unknown file

  $ hg revert -aq
  $ hg purge
  $ hg rm changed
  $ hg ci -m removechanged
  $ echo a > changed
  $ hg import --no-commit ../unknown.diff
  applying ../unknown.diff
  abort: cannot patch changed: file is not tracked
  [255]

Test removing an unknown file

  $ hg up -qC 0
  $ hg purge
  $ hg rm removed
  $ hg ci -m removeremoved
  created new head
  $ echo a > removed
  $ hg import --no-commit ../unknown.diff
  applying ../unknown.diff
  abort: cannot patch removed: file is not tracked
  [255]

Test copying onto an unknown file

  $ hg up -qC 0
  $ hg purge
  $ echo a > copied
  $ hg import --no-commit ../unknown.diff
  applying ../unknown.diff
  abort: cannot create copied: destination already exists
  [255]

  $ cd ..