Translation (GettextAlias _() ) performance bug? too slow to be usable (>1 sec per call)

Bug #624114 reported by Borja López Soilán (NeoPolus)
20
This bug affects 3 people
Affects Status Importance Assigned to Milestone
Odoo Addons (MOVED TO GITHUB)
Invalid
Undecided
Unassigned
Odoo Server (MOVED TO GITHUB)
Fix Released
Medium
OpenERP's Framework R&D

Bug Description

The current implementation of the translation method "_()" seems to be extremely slow (about 1 second per call) on some circumstances, to the point that is the most time consuming method on some business process.

For example the confirming a sale order, with mrp_jit installed, will take about 40 seconds!
       - Of those, about 28.44 seconds are spent on 27 calls to _() on the _check_make_to_stock_product calculation. And most of that time is spent by Python, not Postgres.
       - The rest of the _check_make_to_stock_product calculation takes less than 5 total seconds on the 27 calls to it.

We profiled the action_ship_create method (of sale orders) with several methods, and the only conclusion is that the _() method (from tools.translate import _) is too slow to be usable (though it is used everywhere on OpenERP!!!), while the translate() method (from tools.translate import translate) proved to be hundreds of time faster.

Now we wonder:

    - Is this a a general problem? (a problem with the current implementation of "_()" that relies on the inspect module)
    - Should _() be deprecated? (and use "translate()" instead, which is lots of times faster?).

Now the proof:

--- BASIC TIME.TIME() PROFILING -------------------------------------------------------------

To do a basic profiling, just edit the GettextAlias source code, and where it looks like this:

class GettextAlias(object):
    def __call__(self, source):
        try:
            frame = inspect.stack()[1][0]
        except:
            return source

Replace it with this to get the timings:

class GettextAlias(object):
    def __call__(self, source):
        import time
        start = time.time()
        try:
            frame = inspect.stack()[1][0]
        except:
            return source
        stop = time.time()
        print "GettextAlias time: ", stop-start

Confirming one sale order will call the _() method 27 times, these are the timings of those 27 calls:

GettextAlias time: 1.10414505005
GettextAlias time: 1.10945105553
GettextAlias time: 1.10807609558
GettextAlias time: 0.989851951599
GettextAlias time: 0.996656179428
GettextAlias time: 0.986680030823
GettextAlias time: 1.00202202797
GettextAlias time: 1.01647782326
GettextAlias time: 0.956711053848
GettextAlias time: 1.03200602531
GettextAlias time: 0.976341962814
GettextAlias time: 1.1115489006
GettextAlias time: 0.962368965149
GettextAlias time: 1.09971618652
GettextAlias time: 1.02273511887
GettextAlias time: 0.996299982071
GettextAlias time: 0.975975036621
GettextAlias time: 1.00379395485
GettextAlias time: 1.00497484207
GettextAlias time: 1.08288192749
GettextAlias time: 0.96357011795
GettextAlias time: 1.02459406853
GettextAlias time: 0.991461038589
GettextAlias time: 0.961183071136
GettextAlias time: 0.976219892502
GettextAlias time: 0.969728946686
GettextAlias time: 1.00834703445
GettextAlias time: 1.00862288475

--- PROFILING WITH PROFILE - STATS -------------------------------------------------------------

This is the trace of a profile (http://docs.python.org/library/profile.html) of the action_ship_create method of the sale orders, sorted by cumulative time. Notice how 68.9 seconds are spent on the translate.py module (marked with **** for easier look up):

>>> stats.sort_stats("cumulative").print_stats()
Wed Aug 25 16:53:45 2010 prof.dat

         37130567 function calls (37126505 primitive calls) in 78.907 CPU seconds

   Ordered by: cumulative time

   ncalls tottime percall cumtime percall filename:lineno(function)
        1 0.000 0.000 78.911 78.911 /openerp-server/bin/tools/misc.py:941(__call__)
        1 0.001 0.001 78.911 78.911 /openerp-server/bin/addons/sale/sale.py:570(action_ship_create_orig)
   303/13 0.010 0.000 78.396 6.030 /openerp-server/bin/workflow/workitem.py:44(process)
     37/4 0.001 0.000 78.372 19.593 /openerp-server/bin/workflow/wkf_service.py:75(trg_validate)
    142/8 0.007 0.000 78.369 9.796 /openerp-server/bin/workflow/workitem.py:34(create)
   220/12 0.010 0.000 78.367 6.531 /openerp-server/bin/workflow/workitem.py:148(_split_test)
     37/4 0.001 0.000 78.367 19.592 /openerp-server/bin/workflow/instance.py:45(validate)
    105/4 0.003 0.000 78.336 19.584 /openerp-server/bin/workflow/workitem.py:179(_join_test)
   243/28 0.004 0.000 78.143 2.791 /openerp-server/bin/workflow/wkf_expr.py:44(_eval_expr)
   208/24 0.016 0.000 78.140 3.256 {eval}
   181/24 0.003 0.000 78.120 3.255 /openerp-server/bin/osv/orm.py:167(<lambda>)
   142/20 0.002 0.000 77.939 3.897 /openerp-server/bin/workflow/workitem.py:86(_execute)
    72/10 0.001 0.000 77.895 7.789 /openerp-server/bin/workflow/wkf_expr.py:67(execute)
        3 0.000 0.000 77.622 25.874 /openerp-server/bin/addons/cc_mrp_packing_bom/mrp_procurement.py:32(action_produce_assign_product)
        3 0.001 0.000 77.578 25.859 /openerp-server/bin/addons/mrp/mrp.py:1041(action_produce_assign_product)
        3 0.010 0.003 76.721 25.574 /openerp-server/bin/addons/mrp/mrp.py:630(action_confirm)
       28 0.002 0.000 68.910 2.461 /openerp-server/bin/tools/translate.py:128(__call__) ***********************************
       28 0.000 0.000 68.909 2.461 /usr/lib/python2.5/inspect.py:882(stack)
       28 0.025 0.001 68.909 2.461 /usr/lib/python2.5/inspect.py:858(getouterframes)
     2887 0.057 0.000 68.881 0.024 /usr/lib/python2.5/inspect.py:820(getframeinfo)
  442/440 0.004 0.000 68.565 0.156 /openerp-server/bin/workflow/wkf_expr.py:70(check)
       27 0.001 0.000 67.363 2.495 /openerp-server/bin/addons/mrp/mrp.py:939(check_make_to_stock)
       27 0.003 0.000 67.023 2.482 /openerp-server/bin/addons/mrp/mrp.py:1025(_check_make_to_stock_product)
     6983 29.313 0.004 65.240 0.009 /usr/lib/python2.5/inspect.py:408(getmodule)
     2887 0.049 0.000 47.001 0.016 /usr/lib/python2.5/inspect.py:454(findsource)
5778/5774 0.177 0.000 43.856 0.008 /usr/lib/python2.5/inspect.py:381(getsourcefile)
 12041505 18.983 0.000 26.680 0.000 /usr/lib/python2.5/inspect.py:35(ismodule)
 12223925 7.889 0.000 7.889 0.000 {isinstance}

--- PROFILING WITH PROFILE - CALLERS -------------------------------------------------------------

Ok, now we now that a lot of time is spent on the __call__ method of GettextAlias (that has _ as an alias/shortcut). Now the question is: who is calling it and how many times? and the answer is:

>>> stats.sort_stats("t").print_callers('__call__')
   Ordered by: internal time
   List reduced from 333 to 2 due to restriction <'__call__'>

Function was called by...
                                                                                            ncalls tottime cumtime
/openerp-server/bin/tools/translate.py:128(__call__) <- 1 0.000 2.816 /openerp-server/bin/addons/mrp/mrp.py:1018(action_move_assigned)
                                                                                                27 0.001 66.094 /openerp-server/bin/addons/mrp/mrp.py:1025(_check_make_to_stock_product)
/openerp-server/bin/tools/misc.py:941(__call__) <- 1 0.000 78.911 <string>:1(<module>)

So, _() is called 27 times from _check_make_to_stock_product (taking 66 seconds), and one time from action_move_assigned (taking 2.8 seconds). The _check_make_to_stock_product method looks like this:

    def _check_make_to_stock_product(self, cr, uid, procurement, context={}):
        ok = True
        if procurement.move_id:
            id = procurement.move_id.id
            if not (procurement.move_id.state in ('done','assigned','cancel')):
                ok = ok and self.pool.get('stock.move').action_assign(cr, uid, [id])
                cr.execute('select count(id) from stock_warehouse_orderpoint where product_id=%s', (procurement.product_id.id,))
                if not cr.fetchone()[0]:
                    cr.execute('update mrp_procurement set message=%s where id=%s', (_('from stock and no minimum orderpoint rule defined'), procurement.id))
        return ok

And the sentence that is taking one second per call is the single "_('from stock and no minimum orderpoint rule defined')".

--- PROFILING WITH TRANSLATE() INSTEAD OF _() -------------------------------------------------------------

If we replace this line (that uses _):
                    cr.execute('update mrp_procurement set message=%s where id=%s', (_('from stock and no minimum orderpoint rule defined'), procurement.id))
With this one (that uses translate):
                    cr.execute('update mrp_procurement set message=%s where id=%s', (translate(cr, None, 'code', context.get('lang'), 'from stock and no minimum orderpoint rule defined'), procurement.id))

Then the time (with profiling enabled) is reduced to 10.498 seconds (instead of 68 seconds)!!!:

>>> stats2.sort_stats("cumulative").print_stats()
Wed Aug 25 17:12:19 2010 prof2.dat

         900222 function calls (896164 primitive calls) in 10.494 CPU seconds

   Ordered by: cumulative time

   ncalls tottime percall cumtime percall filename:lineno(function)
        1 0.000 0.000 10.498 10.498 /srv/openerp/openerp5/branches/5.0/openerp-server/bin/tools/misc.py:941(__call__)
        1 0.001 0.001 10.498 10.498 /srv/openerp/openerp5/branches/5.0/openerp-server/bin/addons/sale/sale.py:570(action_ship_create_orig)
   303/13 0.011 0.000 9.967 0.767 /home/omar/Escritorio/openerp-server/5.0/bin/workflow/workitem.py:44(process)
     37/4 0.001 0.000 9.951 2.488 /srv/openerp/openerp5/branches/5.0/openerp-server/bin/workflow/wkf_service.py:75(trg_validate)
     37/4 0.001 0.000 9.946 2.487 /home/omar/Escritorio/openerp-server/5.0/bin/workflow/instance.py:45(validate)
   220/12 0.011 0.000 9.939 0.828 /home/omar/Escritorio/openerp-server/5.0/bin/workflow/workitem.py:148(_split_test)
    142/8 0.007 0.000 9.938 1.242 /home/omar/Escritorio/openerp-server/5.0/bin/workflow/workitem.py:34(create)
    105/4 0.003 0.000 9.913 2.478 /home/omar/Escritorio/openerp-server/5.0/bin/workflow/workitem.py:179(_join_test)
   243/28 0.004 0.000 9.718 0.347 /home/omar/Escritorio/openerp-server/5.0/bin/workflow/wkf_expr.py:44(_eval_expr)
   208/24 0.017 0.000 9.715 0.405 {eval}
   181/24 0.003 0.000 9.695 0.404 /srv/openerp/openerp5/branches/5.0/openerp-server/bin/osv/orm.py:167(<lambda>)
   142/20 0.002 0.000 9.501 0.475 /home/omar/Escritorio/openerp-server/5.0/bin/workflow/workitem.py:86(_execute)
    72/10 0.001 0.000 9.457 0.946 /home/omar/Escritorio/openerp-server/5.0/bin/workflow/wkf_expr.py:67(execute)
        3 0.000 0.000 9.165 3.055 /srv/openerp/openerp5/branches/5.0/openerp-server/bin/addons/cc_mrp_packing_bom/mrp_procurement.py:32(action_produce_assign_product)
        3 0.001 0.000 9.117 3.039 /srv/openerp/openerp5/branches/5.0/openerp-server/bin/addons/mrp/mrp.py:1041(action_produce_assign_product)
        3 0.011 0.004 8.288 2.763 /srv/openerp/openerp5/branches/5.0/openerp-server/bin/addons/mrp/mrp.py:630(action_confirm)
     8382 0.054 0.000 6.611 0.001 /srv/openerp/openerp5/branches/5.0/openerp-server/bin/sql_db.py:73(wrapper)
     4624 0.278 0.000 6.540 0.001 /srv/openerp/openerp5/branches/5.0/openerp-server/bin/sql_db.py:109(execute)
5114/5026 0.378 0.000 5.786 0.001 /srv/openerp/openerp5/branches/5.0/openerp-server/bin/osv/orm.py:154(__getitem__)
4866/4788 0.033 0.000 5.635 0.001 /srv/openerp/openerp5/branches/5.0/openerp-server/bin/osv/orm.py:237(__getattr__)
     4624 5.130 0.001 5.201 0.001 {method 'execute' of 'psycopg2._psycopg.cursor' objects}
1328/1140 0.382 0.000 5.000 0.004 /srv/openerp/openerp5/branches/5.0/openerp-server/bin/osv/orm.py:2221(read)
1409/1140 0.444 0.000 4.359 0.004 /srv/openerp/openerp5/branches/5.0/openerp-server/bin/osv/orm.py:2256(_read_flat)
      124 0.062 0.001 3.053 0.025 /srv/openerp/openerp5/branches/5.0/openerp-server/bin/osv/orm.py:2742(create)
  442/440 0.004 0.000 2.587 0.006 /home/omar/Escritorio/openerp-server/5.0/bin/workflow/wkf_expr.py:70(check)
  131/125 0.025 0.000 1.499 0.012 /srv/openerp/openerp5/branches/5.0/openerp-server/bin/osv/orm.py:2506(write)
       30 0.003 0.000 1.428 0.048 /srv/openerp/openerp5/branches/5.0/openerp-server/bin/addons/mrp/mrp.py:991(action_confirm)
       27 0.001 0.000 1.341 0.050 /srv/openerp/openerp5/branches/5.0/openerp-server/bin/addons/mrp/mrp.py:939(check_make_to_stock)
    50/48 0.001 0.000 1.133 0.024 /srv/openerp/openerp5/branches/5.0/openerp-server/bin/osv/fields.py:654(get)
       40 0.002 0.000 1.112 0.028 /srv/openerp/openerp5/branches/5.0/openerp-server/bin/osv/orm.py:2943(_store_set_values)
       27 0.003 0.000 0.976 0.036 /srv/openerp/openerp5/branches/5.0/openerp-server/bin/addons/mrp/mrp.py:1025(_check_make_to_stock_product)

Related branches

Revision history for this message
Albert Cervera i Areny - http://www.NaN-tic.com (albert-nan) wrote :

Borja, that is pretty strange. These are the times it takes to calculate a couple of translations (with SQL query included) in my system:

0.00142478942871 seconds
0.00103211402893 seconds

Maybe inspect is exponentially slower when the number of calls in the backtrace increases (I just tested with a simple raise osv.except_osv I had in hand). Can you try with such a simple case?

Revision history for this message
Borja López Soilán (NeoPolus) (borjals) wrote :

Albert, stack() seems to be O(n) with n being the number stack items. See this example:

----------------------------
$ cat test-python-inspect.py

import inspect
import time

def test(n, maxn):
    start = time.time()
    frame = inspect.stack()[1][0]
    stop = time.time()
    if n % 100 == 0:
        print "Test time (n=%s): " % n, stop-start
    if n <= maxn:
        return test(n+1, maxn)
    else:
        return n

test(1, 500)

$ python test-python-inspect.py
Test time (n=100): 0.0100071430206
Test time (n=200): 0.0189220905304
Test time (n=300): 0.0286138057709
Test time (n=400): 0.0375599861145
Test time (n=500): 0.0474269390106

----------------------------

But anyway, on my simple example it takes less than 0.05 seconds per call in the 500th recursive call, so I must be missing something: why does it takes so much when called from OpenERP? threading stalls or something?

Revision history for this message
Borja López Soilán (NeoPolus) (borjals) wrote :

By the way, we just inspected the stack ourselves and found than when the _() method is called, the stack length is greater than 100. Yep, more than 100 calls!

We found that most of the stack items are related to four bin/workflow/workitem.py methods: create, process, _split_test and _join_test that appear again and again in a kind of recursive way.

Revision history for this message
Albert Cervera i Areny - http://www.NaN-tic.com (albert-nan) wrote : Re: [Bug 624114] Re: [5.0] Translation (GettextAlias _() ) performance bug? too slow to be usable (>1 sec per call)

A Dijous, 26 d'agost de 2010, Borja López Soilán (Pexego) va escriure:
> But anyway, on my simple example it takes less than 0.05 seconds per
> call in the 500th recursive call, so I must be missing something: why
> does it takes so much when called from OpenERP? threading stalls or
> something?

In my case I tried it with OpenERP and it was fast, so it must be something
specific to your test case...

--
Albert Cervera i Areny
http://www.NaN-tic.com
OpenERP Partners
Mòbil: +34 669 40 40 18

http://twitter.com/albertnan
http://albert-nan.blogspot.com

Revision history for this message
Borja López Soilán (NeoPolus) (borjals) wrote : Re: [5.0] Translation (GettextAlias _() ) performance bug? too slow to be usable (>1 sec per call)

I don't now if it is specific to MRP with JIT, but I've been printing the times of the inspect.stack() call on a normal OpenERP usage and it seems that the times are not deterministic at all:

GettextAlias time: 0.0242729187012
GettextAlias time: 0.0695328712463
GettextAlias time: 0.0346441268921
GettextAlias time: 0.108239173889
GettextAlias time: 0.0100328922272
GettextAlias time: 0.0330319404602
GettextAlias time: 0.0410368442535
GettextAlias time: 0.00120401382446
GettextAlias time: 0.00120115280151
GettextAlias time: 0.0012469291687

The average on those ten calls is 0.032; but some of them took 0.001 while others took 0.1 (100 times more!).

I'm linking this bug to the addons too, just in case it has something to do with how the MRP works, and so somebody at least replaces the _() call with a translate() call in that single point...

Revision history for this message
Borja López Soilán (NeoPolus) (borjals) wrote :

By the way, maybe we should provide an option to override the whole inspection part.

I mean, maybe we could allow an optional parameter for _( ) with the language to use, something like: _("Translate this", 'es_ES')

That would allow to workaround this kind of performance problems with small changes on our code. Instead of:

      cr.execute('update mrp_procurement set message=%s where id=%s', (_('from stock and no minimum orderpoint rule defined'), procurement.id))

We would use:

      cr.execute('update mrp_procurement set message=%s where id=%s', (_('from stock and no minimum orderpoint rule defined', context.get('lang')), procurement.id))

It would be much quicker, and it would be clean!

Revision history for this message
Dukai Gábor (gdukai) wrote :

In our modules, we could use a 5-line replacement like this:
    def my_method(self, cr, uid, ids, context=None):
        def _(source):
            lang = (context or {}).get('lang', False)
            if not lang:
                return source
            return translate(cr, False, 'code', lang, source) or source

Revision history for this message
Albert Cervera i Areny - http://www.NaN-tic.com (albert-nan) wrote : Re: [Bug 624114] Re: [5.0] Translation (GettextAlias _() ) performance bug? too slow to be usable (>1 sec per call)
Download full text (10.4 KiB)

A Dilluns, 30 d'agost de 2010, Dukai Gábor va escriure:
> In our modules, we could use a 5-line replacement like this:
> def my_method(self, cr, uid, ids, context=None):
> def _(source):
> lang = (context or {}).get('lang', False)
> if not lang:
> return source
> return translate(cr, False, 'code', lang, source) or source

Which sounds terrible. I think that adding an optional parameter to standard
_() like Borja said, is much much better.

>
> >>> stats.sort_stats("cumulative").print_stats()
>
> Wed Aug 25 16:53:45 2010 prof.dat
>
> 37130567 function calls (37126505 primitive calls) in 78.907 CPU
> seconds
>
> Ordered by: cumulative time
>
> ncalls tottime percall cumtime percall filename:lineno(function)
> 1 0.000 0.000 78.911 78.911
> /openerp-server/bin/tools/misc.py:941(__call__) 1 0.001 0.001
> 78.911 78.911
> /openerp-server/bin/addons/sale/sale.py:570(action_ship_create_orig)
> 303/13 0.010 0.000 78.396 6.030
> /openerp-server/bin/workflow/workitem.py:44(process) 37/4 0.001
> 0.000 78.372 19.593
> /openerp-server/bin/workflow/wkf_service.py:75(trg_validate) 142/8
> 0.007 0.000 78.369 9.796
> /openerp-server/bin/workflow/workitem.py:34(create) 220/12 0.010
> 0.000 78.367 6.531
> /openerp-server/bin/workflow/workitem.py:148(_split_test) 37/4 0.001
> 0.000 78.367 19.592
> /openerp-server/bin/workflow/instance.py:45(validate) 105/4 0.003
> 0.000 78.336 19.584
> /openerp-server/bin/workflow/workitem.py:179(_join_test) 243/28 0.004
> 0.000 78.143 2.791
> /openerp-server/bin/workflow/wkf_expr.py:44(_eval_expr) 208/24 0.016
> 0.000 78.140 3.256 {eval}
> 181/24 0.003 0.000 78.120 3.255
> /openerp-server/bin/osv/orm.py:167(<lambda>) 142/20 0.002 0.000
> 77.939 3.897 /openerp-server/bin/workflow/workitem.py:86(_execute)
> 72/10 0.001 0.000 77.895 7.789
> /openerp-server/bin/workflow/wkf_expr.py:67(execute) 3 0.000 0.000
> 77.622 25.874
> /openerp-server/bin/addons/cc_mrp_packing_bom/mrp_procurement.py:32(action
> _produce_assign_product) 3 0.001 0.000 77.578 25.859
> /openerp-server/bin/addons/mrp/mrp.py:1041(action_produce_assign_product)
> 3 0.010 0.003 76.721 25.574
> /openerp-server/bin/addons/mrp/mrp.py:630(action_confirm) 28 0.002
> 0.000 68.910 2.461
> /openerp-server/bin/tools/translate.py:128(__call__)
> *********************************** 28 0.000 0.000 68.909 2.461
> /usr/lib/python2.5/inspect.py:882(stack) 28 0.025 0.001 68.909
> 2.461 /usr/lib/python2.5/inspect.py:858(getouterframes) 2887 0.057
> 0.000 68.881 0.024 /usr/lib/python2.5/inspect.py:820(getframeinfo)
> 442/440 0.004 0.000 68.565 0.156
> /openerp-server/bin/workflow/wkf_expr.py:70(check) 27 0.001 0.000
> 67.363 2.495
> /openerp-server/bin/addons/mrp/mrp.py:939(check_make_to_stock) 27 0.003
> 0.000 67.023 2.482
> /openerp-server/bin/addons/mrp/mrp.py:1025(_check_make_to_stock_product)
> 6983 29.313 0.004 65.240 0.009
...

Revision history for this message
xrg (xrg) wrote :

On Monday 30 August 2010, you wrote:
> By the way, maybe we should provide an option to override the whole
> inspection part.
>
> I mean, maybe we could allow an optional parameter for _( ) with the
> language to use, something like: _("Translate this", 'es_ES')

I would vote against that. The _("...") should really behave the gettext
way, just localize the string. If we needed some other fn, yes, that
would be the translate(), with more arguments.

Please, try that patch I have written today, and see if it performs
any better..

http://git.hellug.gr/?p=xrg/openobject-server;a=commitdiff;h=8817593e29d00dd672d552

Revision history for this message
Albert Cervera i Areny - http://www.NaN-tic.com (albert-nan) wrote : Re: [5.0] Translation (GettextAlias _() ) performance bug? too slow to be usable (>1 sec per call)

Borja, did you try xrg's patch? It seems it may go to the right direction.

Revision history for this message
Borja López Soilán (NeoPolus) (borjals) wrote :

Thanks xrg, we haven't tested the patch yet (holidays...), but I've taken a look to the diff file.

As Albert says, your patch might make a difference: As you detected, "frame = inspect.stack()[1][0]" is getting the full stack (with all the variables, parameters and such) into a list, just to discard all the info and get only the second element (the parent frame). So the performance of that like is "O(n)" depending on the stack size (n frames inspected), while your patch would be just "O(2)" (just two frames inspected, always).

We would test it, I wonder how much it may improve...

Changed in openobject-addons:
status: New → Triaged
Changed in openobject-server:
status: New → Triaged
Revision history for this message
Olivier Dony (Odoo) (odo-openerp) wrote :

Closing for addons, as this really should be handled on the server side

summary: - [5.0] Translation (GettextAlias _() ) performance bug? too slow to be
- usable (>1 sec per call)
+ Translation (GettextAlias _() ) performance bug? too slow to be usable
+ (>1 sec per call)
Changed in openobject-addons:
status: Triaged → Invalid
Revision history for this message
Olivier Dony (Odoo) (odo-openerp) wrote :

Basically we need to stop using 'inspect' just to access the last frames, because it is really only meant for debugging.
Obviously it will be O(n) according to the size of the stack, but it will also hit the disk to lookup line numbers in source code!

This refactoring is pending and will be merged before RC2.

Thanks for reporting!

Changed in openobject-server:
assignee: nobody → OpenERP's Framework R&D (openerp-dev-framework)
importance: Undecided → Medium
milestone: none → 6.0-rc2
status: Triaged → Confirmed
Revision history for this message
Olivier Dony (Odoo) (odo-openerp) wrote :

BTW Borja, great job with the profiling and analysis, muchas gracias :-)

xrg (xrg)
Changed in openobject-server:
status: Confirmed → In Progress
Revision history for this message
Olivier Dony (Odoo) (odo-openerp) wrote :

This should be vastly improved after server revision 3114 <email address hidden> in trunk.

The call to inspect.stack() has been removed, the heuristics to locate the language and cursor have been improved, and we now even attempt to use the cache from ir.translation if available, by calling _get_source instead of hitting the DB every time.

Profiling shows a nice difference, for example during a simple call to action_ship_create() with mrp_jit (as in your example), I see this difference:

Revision 3112: 4.17s, with __call__ (translate.py:183), 11 calls, cumulative time: 2.92918s (70.19% total time)
Revision 3114: 1.07s, with __call__ (translate.py:200), 11 calls, cumulative time: 0.00371s (00.36% total time)

Notice the difference in the percentage of the total time used by _( ), not just the decreased overall duration.

Changed in openobject-server:
status: In Progress → Fix Released
Revision history for this message
Borja López Soilán (NeoPolus) (borjals) wrote :

Wow, it looks like a great improvement, good job Olivier :)

Revision history for this message
Borja López Soilán (NeoPolus) (borjals) wrote :

By the way, thanks also to xrg and everyone else that contributed ;)

Revision history for this message
Albert Cervera i Areny - http://www.NaN-tic.com (albert-nan) wrote : Re: [Bug 624114] Re: Translation (GettextAlias _() ) performance bug? too slow to be usable (>1 sec per call)
Download full text (10.2 KiB)

A Dijous, 16 de desembre de 2010, Borja López Soilán va escriure:
> By the way, thanks also to xrg and everyone else that contributed ;)

+1

By the way, I'd be nice if _() was exported to globals and we didn't need to
import it in each python file!

>
> >>> stats.sort_stats("cumulative").print_stats()
>
> Wed Aug 25 16:53:45 2010 prof.dat
>
> 37130567 function calls (37126505 primitive calls) in 78.907 CPU
> seconds
>
> Ordered by: cumulative time
>
> ncalls tottime percall cumtime percall filename:lineno(function)
> 1 0.000 0.000 78.911 78.911
> /openerp-server/bin/tools/misc.py:941(__call__) 1 0.001 0.001
> 78.911 78.911
> /openerp-server/bin/addons/sale/sale.py:570(action_ship_create_orig)
> 303/13 0.010 0.000 78.396 6.030
> /openerp-server/bin/workflow/workitem.py:44(process) 37/4 0.001
> 0.000 78.372 19.593
> /openerp-server/bin/workflow/wkf_service.py:75(trg_validate) 142/8
> 0.007 0.000 78.369 9.796
> /openerp-server/bin/workflow/workitem.py:34(create) 220/12 0.010
> 0.000 78.367 6.531
> /openerp-server/bin/workflow/workitem.py:148(_split_test) 37/4 0.001
> 0.000 78.367 19.592
> /openerp-server/bin/workflow/instance.py:45(validate) 105/4 0.003
> 0.000 78.336 19.584
> /openerp-server/bin/workflow/workitem.py:179(_join_test) 243/28 0.004
> 0.000 78.143 2.791
> /openerp-server/bin/workflow/wkf_expr.py:44(_eval_expr) 208/24 0.016
> 0.000 78.140 3.256 {eval}
> 181/24 0.003 0.000 78.120 3.255
> /openerp-server/bin/osv/orm.py:167(<lambda>) 142/20 0.002 0.000
> 77.939 3.897 /openerp-server/bin/workflow/workitem.py:86(_execute)
> 72/10 0.001 0.000 77.895 7.789
> /openerp-server/bin/workflow/wkf_expr.py:67(execute) 3 0.000 0.000
> 77.622 25.874
> /openerp-server/bin/addons/cc_mrp_packing_bom/mrp_procurement.py:32(action
> _produce_assign_product) 3 0.001 0.000 77.578 25.859
> /openerp-server/bin/addons/mrp/mrp.py:1041(action_produce_assign_product)
> 3 0.010 0.003 76.721 25.574
> /openerp-server/bin/addons/mrp/mrp.py:630(action_confirm) 28 0.002
> 0.000 68.910 2.461
> /openerp-server/bin/tools/translate.py:128(__call__)
> *********************************** 28 0.000 0.000 68.909 2.461
> /usr/lib/python2.5/inspect.py:882(stack) 28 0.025 0.001 68.909
> 2.461 /usr/lib/python2.5/inspect.py:858(getouterframes) 2887 0.057
> 0.000 68.881 0.024 /usr/lib/python2.5/inspect.py:820(getframeinfo)
> 442/440 0.004 0.000 68.565 0.156
> /openerp-server/bin/workflow/wkf_expr.py:70(check) 27 0.001 0.000
> 67.363 2.495
> /openerp-server/bin/addons/mrp/mrp.py:939(check_make_to_stock) 27 0.003
> 0.000 67.023 2.482
> /openerp-server/bin/addons/mrp/mrp.py:1025(_check_make_to_stock_product)
> 6983 29.313 0.004 65.240 0.009
> /usr/lib/python2.5/inspect.py:408(getmodule) 2887 0.049 0.000
> 47.001 0.016 /usr/lib/python2.5/inspect.py:454(findsource) 5778/5774
> 0.177 0.000 43.856 0.008
> /usr/lib/python2.5/inspect.py:381(getsourcefile) 12041505 18.983
> ...

Revision history for this message
Xavier (Open ERP) (xmo-deactivatedaccount) wrote :

albert, I think you mean builtins (globals is module-global scope so it would not help much if any). But this should be in a different ticket.

Revision history for this message
Albert Cervera i Areny - http://www.NaN-tic.com (albert-nan) wrote :
Download full text (10.1 KiB)

A Dijous, 16 de desembre de 2010, Xavier (Open ERP) va escriure:
> albert, I think you mean builtins (globals is module-global scope so it
> would not help much if any). But this should be in a different ticket.

That's it :)

>
> >>> stats.sort_stats("cumulative").print_stats()
>
> Wed Aug 25 16:53:45 2010 prof.dat
>
> 37130567 function calls (37126505 primitive calls) in 78.907 CPU
> seconds
>
> Ordered by: cumulative time
>
> ncalls tottime percall cumtime percall filename:lineno(function)
> 1 0.000 0.000 78.911 78.911
> /openerp-server/bin/tools/misc.py:941(__call__) 1 0.001 0.001
> 78.911 78.911
> /openerp-server/bin/addons/sale/sale.py:570(action_ship_create_orig)
> 303/13 0.010 0.000 78.396 6.030
> /openerp-server/bin/workflow/workitem.py:44(process) 37/4 0.001
> 0.000 78.372 19.593
> /openerp-server/bin/workflow/wkf_service.py:75(trg_validate) 142/8
> 0.007 0.000 78.369 9.796
> /openerp-server/bin/workflow/workitem.py:34(create) 220/12 0.010
> 0.000 78.367 6.531
> /openerp-server/bin/workflow/workitem.py:148(_split_test) 37/4 0.001
> 0.000 78.367 19.592
> /openerp-server/bin/workflow/instance.py:45(validate) 105/4 0.003
> 0.000 78.336 19.584
> /openerp-server/bin/workflow/workitem.py:179(_join_test) 243/28 0.004
> 0.000 78.143 2.791
> /openerp-server/bin/workflow/wkf_expr.py:44(_eval_expr) 208/24 0.016
> 0.000 78.140 3.256 {eval}
> 181/24 0.003 0.000 78.120 3.255
> /openerp-server/bin/osv/orm.py:167(<lambda>) 142/20 0.002 0.000
> 77.939 3.897 /openerp-server/bin/workflow/workitem.py:86(_execute)
> 72/10 0.001 0.000 77.895 7.789
> /openerp-server/bin/workflow/wkf_expr.py:67(execute) 3 0.000 0.000
> 77.622 25.874
> /openerp-server/bin/addons/cc_mrp_packing_bom/mrp_procurement.py:32(action
> _produce_assign_product) 3 0.001 0.000 77.578 25.859
> /openerp-server/bin/addons/mrp/mrp.py:1041(action_produce_assign_product)
> 3 0.010 0.003 76.721 25.574
> /openerp-server/bin/addons/mrp/mrp.py:630(action_confirm) 28 0.002
> 0.000 68.910 2.461
> /openerp-server/bin/tools/translate.py:128(__call__)
> *********************************** 28 0.000 0.000 68.909 2.461
> /usr/lib/python2.5/inspect.py:882(stack) 28 0.025 0.001 68.909
> 2.461 /usr/lib/python2.5/inspect.py:858(getouterframes) 2887 0.057
> 0.000 68.881 0.024 /usr/lib/python2.5/inspect.py:820(getframeinfo)
> 442/440 0.004 0.000 68.565 0.156
> /openerp-server/bin/workflow/wkf_expr.py:70(check) 27 0.001 0.000
> 67.363 2.495
> /openerp-server/bin/addons/mrp/mrp.py:939(check_make_to_stock) 27 0.003
> 0.000 67.023 2.482
> /openerp-server/bin/addons/mrp/mrp.py:1025(_check_make_to_stock_product)
> 6983 29.313 0.004 65.240 0.009
> /usr/lib/python2.5/inspect.py:408(getmodule) 2887 0.049 0.000
> 47.001 0.016 /usr/lib/python2.5/inspect.py:454(findsource) 5778/5774
> 0.177 0.000 43.856 0.008
> /usr/lib/python2.5/inspect.py:381(getsourcefile) 12041505 18.983
> 0.000 26.680 0.000 /u...

To post a comment you must log in.
This report contains Public information  
Everyone can see this information.

Other bug subscribers

Remote bug watches

Bug watches keep track of this bug in other bug trackers.