| 1 | # This program is free software; you can redistribute it and/or modify it under |
|---|
| 2 | # the terms of the GNU General Public License as published by the Free Software |
|---|
| 3 | # Foundation; either version 2 of the License, or (at your option) any later |
|---|
| 4 | # version. |
|---|
| 5 | # |
|---|
| 6 | # This program is distributed in the hope that it will be useful, but WITHOUT |
|---|
| 7 | # ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS |
|---|
| 8 | # FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. |
|---|
| 9 | # |
|---|
| 10 | # You should have received a copy of the GNU General Public License along with |
|---|
| 11 | # this program; if not, write to the Free Software Foundation, Inc., |
|---|
| 12 | # 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. |
|---|
| 13 | |
|---|
| 14 | """ Copyright (c) 2002-2003 LOGILAB S.A. (Paris, FRANCE). |
|---|
| 15 | http://www.logilab.fr/ -- mailto:contact@logilab.fr |
|---|
| 16 | |
|---|
| 17 | This module provides a way to optimize globals in certain functions by binding |
|---|
| 18 | their names to values provided in a dictionnary |
|---|
| 19 | """ |
|---|
| 20 | |
|---|
| 21 | from warnings import warn |
|---|
| 22 | warn('bind module is deprecated and will disappear in a near release', |
|---|
| 23 | DeprecationWarning, stacklevel=1) |
|---|
| 24 | |
|---|
| 25 | __revision__ = '$Id: bind.py,v 1.8 2005-11-22 13:12:59 syt Exp $' |
|---|
| 26 | |
|---|
| 27 | # TODO: unit tests |
|---|
| 28 | # * this module provide a function bind(func,vars) which replaces every |
|---|
| 29 | # global variable 'm' by the value vars['m'] if such value exists in dict |
|---|
| 30 | |
|---|
| 31 | from dis import HAVE_ARGUMENT |
|---|
| 32 | from new import code as make_code, function as make_function |
|---|
| 33 | import inspect |
|---|
| 34 | |
|---|
| 35 | LOAD_GLOBAL = 116 |
|---|
| 36 | LOAD_CONST = 100 |
|---|
| 37 | EXTENDED_ARG = 143 |
|---|
| 38 | STORE_GLOBAL = 97 |
|---|
| 39 | |
|---|
| 40 | def bind_code(co, globals): |
|---|
| 41 | """ |
|---|
| 42 | Take a code object and a dictionnary and returns a new code object where |
|---|
| 43 | the opcodes LOAD_GLOBAL are replaced by LOAD_CONST whenever the global's |
|---|
| 44 | name appear in the dictionnary |
|---|
| 45 | """ |
|---|
| 46 | consts = list(co.co_consts) |
|---|
| 47 | assigned = {} |
|---|
| 48 | |
|---|
| 49 | code = co.co_code |
|---|
| 50 | new_code = "" |
|---|
| 51 | n = len(code) |
|---|
| 52 | i = 0 |
|---|
| 53 | while i < n: |
|---|
| 54 | c = code[i] |
|---|
| 55 | op = ord(c) |
|---|
| 56 | i += 1 |
|---|
| 57 | if op >= HAVE_ARGUMENT: |
|---|
| 58 | oparg = ord(code[i]) + ord(code[i+1]) * 256 |
|---|
| 59 | i += 2 |
|---|
| 60 | else: |
|---|
| 61 | oparg = None |
|---|
| 62 | if op == LOAD_GLOBAL: |
|---|
| 63 | name = co.co_names[oparg] |
|---|
| 64 | if globals.has_key(name): |
|---|
| 65 | k = assigned.get(name, None) |
|---|
| 66 | if k == None: |
|---|
| 67 | k = len(consts) |
|---|
| 68 | assigned[name] = len(consts) |
|---|
| 69 | consts.append(globals[name]) |
|---|
| 70 | op = LOAD_CONST |
|---|
| 71 | oparg = k |
|---|
| 72 | new_code += chr(op) |
|---|
| 73 | if oparg is not None: |
|---|
| 74 | new_code += chr(oparg & 255) |
|---|
| 75 | new_code += chr( (oparg>>8) & 255 ) |
|---|
| 76 | |
|---|
| 77 | return make_code(co.co_argcount, |
|---|
| 78 | co.co_nlocals, |
|---|
| 79 | co.co_stacksize, |
|---|
| 80 | co.co_flags, |
|---|
| 81 | new_code, |
|---|
| 82 | tuple(consts), |
|---|
| 83 | co.co_names, |
|---|
| 84 | co.co_varnames, |
|---|
| 85 | co.co_filename, |
|---|
| 86 | co.co_name, |
|---|
| 87 | co.co_firstlineno, |
|---|
| 88 | co.co_lnotab ) |
|---|
| 89 | |
|---|
| 90 | |
|---|
| 91 | def bind(f, globals): |
|---|
| 92 | """Returns a new function whose code object has been |
|---|
| 93 | bound by bind_code()""" |
|---|
| 94 | newcode = bind_code(f.func_code, globals) |
|---|
| 95 | defaults = f.func_defaults or () |
|---|
| 96 | return make_function(newcode, f.func_globals, f.func_name, defaults) |
|---|
| 97 | |
|---|
| 98 | if type(__builtins__) == dict: |
|---|
| 99 | builtins = __builtins__ |
|---|
| 100 | else: |
|---|
| 101 | builtins = __builtins__.__dict__ |
|---|
| 102 | |
|---|
| 103 | bind_code_opt = bind(bind_code, builtins ) |
|---|
| 104 | bind_code_opt = bind(bind_code_opt, globals() ) |
|---|
| 105 | |
|---|
| 106 | |
|---|
| 107 | def optimize_module(m, global_consts): |
|---|
| 108 | if not inspect.ismodule(m): |
|---|
| 109 | raise TypeError |
|---|
| 110 | d = {} |
|---|
| 111 | for i in global_consts: |
|---|
| 112 | v = m.__dict__.get(i) |
|---|
| 113 | d[i] = v |
|---|
| 114 | builtins = m.__builtins__ |
|---|
| 115 | for name, f in m.__dict__.items(): |
|---|
| 116 | if inspect.isfunction(f): |
|---|
| 117 | f = bind(f, builtins) |
|---|
| 118 | if d: |
|---|
| 119 | f = bind(f, d) |
|---|
| 120 | m.__dict__[name] = f |
|---|
| 121 | |
|---|
| 122 | |
|---|
| 123 | |
|---|
| 124 | |
|---|
| 125 | def analyze_code(co, globals, consts_dict, consts_list): |
|---|
| 126 | """Take a code object and a dictionnary and returns a |
|---|
| 127 | new code object where the opcodes LOAD_GLOBAL are replaced |
|---|
| 128 | by LOAD_CONST whenever the global's name appear in the |
|---|
| 129 | dictionnary""" |
|---|
| 130 | modified_globals = [] |
|---|
| 131 | for c in co.co_consts: |
|---|
| 132 | if c not in consts_list: |
|---|
| 133 | consts_list.append(c) |
|---|
| 134 | modified = [] |
|---|
| 135 | code = co.co_code |
|---|
| 136 | new_code = "" |
|---|
| 137 | n = len(code) |
|---|
| 138 | i = 0 |
|---|
| 139 | extended_arg = 0 |
|---|
| 140 | while i < n: |
|---|
| 141 | c = code[i] |
|---|
| 142 | op = ord(c) |
|---|
| 143 | i += 1 |
|---|
| 144 | if op >= HAVE_ARGUMENT: |
|---|
| 145 | oparg = ord(code[i]) + ord(code[i+1])*256 + extended_arg |
|---|
| 146 | extended_arg = 0 |
|---|
| 147 | i += 2 |
|---|
| 148 | else: |
|---|
| 149 | oparg = None |
|---|
| 150 | if op == EXTENDED_ARG: |
|---|
| 151 | extended_arg = oparg*65536L |
|---|
| 152 | |
|---|
| 153 | if op == LOAD_GLOBAL: |
|---|
| 154 | name = co.co_names[oparg] |
|---|
| 155 | if globals.has_key(name): |
|---|
| 156 | k = consts_dict.get(name, None) |
|---|
| 157 | if k == None: |
|---|
| 158 | k = len(consts_list) |
|---|
| 159 | consts_dict[name] = k |
|---|
| 160 | consts_list.append(globals[name]) |
|---|
| 161 | if op == STORE_GLOBAL: |
|---|
| 162 | name = co.co_names[oparg] |
|---|
| 163 | if globals.has_key(name): |
|---|
| 164 | modified_globals.append(name) |
|---|
| 165 | return modified_globals |
|---|
| 166 | |
|---|
| 167 | def rewrite_code(co, consts_dict, consts_tuple): |
|---|
| 168 | """Take a code object and a dictionnary and returns a |
|---|
| 169 | new code object where the opcodes LOAD_GLOBAL are replaced |
|---|
| 170 | by LOAD_CONST whenever the global's name appear in the |
|---|
| 171 | dictionnary""" |
|---|
| 172 | code = co.co_code |
|---|
| 173 | new_code = "" |
|---|
| 174 | n = len(code) |
|---|
| 175 | i = 0 |
|---|
| 176 | consts_list = list(consts_tuple) |
|---|
| 177 | while i < n: |
|---|
| 178 | c = code[i] |
|---|
| 179 | op = ord(c) |
|---|
| 180 | i += 1 |
|---|
| 181 | extended_arg = 0 |
|---|
| 182 | if op >= HAVE_ARGUMENT: |
|---|
| 183 | oparg = ord(code[i]) + ord(code[i+1])*256+extended_arg |
|---|
| 184 | extended_arg = 0 |
|---|
| 185 | i += 2 |
|---|
| 186 | else: |
|---|
| 187 | oparg = None |
|---|
| 188 | if op == EXTENDED_ARG: |
|---|
| 189 | extended_arg = oparg*65536L |
|---|
| 190 | elif op == LOAD_GLOBAL: |
|---|
| 191 | name = co.co_names[oparg] |
|---|
| 192 | k = consts_dict.get(name) |
|---|
| 193 | if k is not None: |
|---|
| 194 | op = LOAD_CONST |
|---|
| 195 | oparg = k |
|---|
| 196 | elif op == LOAD_CONST: |
|---|
| 197 | val = co.co_consts[oparg] |
|---|
| 198 | oparg = consts_list.index(val) |
|---|
| 199 | new_code += chr(op) |
|---|
| 200 | if oparg is not None: |
|---|
| 201 | new_code += chr(oparg & 255) |
|---|
| 202 | new_code += chr( (oparg>>8) & 255 ) |
|---|
| 203 | |
|---|
| 204 | return make_code(co.co_argcount, |
|---|
| 205 | co.co_nlocals, |
|---|
| 206 | co.co_stacksize, |
|---|
| 207 | co.co_flags, |
|---|
| 208 | new_code, |
|---|
| 209 | consts_tuple, |
|---|
| 210 | co.co_names, |
|---|
| 211 | co.co_varnames, |
|---|
| 212 | co.co_filename, |
|---|
| 213 | co.co_name, |
|---|
| 214 | co.co_firstlineno, |
|---|
| 215 | co.co_lnotab ) |
|---|
| 216 | |
|---|
| 217 | def optimize_module_2(m, globals_consts, bind_builtins=1): |
|---|
| 218 | if not inspect.ismodule(m): |
|---|
| 219 | raise TypeError |
|---|
| 220 | consts_dict = {} |
|---|
| 221 | consts_list = [] |
|---|
| 222 | if type(globals_consts) == list or type(globals_consts) == tuple: |
|---|
| 223 | globals = {} |
|---|
| 224 | for i in globals_consts: |
|---|
| 225 | v = m.__dict__.get(i) |
|---|
| 226 | globals[i] = v |
|---|
| 227 | else: |
|---|
| 228 | globals = globals_consts |
|---|
| 229 | if bind_builtins: |
|---|
| 230 | for builtin_name, builtin_value in m.__builtins__.items(): |
|---|
| 231 | # this way it is possible to redefine a builtin in globals_consts |
|---|
| 232 | globals.setdefault(builtin_name, builtin_value) |
|---|
| 233 | functions = {} |
|---|
| 234 | for name, f in m.__dict__.items(): |
|---|
| 235 | if inspect.isfunction(f): |
|---|
| 236 | functions[name] = f |
|---|
| 237 | analyze_code(f.func_code, globals, consts_dict, consts_list) |
|---|
| 238 | consts_list = tuple(consts_list) |
|---|
| 239 | for name, f in functions.items(): |
|---|
| 240 | newcode = rewrite_code(f.func_code, consts_dict, consts_list) |
|---|
| 241 | defaults = f.func_defaults or () |
|---|
| 242 | m.__dict__[name] = make_function(newcode, f.func_globals, f.func_name, |
|---|
| 243 | defaults) |
|---|
| 244 | |
|---|
| 245 | |
|---|
| 246 | def run_bench(n): |
|---|
| 247 | from time import time |
|---|
| 248 | t = time() |
|---|
| 249 | g = globals() |
|---|
| 250 | for i in range(n): |
|---|
| 251 | test = bind(bind_code, g) |
|---|
| 252 | t1 = time()-t |
|---|
| 253 | bind2 = bind(bind, {'bind_code':bind_code_opt}) |
|---|
| 254 | t = time() |
|---|
| 255 | for i in range(n): |
|---|
| 256 | test=bind2(bind_code, g) |
|---|
| 257 | t2 = time()-t |
|---|
| 258 | print "1 regular version", t1 |
|---|
| 259 | print "2 optimized version", t2 |
|---|
| 260 | print "ratio (1-2)/1 : %f %%" % (100.*(t1-t2)/t1) |
|---|
| 261 | |
|---|
| 262 | |
|---|
| 263 | def test_pystone(): |
|---|
| 264 | from test import pystone |
|---|
| 265 | for _ in range(5): |
|---|
| 266 | pystone.main() |
|---|
| 267 | optimize_module(pystone, ('TRUE','FALSE','Proc0','Proc1','Proc2','Proc3', |
|---|
| 268 | 'Proc4','Proc5','Proc6','Proc7','Proc8','Func1', |
|---|
| 269 | ' Func2','Func3')) |
|---|
| 270 | optimize_module(pystone, builtins.keys()) |
|---|
| 271 | for _ in range(5): |
|---|
| 272 | pystone.main() |
|---|
| 273 | |
|---|
| 274 | |
|---|
| 275 | if __name__ == "__main__": |
|---|
| 276 | run_bench(1000) |
|---|