1

This question has been asked many times on SO (for instance here), but there is no real answer yet.

I am writing a short command line tool that renders templates. It is frigged using a Makefile:

i = $(wildcard *.in)
o = $(patsubst %.in, %.out, $(t))

all: $(o)

%.out: %.in
    ./script.py -o $@ $<

In this dummy example, the Makefile parses every .in file to generate an .out file. It is very convenient for me to use make because I have a lot of other actions to trig before and after this script. Moreover I would like to remain as KISS as possible.

Thus, I want to keep my tool simple, stupid and process each file separately using the syntax script -o out in

My script uses the following:

#!/usr/bin/env python
from jinja2 import Template, nodes
from jinja2.ext import Extension
import hiyapyco
import argparse
import re   

...

The problem is that each execution costs me about 1.2s ( ~60ms for the processing and ~1140ms for the import directives):

$ time ./script.py -o foo.out foo.in
real    0m1.625s
user    0m0.452s
sys     0m1.185s

The overall execution of my Makefile for 100 files is ridiculous: ~100 files x 1.2s = 120s.

This is not a solution, but this should be the solution.

What alternative can I use?

EDIT

I love Python because its syntax is readable and size of its community. In this particular case (command line tools), I have to admit Perl is still a decent alternative. The same script written in Perl (which is also an interpreted language) is about 12 times faster (using Text::Xslate).

I don't want to promote Perl in anyway I am just trying to solve my biggest issue with Python: it is not yet a suitable language for simple command line tools because of the poor import time.

Community
  • 1
  • 1
nowox
  • 25,978
  • 39
  • 143
  • 293
  • This would be a better fit on [Code Review](http://codereview.stackexchange.com/) as long as you have a fully working example – muddyfish Aug 23 '16 at 11:52
  • @muddyfish This is not a Code Review question since this may concern any CLI tool written in Python. – nowox Aug 23 '16 at 11:54
  • Not a solution to the question, but how about doing things in parallel for starters? Could solve your problem. – Reut Sharabani Aug 23 '16 at 11:54
  • 1
    If you have a lot of files to process, pass _all_ of them to the script as command line arguments. Then the imports only slow you down once. – Aran-Fey Aug 23 '16 at 11:54
  • @ReutSharabani Indeed, I can use `make -j12`, but this looks like an ugly workaround. – nowox Aug 23 '16 at 11:55
  • @Rawing As mentioned, this is not what I want to do. The same remark may apply to `gcc`. Why is it impossible, and not recommended, to pass *all* c files to gcc at once? – nowox Aug 23 '16 at 11:57
  • Why not use a workflow manager like ruffus or snakemake instead of Make? e.g. http://www.ruffus.org.uk/ – Chris_Rands Aug 23 '16 at 12:00
  • Because `make` is powerful, simple and does exactly what I want to do. `snakemake` could be a solution but this mean I have to rework all my toolchain. – nowox Aug 23 '16 at 12:04

4 Answers4

3

It is not quite easy, but you could turn your program into one that sits in the background and processes commands to process a file.

Another program could feed the processing commands to it and thus make the real start quite easy.

glglgl
  • 89,107
  • 13
  • 149
  • 217
  • 1
    I think this is probably the most reasonable solution, because I don't think there is a good way to speedup the module import. – Hannes Ovrén Aug 23 '16 at 12:10
1

Write the template part as a separate process. The first time "script.py" is run it would launch this separate process. Once the process exists it can be passed the input/output filenames via a named pipe. If the process gets no input for x seconds, it automatically exits. How big x is depends on what your needs are

So the parameters are passed to the long running process via the script.py writing to a named pipe. The imports only occur once (provided the inputs are fairly often) and as BPL points out this would make everything run faster

Vorsprung
  • 32,923
  • 5
  • 39
  • 63
0

You could use glob to perform that actions with the files you need.

import glob
in_files=glob.glob('*.in') 
out_files=glob.glob('*.out') 

Thus, you process all the files in the same script, instead of calling the script every time with every pair of files. At least that way you don't have to start python every time.

silgon
  • 6,890
  • 7
  • 46
  • 67
  • 1
    You're not really answering the question – nowox Aug 23 '16 at 12:00
  • It's just a workaround that I use when I need to treat a lot of files. If you call your script and load libraries every time every time, of course it will take `n` time, being `n` the number of pair of files you process. – silgon Aug 23 '16 at 12:03
0

It seems quite clear where the problem is, right now you got:

cost(file) = 1.2s = 60ms + 1040ms, which means:

cost(N*files) = N*1.2s

now, why don't you change it to become:

cost1(files) = 1040ms + N*60ms

that way, theorically processing 100 files would be 7,04s instead 120s

EDIT:

Because I'm receiving downvotes to this question, I'll post a little example, let's assume you got this python file:

# foo.py
import numpy
import cv2

print sys.argv[0]

The execution time is 1.3s on my box, now, if i do:

for /l %x in (1, 1, 100) do python foo.py

I'll get 100*1.3s execution time, my proposal was turn foo.py into this:

import numpy
import cv2

def whatever_rendering_you_want_to_do(file):
    pass

for file in sys.argv:
    whatever_rendering_you_want_to_do(file)

That way you're importing only once instead of 100 times

BPL
  • 9,632
  • 9
  • 59
  • 117
  • Exactly, you pointed it out where the problem is. So my question "How to improve python import speed?" – nowox Aug 23 '16 at 12:05
  • @nowox I've edited my answer, hope it helps, dunno why people were downvoting before, I thought identifying the problem was a valid answer per se. – BPL Aug 23 '16 at 12:18
  • I understand your answer and this is perhaps a good solution to another question, but not this particular one. I don't want to do the for loop inside my python script. – nowox Aug 23 '16 at 12:27
  • @nowox I wasn't suggesting looping in your python script, which is doing 1 task and is doing it ok. I'm suggesting to extract out the import directives to a main processor file, and from there you just call your worker avoiding the overhead of importing over and over. That's pretty much... Anyway, good luck :) – BPL Aug 23 '16 at 12:30