PyCC Documentation

PyCC is a Python code optimizer. It rewrites your code to make it faster.

PyCC Usage Documentation

PyCC offers two command line utilities: pycc-transform and pycc-compile.

Transforming Code

PyCC is quite a young project. As a result, you may be hesitant to simply trust the output and skip straight to the compile step. Instead, you may want to see an example of what the optimizer has produced so that you can verify that it is, indeed, both valid Python and acceptable code. This is what the pycc-transform command is for.

Usage for this command is simple:

pycc-transform <python_file.py> [–optimizer_option]

All optimization is disabled by default. Use the appropriate flags to enable the optimizations you want to apply. At the time of writing the full options listing for pycc-transform was:

usage: pycc-transform [-h] [–constants] source

PyCC Transformer

positional arguments:
source Path to the python source code. May be file or directory.
optional arguments:
-h, --help show this help message and exit
--constants Replace constant expressions with inline literals.

Running this command will output the optimized source code to the terminal.

If you run pycc-transform and it produces invalid Python code or it changes the code such that it no longer does what it is supposed to do then please post the original source and optimization options enabled as a bug on the project GitHub page.

Compiling Code

Once you are comfortable with the way PyCC alters your code you can start running the pycc-compile command:

usage: pycc-compile [-h] [–constants] [–destination DESTINATION] source

PyCC Compiler

positional arguments:
source Path to the python source code. May be file or
directory.
optional arguments:
-h, --help show this help message and exit
--constants Replace constant expressions with inline literals.
--destination DESTINATION
 Path to place compiled binaries in.

The compiler can be run on either individual Python modules or it can be pointed at a Python package. By default the script will drop the ‘.pyc’ files right next to the source files just like the normal Python compiler. Alternatively you may use the ‘destination’ option to direct the ‘.pyc’ files to another directory.

The Python compiler will overwrite existing ‘.pyc’ files every time a source file is updated. The ‘destination’ option is useful if you want to create a compiled version of a module or package that doesn’t get overwritten every time you make a change to the source.

PyCC Optimizer Documentation

Each PyCC optimizer applies some transformation to your source code. All optimizers are disabled by default in both the pycc-transform and pycc-compile scripts. Below is a list of of optimizations that can be enabled, a description of what transformation it applies, and the command line flag needed to enable it.

Constant Inlining

Flag: –constants

As demonstrated on the main page, this option replaces the use of read-only, constant values with their literal values. This affects variables that are assigned only once within the given scope and are assigned to a number, string, or name value. Name values are any other symbols including True, False, and None.

This transformation does not apply to constants that are assigned to complex types such as lists, tuples, function calls, or generators.

PyCC Developer Documentation

Contributing To The Project

Adding a new optimizer to the core project is (hopefully) a straightforward process. You are, of course, welcome to use any kind of workflow you like. For this walkthrough I will be using my own as an example.

Step 1: Light Scaffolding

The first step is getting the CLI utilities bootstrapped for you new optimizer so you can use the pycc-transform command to help iterate and work out bugs.

Start by creating a new module in the optimizers subpackage to house your work, name it something relevant, and put function in it named optimize that has the following signature:

function optimize(module, *args, **kwargs):

    pass

This function will be run by the CLI tools to execute your optimization chain. The first argument with always be a module.Module object. The rest of the arguments are determined by the CLI. If your optimizer is configurable then you may add additional named arguments after ‘module’.

Now add a line like pycc_my_optimizer = pycc.optimizers.my_optimizer:optimize to the pycc.optimizers section in setup.py. The pycc_ prefix is important so don’t leave it off.

Finally, add a new argument to the parser in the register function of the cli.args module. Example:

parser.add_argument(
    '--my_optimizer',
    help="Makes stuff go faster.",
    action='store_const',
    default=None,
    const="pycc_my_optimizer",
)

The value of the const parameter should match the name you selected in the pycc.optimizers entry points from above.

If you want to make a configuration value for your optimizer accessible as a CLI flag then you may add additional arguments to the parser. Just be sure to choose a name that is not so generic as to cause conflicts. All CLI arguments will be passed to your optimize function as keyword arguments. You can collect the values either through the **kwargs interface or by simply accepting a named parameter in the optimize function.

Once you have this basic scaffolding set up you will be able to see your new optimizer flag in the command line scripts. As you add actual code you will be able to see immediate results by running the pycc-transform command.

Step 2: Write A Finder

The next step is implementing the optimizers.base.Finder interface. The interface is simple. Basically it must return an iterable of optimizers.base.FinderResult objects when called. The logic used to find the AST nodes related to your particular optimization is up to you. I found it useful to use the ast.NodeVisitor class to easily walk the AST.

The basic idea behind the Finder is to group together all the logic needed to identify the AST node(s) required to perform the transformation. For example, the optimizers.constant.ConstantFinder pushes out FinderResult objects containing the ast.Assign node for any constant values it finds.

Step 3: Write A Transformer

A transformer is an implementation of optimizers.base.Transformer. It accepts a FinderResult as an argument to the call and modifies the AST in any way required to apply the optimization. The logic that applies the AST transformation is up to you. I found it useful to use the ast.NodeTransformer class for this purpose.

Easy mistakes to avoid:

Make sure to use the ast.copy_location if you are replacing a node. You will likely experience some errors with generating the transformed code if you don’t.

If you decide to the the ast.NodeTransformer make sure return the results of self.generic_visit(node) any time you do not alter the node. Forgetting this step could case a large portion of the code to disappear.

Step 4: Optimize And Iterate

The optimize function you created in step one is the primary entry point for your new optimization. It should run your Finder and pass all the results through your Transformer. Once you implement this function you will start to see results through the pycc-transform command.

Use the command line client on some sample code and iterate until you feel it’s right.

Step 5: Test And Lint

Make sure the code passes PEP8 and PyFlakes. Those are the linters that Travis will run. Also be sure to add some test coverage for the Finder and Transformer. We’re using py.test.

Developing Third Party Extensions

Writing third party extensions that plug into the PyCC commands requires you to provide two interfaces on the right entry points.

On the pycc.optimizers entry point you must expose a callable which accepts a module.Module object as the first parameter, any number of named parameters which correspond with relevant CLI args added by your module, *args, and`**kwargs`. The name of this entry point must be prefixed with pycc_.

On the pycc.cli.args entry point you must expose a callable which accepts an argument parser from argparse. Your function must use this parser to register an argument as follows:

parser.add_argument(
    '--my_optimizer',
    help="Makes stuff go faster.",
    action='store_const',
    default=None,
    const="pycc_my_optimizer",
)

The value of the const parameter should match exactly the name give to the pycc.optimizers entry point.

You may also register arguments needed to configure your optimizer. They will be passed into your pycc.optimizers entry point addition as keyword arguments.

Basic Example

Symbol table (variable) lookups don’t seem expensive at first.

# awesome_module.py

MAGIC_NUMBER = 7

for x in xrange(10000000):

    MAGIC_NUMBER * MAGIC_NUMBER

Now let’s make a crude benchmark.

# Generate bytecode file to skip compilation at runtime.
python -m compileall awesome_module.py
# Now get a simple timer.
time python awesome_module.pyc

# real    0m0.923s
# user    0m0.920s
# sys     0m0.004s

What does PyCC have to say about it?

pycc-transform awesome_module.py --constants
MAGIC_NUMBER = 7
for x in xrange(10000000):
    (7 * 7)

Neat, but what good does that do?

pycc-compile awesome_module.py -- constants
time python awesome_module.pyc

# real    0m0.473s
# user    0m0.469s
# sys     0m0.004s

How To Get It

pip install pycc

Source?

If you want to file a bug, request an optimization, contribute a patch, or just read through the source then head over to the GitHub page.

License

The project is licensed under the Apache 2 license.

Indices and tables