1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78
|
"""
Demonstrate the use of multiprocessing with PyMuPDF.
Depending on the number of CPUs, the document is divided in page ranges.
Each range is then worked on by one process.
The type of work would typically be text extraction or page rendering. Each
process must know where to put its results, because this processing pattern
does not include inter-process communication or data sharing.
Compared to sequential processing, speed improvements in range of 100% (ie.
twice as fast) or better can be expected.
"""
from __future__ import print_function, division
import sys
import os
import time
from multiprocessing import Pool, cpu_count
import pymupdf
# choose a version specific timer function (bytes == str in Python 2)
mytime = time.clock if str is bytes else time.perf_counter
def render_page(vector):
"""Render a page range of a document.
Notes:
The PyMuPDF document cannot be part of the argument, because that
cannot be pickled. So we are being passed in just its filename.
This is no performance issue, because we are a separate process and
need to open the document anyway.
Any page-specific function can be processed here - rendering is just
an example - text extraction might be another.
The work must however be self-contained: no inter-process communication
or synchronization is possible with this design.
Care must also be taken with which parameters are contained in the
argument, because it will be passed in via pickling by the Pool class.
So any large objects will increase the overall duration.
Args:
vector: a list containing required parameters.
"""
# recreate the arguments
idx = vector[0] # this is the segment number we have to process
cpu = vector[1] # number of CPUs
filename = vector[2] # document filename
mat = vector[3] # the matrix for rendering
doc = pymupdf.open(filename) # open the document
num_pages = doc.page_count # get number of pages
# pages per segment: make sure that cpu * seg_size >= num_pages!
seg_size = int(num_pages / cpu + 1)
seg_from = idx * seg_size # our first page number
seg_to = min(seg_from + seg_size, num_pages) # last page number
for i in range(seg_from, seg_to): # work through our page segment
page = doc[i]
# page.get_text("rawdict") # use any page-related type of work here, eg
pix = page.get_pixmap(alpha=False, matrix=mat)
# store away the result somewhere ...
# pix.save("p-%i.png" % i)
print("Processed page numbers %i through %i" % (seg_from, seg_to - 1))
if __name__ == "__main__":
t0 = mytime() # start a timer
filename = sys.argv[1]
mat = pymupdf.Matrix(0.2, 0.2) # the rendering matrix: scale down to 20%
cpu = cpu_count()
# make vectors of arguments for the processes
vectors = [(i, cpu, filename, mat) for i in range(cpu)]
print("Starting %i processes for '%s'." % (cpu, filename))
pool = Pool() # make pool of 'cpu_count()' processes
pool.map(render_page, vectors, 1) # start processes passing each a vector
t1 = mytime() # stop the timer
print("Total time %g seconds" % round(t1 - t0, 2))
|