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

Skip to content

Commit 9f3da44

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 73b5101 commit 9f3da44

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
@@ -1032,7 +1032,50 @@ def _parse_enc(path):
10321032
"Failed to parse {} as Postscript encoding".format(path))
10331033

10341034

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

1067-
if os.name == 'nt':
1110+
if format is not None: # Deprecated.
1111+
if os.name == 'nt': # See below re: encoding.
1112+
kwargs = {'env': {**os.environ, 'command_line_encoding': 'utf-8'},
1113+
'encoding': 'utf-8'}
1114+
else: # On POSIX, run through the equivalent of os.fsdecode().
1115+
kwargs = {'encoding': sys.getfilesystemencoding(),
1116+
'errors': 'surrogatescape'}
1117+
cmd = ['kpsewhich']
1118+
if format is not None:
1119+
cmd += ['--format=' + format]
1120+
cmd += [filename]
1121+
try:
1122+
result = cbook._check_and_log_subprocess(cmd, _log, **kwargs)
1123+
except (FileNotFoundError, RuntimeError):
1124+
return ''
1125+
return result.rstrip('\n')
1126+
1127+
if os.name == "nt":
10681128
# On Windows only, kpathsea can use utf-8 for cmd args and output.
1069-
# The `command_line_encoding` environment variable is set to force it
1070-
# to always use utf-8 encoding. See Matplotlib issue #11848.
1071-
kwargs = {'env': {**os.environ, 'command_line_encoding': 'utf-8'},
1072-
'encoding': 'utf-8'}
1073-
else: # On POSIX, run through the equivalent of os.fsdecode().
1074-
kwargs = {'encoding': sys.getfilesystemencoding(),
1075-
'errors': 'surrogatescape'}
1076-
1077-
cmd = ['kpsewhich']
1078-
if format is not None:
1079-
cmd += ['--format=' + format]
1080-
cmd += [filename]
1081-
try:
1082-
result = cbook._check_and_log_subprocess(cmd, _log, **kwargs)
1083-
except (FileNotFoundError, RuntimeError):
1084-
return ''
1085-
return result.rstrip('\n')
1129+
# The `command_line_encoding` environment variable is set to force
1130+
# it to always use utf-8 encoding. See Matplotlib issue #11848.
1131+
try:
1132+
result = cbook._check_and_log_subprocess(
1133+
["kpsewhich", filename], _log, encoding="utf-8",
1134+
env={**os.environ, "command_line_encoding": "utf-8"})
1135+
except (FileNotFoundError, RuntimeError):
1136+
return ""
1137+
return result.rstrip("\n")
1138+
else:
1139+
return _Kpsewhich().search(filename)
10861140

10871141

10881142
@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)