Thanks to visit codestin.com
Credit goes to github.com

Skip to content

Commit 737c80e

Browse files
committed
Reuse single kpsewhich instance for speed.
On MacOS, where spawing kpsewhich instances is rather slow, this appears to speed up ``` python -c 'from pylab import *; mpl.use("pdf"); rcParams["text.usetex"] = True; plot(); savefig("/tmp/test.pdf", backend="pdf")' ``` around two-fold (~4s to ~2s). (There's also a small speedup on Linux, perhaps ~10%, but the whole thing is already reasonably fast.) Note that this is assuming that the dvi cache has already been built; the costly subprocess calls here are due to calls to kpsewhich to resolve the fonts whose name are listed in the dvi file. Much of the complexity here comes from the need to force unbuffered stdin/stdout when interacting with kpsewhich (otherwise, things just hang); this is also the reason why this is not implemented on Windows (Windows experts are welcome to look into this...; there, the speedup should be even more significant). (On Linux, another solution, which does not require a third-party dependency, is to call `stdbuf -oL kpsewhich ...` and pass bufsize=0 to Popen(), but `ptyprocess` is pure Python so adding a dependency seems reasonable). The `format` kwarg to `find_tex_file` had never been used before, and cannot be handled in the single-process case, so just deprecate it.
1 parent 01d3149 commit 737c80e

File tree

4 files changed

+77
-19
lines changed

4 files changed

+77
-19
lines changed

.github/workflows/tests.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -141,7 +141,7 @@ jobs:
141141
142142
# Install dependencies from PyPI.
143143
python -mpip install --upgrade $PRE \
144-
cycler kiwisolver numpy pillow pyparsing python-dateutil \
144+
cycler kiwisolver numpy pillow ptyprocess pyparsing python-dateutil \
145145
-r requirements/testing/all.txt \
146146
${{ matrix.extra-requirements }}
147147
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
The *format* kwarg to ``dviread.find_tex_file``
2+
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
3+
... is deprecated.

lib/matplotlib/dviread.py

Lines changed: 72 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -1040,7 +1040,50 @@ def _parse_enc(path):
10401040
"Failed to parse {} as Postscript encoding".format(path))
10411041

10421042

1043+
class _Kpsewhich:
1044+
@lru_cache() # A singleton.
1045+
def __new__(cls):
1046+
self = object.__new__(cls)
1047+
self._proc = self._new_proc()
1048+
return self
1049+
1050+
def _new_proc(self):
1051+
from ptyprocess import PtyProcess # Force unbuffered IO.
1052+
try:
1053+
# kpsewhich requires being passed at least argument, so query
1054+
# texmf.cnf and read that out. This also serves as sentinel below.
1055+
proc = PtyProcess.spawn(
1056+
["kpsewhich", "-interactive", "texmf.cnf"], echo=False)
1057+
self._sentinel = proc.readline()
1058+
return proc
1059+
except FileNotFoundError:
1060+
return None
1061+
1062+
def search(self, filename):
1063+
if filename == "texmf.cnf":
1064+
return self._sentinel
1065+
if self._proc is not None and not self._proc.isalive():
1066+
self._proc = self._new_proc() # Give a 2nd chance if it crashed.
1067+
if self._proc is None or not self._proc.isalive():
1068+
return ""
1069+
# kpsewhich prints nothing if the file does not exist. To detect this
1070+
# case without setting an arbitrary timeout, we additionally query for
1071+
# texmf.cnf, which is known to exist at `self._sentinel`. We can then
1072+
# check whether the first query exists by comparing the first returned
1073+
# line to `self._sentinel`. (This is also why we need to separately
1074+
# handle the case of querying texmf.cnf above.)
1075+
self._proc.write(os.fsencode(filename) + b"\ntexmf.cnf\n")
1076+
out = self._proc.readline()
1077+
if out == self._sentinel:
1078+
return ""
1079+
else:
1080+
self._proc.readline() # flush the extra sentinel line.
1081+
# POSIX ptys actually emit \r\n.
1082+
return os.fsdecode(out).rstrip("\r\n")
1083+
1084+
10431085
@lru_cache()
1086+
@_api.delete_parameter("3.5", "format")
10441087
def find_tex_file(filename, format=None):
10451088
"""
10461089
Find a file in the texmf tree.
@@ -1072,25 +1115,36 @@ def find_tex_file(filename, format=None):
10721115
if isinstance(format, bytes):
10731116
format = format.decode('utf-8', errors='replace')
10741117

1075-
if os.name == 'nt':
1118+
if format is not None: # Deprecated.
1119+
if os.name == 'nt': # See below re: encoding.
1120+
kwargs = {'env': {**os.environ, 'command_line_encoding': 'utf-8'},
1121+
'encoding': 'utf-8'}
1122+
else: # On POSIX, run through the equivalent of os.fsdecode().
1123+
kwargs = {'encoding': sys.getfilesystemencoding(),
1124+
'errors': 'surrogatescape'}
1125+
cmd = ['kpsewhich']
1126+
if format is not None:
1127+
cmd += ['--format=' + format]
1128+
cmd += [filename]
1129+
try:
1130+
result = cbook._check_and_log_subprocess(cmd, _log, **kwargs)
1131+
except (FileNotFoundError, RuntimeError):
1132+
return ''
1133+
return result.rstrip('\n')
1134+
1135+
if os.name == "nt":
10761136
# On Windows only, kpathsea can use utf-8 for cmd args and output.
1077-
# The `command_line_encoding` environment variable is set to force it
1078-
# to always use utf-8 encoding. See Matplotlib issue #11848.
1079-
kwargs = {'env': {**os.environ, 'command_line_encoding': 'utf-8'},
1080-
'encoding': 'utf-8'}
1081-
else: # On POSIX, run through the equivalent of os.fsdecode().
1082-
kwargs = {'encoding': sys.getfilesystemencoding(),
1083-
'errors': 'surrogatescape'}
1084-
1085-
cmd = ['kpsewhich']
1086-
if format is not None:
1087-
cmd += ['--format=' + format]
1088-
cmd += [filename]
1089-
try:
1090-
result = cbook._check_and_log_subprocess(cmd, _log, **kwargs)
1091-
except (FileNotFoundError, RuntimeError):
1092-
return ''
1093-
return result.rstrip('\n')
1137+
# The `command_line_encoding` environment variable is set to force
1138+
# it to always use utf-8 encoding. See Matplotlib issue #11848.
1139+
try:
1140+
result = cbook._check_and_log_subprocess(
1141+
["kpsewhich", filename], _log, encoding="utf-8",
1142+
env={**os.environ, "command_line_encoding": "utf-8"})
1143+
except (FileNotFoundError, RuntimeError):
1144+
return ""
1145+
return result.rstrip("\n")
1146+
else:
1147+
return _Kpsewhich().search(filename)
10941148

10951149

10961150
@lru_cache()

setup.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -309,6 +309,7 @@ def build_extensions(self):
309309
"pillow>=6.2.0",
310310
"pyparsing>=2.2.1",
311311
"python-dateutil>=2.7",
312+
"ptyprocess;os_name=='posix'",
312313
],
313314

314315
cmdclass=cmdclass,

0 commit comments

Comments
 (0)