relative speed of incremention syntaxes (or "i=i+1" VS "i+=1")

L

Laurent Payot

I made Python my language of choice because of its readability and simpleness, and not because of its speed. But it's always good to know what is the fastest sometimes when you don't want to write a module in C. So I was justwondering if there was a difference. There is, of a few percent. Anyway I will keep on using the 2% slower "i += 1" because for me that's less prone to errors because you write the variable only once, and that's more important than speed.
 
A

Andreas Löscher

Am Sonntag, den 21.08.2011, 19:38 -0400 schrieb Terry Reedy:
A jump or branch table is applicable when the case value values are all
small ints, like bytes or less. For C, the table is simply an array of
pointers (addressess, with entries for unused byte codes would be a void
pointer). Hence, O(1) access.
https://secure.wikimedia.org/wikipedia/en/wiki/Jump_table


add jump to address of the code for PRINT_NEWLINE

:) too easy or too late
thanks
 
T

Terry Reedy

If the value on the left has an __iadd__ method, that will be called;
Correct

the value will be updated in-place,

Not necessarily correct. The target is rebound to the return from the
__iadd__ method. Augmented *assignment* is always assignment. This trips
up people who try

t = (1, [])
t[1] += [1,2] # which *does* extend the array, but raises
TypeError: 'tuple' object does not support item assignment
# instead of
t[1].extend([1,2]) # which extends without raising an error


*IF* (and only if) the target object is mutable, then the __iadd__ may
optionally mutate the target object. But the rebinding takes place
nonetheless. Numbers, the subject of this thread, are not mutable and
are not 'updated in-place'

class test:
def __init__(self, n):
self.n = n
def __iadd__(self, other):
return test(self.n + other.n)
def __repr__(self):
return repr(self.n)

r = test(1)
t = r
t += r
print(r, t)
# 1,2

That said, there is normally no reason to write an __ixxx__ method
unless instances are mutable and the operation can be and is done in
place therein. So the class above is for illustrative purposes only. A
saner example is the following, which treats test examples as mutable
number containers rather than as immutable surrogates.

class test:
def __init__(self, n):
self.n = n
def __add__(self, other):
return test(self.n + other.n)
def __iadd__(self, other):
n = self.n + other.n
self.n = n
return n
def __repr__(self):
return repr(self.n)

r = test(1)
t = r
t += r
print(r, t)
# 2 2

The interpreter cannot enforce that 'x += a' have the same effect as 'x
= x+a', but it would break normal expectations to make the two different.

so all references to that object will be affected:

Only if the target object is mutable and is mutated by the optional
augmented assignment __ixxx__ methods.
> import numpy as np
> a = np.zeros(3)

Numpy arrays meet the qualification above.
If the value on the left doesn't have an __iadd__ method, then addition is
performed and the name is re-bound to the result:

As is also done with the results of __ixxx__ methods.
 
S

Steven D'Aprano

Laurent said:
For floats it is understandable. But for integers, seriously, 4% is a lot.
I would never have thought an interpreter would have differences like this
in syntax for something as fundamental as adding 1.

Why? Python integers are rich objects, not native ints. Adding 1 is a
moderately heavyweight operation far more complicated than the same
operation in C.

n=n+1 and n+=1 call different methods and do different things, they are
*not* just different syntax. Your intuition about what should and shouldn't
take the same time should not be trusted.

But really, we're talking about tiny differences in speed. Such trivial
differences are at, or beyond, the limit of what can realistically be
measured on a noisy PC running multiple processes (pretty much all PCs
these days). Here are three runs of each on my computer:


[steve@sylar python]$ python2.5 -m timeit -s 'n=0' 'n = n+1'
1000000 loops, best of 3: 0.508 usec per loop
[steve@sylar python]$ python2.5 -m timeit -s 'n=0' 'n = n+1'
1000000 loops, best of 3: 0.587 usec per loop
[steve@sylar python]$ python2.5 -m timeit -s 'n=0' 'n = n+1'
1000000 loops, best of 3: 0.251 usec per loop


[steve@sylar python]$ python2.5 -m timeit -s 'n=0' 'n += 1'
1000000 loops, best of 3: 0.226 usec per loop
[steve@sylar python]$ python2.5 -m timeit -s 'n=0' 'n += 1'
1000000 loops, best of 3: 0.494 usec per loop
[steve@sylar python]$ python2.5 -m timeit -s 'n=0' 'n += 1'
1000000 loops, best of 3: 0.53 usec per loop

Look at the variation between runs! About 130% variation between the fastest
and slowest for each expression. And there's no reason to think that the
fastest results shown is as fast as it can get. The time is dominated by
noise, not the addition.


For what it's worth, if I try it with a more recent Python:

[steve@sylar python]$ python3.2 -m timeit -s 'n=0' 'n = n+1'
1000000 loops, best of 3: 0.221 usec per loop
[steve@sylar python]$ python3.2 -m timeit -s 'n=0' 'n = n+1'
1000000 loops, best of 3: 0.202 usec per loop
[steve@sylar python]$ python3.2 -m timeit -s 'n=0' 'n = n+1'
1000000 loops, best of 3: 0.244 usec per loop

[steve@sylar python]$ python3.2 -m timeit -s 'n=0' 'n += 1'
1000000 loops, best of 3: 0.49 usec per loop
[steve@sylar python]$ python3.2 -m timeit -s 'n=0' 'n += 1'
1000000 loops, best of 3: 0.176 usec per loop
[steve@sylar python]$ python3.2 -m timeit -s 'n=0' 'n += 1'
1000000 loops, best of 3: 0.49 usec per loop


I simply do not believe that we can justify making *any* claim about the
relative speeds of n=n+1 and n+=1 other than "they are about the same". Any
result you get, faster or slower, will depend more on chance than on any
real or significant difference in the code.
 
S

Stephen Hansen

For floats it is understandable. But for integers, seriously, 4% is a lot. I would never have thought an interpreter would have differences likethis in syntax for something as fundamental as adding 1.

Its, seriously, not even kind of a lot at all. Percentages without
context are meaningless: its 4% slower, sure -- but that is 4% of an
incredibly small probably constant-time amount of time.

Picking "i += 1" over "i = i + 1" based on one being 4% slower is sorta
kinda crazy. The difference in speed is probably related to churn and
cache as much as anything else (its not as consistent on my machine, for
example): or the ceval loop doing a few more case-tests between them as
others have mentioned. All in all, if 4% of a nanomicrofraction of a
chunk of time is that meaningful, you're probably best served not using
Python. :)

That said: my advice is always to avoid += like a plague. It is magic
and impossible to predict without intimate knowledge of exactly what's
on the left-side.

i += 1
n += x

Those two things look very similar, but they may do -completely-
different things depending on just what "n" is.

It may or may not do something that is like:

n = n + x

Or, it may do something that's more akin to

n.extend(x)
n = n

Those aren't even kind of equivalent actions. And things get more
complicated if 'n' is say, n[0] (especially if something goes wrong
between the extend and the rebinding).

Python's usually all explicit and pretty well-defined in how its basic
syntax and behaviors operate, and you usually don't really have to know
details about how a data-type works to predict exactly what it's doing:
in fact, its often beneficial to not pay too much attention to such
details, and just assume the data type will work approximately as you'd
expect. That way people can slip something-something to you and wink and
say of /course/ its a dict, darling. Try it, you'll like it, okay? This
sorta thing is encouraged, but it kinda depends on trusting objects to
behave a certain way and for things to be predictable in both how they
work and how they fail.

With "i = i + 1", I know that generally speaking, my "i" is being
assigned a new object and that's that, no matter what type "i" is.
(Okay: I do know that you could modify __add__ to do something
underhanded here, tweaking internal state and then returning self.
People going out of their way to behave unpredictably is not my
objection: supposedly easy and straight-forward normal Python-fu being
inherently unpredictable is).

For example: I just /know/ that it doesn't matter who or what may have
their own binding to that object before I go and increment it, they
won't be affected and everything just will work fine. With augmented
assignment, I can't be sure of that. Now, while I admit, you generally
do have to keep track in your head of which of your data-types are
mutable vs immutable and take care with sharing mutables, the fact that
"n += x" is described and generally thought of as merely syntactical
sugar for:

n = n + x

... lets one easily think that this should be entirely safe, even with
mutable objects, because if += were merely syntactical sugar, it would
be. But its not! Because += is wiggly. It can do more then one entirely
different kind of behavior.

Anyways. </rant> I've been kinda annoyed at augmented assignment for
years now :p

--

Stephen Hansen
... Also: Ixokai
... Mail: me+list/python (AT) ixokai (DOT) io
... Blog: http://meh.ixokai.io/


-----BEGIN PGP SIGNATURE-----
Version: GnuPG v2.0.10 (Darwin)

iQEcBAEBAgAGBQJOUboaAAoJEKcbwptVWx/l0bUH/0XTvNSVtbZopA0ZKdRIUqZv
iOmCyX22PK7MSc90NrTp/uoLUsZEyUt/hIEWmj3EIyDFyFb3VbNyHkXNzqDc3DXy
wKzhbt7/wcL3Zoxx8uai1BX6+wHgfrR+ycVf43hhtorCL0LEoERLA70Tgkmsl7a0
oxAGqhTl7WRcLJ7CA9Ayza7VIloWVbrWZGajHr/8ZMcNXtdsD4td0XwAOTIe5Q0k
H8aPEYTlaqaSe8sZgcBNvMWqUiR3J2c2tm2DUGrjyBW/51Q68UT5tj9kno+lyVGg
CqKq3HCnLOAkaIMDCFSu7x9xyOsPqqjSH8dcLnfiKYpUtfMBYkgJgDksAM7OPd0=
=zsLA
-----END PGP SIGNATURE-----
 
T

Terry Reedy

Agreed. If Python needed a faster alternative to "a=a+1", then I would
recommend an "a.inc()" call or something

But looking up the method name, creating a bound method wrapper, and
making the call would probably be slower than the syntax;-).
 
T

Terry Reedy

I made Python my language of choice because of its readability and
simpleness, and not because of its speed. But it's always good to
know what is the fastest sometimes when you don't want to write a
module in C. So I was just wondering if there was a difference. There
is, of a few percent. Anyway I will keep on using the 2% slower "i +=
1" because for me that's less prone to errors because you write the
variable only once, and that's more important than speed.

For longer variable names, it is also easier and faster to read once one
gets used to the idiom.

number_of_chars += 1 # versus
number_of_chars = number_of_chars + 1

Not repeating was a major reason for the addition.
 
S

Steven D'Aprano

Picking "i += 1" over "i = i + 1" based on one being 4% slower is sorta
kinda crazy. The difference in speed is probably related to churn and
cache as much as anything else (its not as consistent on my machine, for
example): or the ceval loop doing a few more case-tests between them as
others have mentioned. All in all, if 4% of a nanomicrofraction of a
chunk of time is that meaningful, you're probably best served not using
Python. :)
Agreed.

But...

That said: my advice is always to avoid += like a plague. It is magic
and impossible to predict without intimate knowledge of exactly what's
on the left-side.

i += 1
n += x

Those two things look very similar, but they may do -completely-
different things depending on just what "n" is.


Technically, the *exact same criticism* can be applied to:

n = n + x

since either n or x could override __add__ or __radd__ and do anything it
bloody well likes. Including in-place modification of *either* argument
(where possible).


[...]
With "i = i + 1", I know that generally speaking, my "i" is being
assigned a new object and that's that, no matter what type "i" is.
(Okay: I do know that you could modify __add__ to do something
underhanded here, tweaking internal state and then returning self.

What makes you think that's underhanded?

To my mind, __add__ modifying self as a side-effect is unusual, but apart
from breaking the general rule of thumb to avoid side-effects, not
particularly evil. (But I would accept this is a code smell.)

People going out of their way to behave unpredictably is not my
objection: supposedly easy and straight-forward normal Python-fu being
inherently unpredictable is).

For example: I just /know/ that it doesn't matter who or what may have
their own binding to that object before I go and increment it, they
won't be affected and everything just will work fine.

But you can't /know/ that at all, unless you know that the object
isn't "underhanded" (however you define that!). And given some arbitrary
object, how can you know that?
 
S

Stephen Hansen

Technically, the *exact same criticism* can be applied to:

n = n + x

since either n or x could override __add__ or __radd__ and do anything it
bloody well likes. Including in-place modification of *either* argument
(where possible).

I know: I addressed that. :p See, you know I addressed that, because:
[...]
With "i = i + 1", I know that generally speaking, my "i" is being
assigned a new object and that's that, no matter what type "i" is.
(Okay: I do know that you could modify __add__ to do something
underhanded here, tweaking internal state and then returning self.

What makes you think that's underhanded?

You quoted me addressing that very fact, and responded! :)

Its underhanded outside of narrow, well-defined situations such as ORM's
and the like where they're doing interesting and novel things with the
object model.

Underhanded doesn't mean there's no reason one should ever do it, but
its very much an unusual thing and objects that do things like that
should NOT freely interact with general Python objects: very surprising
behavior will result.
To my mind, __add__ modifying self as a side-effect is unusual, but apart
from breaking the general rule of thumb to avoid side-effects, not
particularly evil. (But I would accept this is a code smell.)

I didn't really say its evil, just that its underhanded: that's like,
sketchy, not evil. :)

There's good reasons to do it on occasion, but if an object does
something like that then that is a very special kind of object and when
using it, you need to be very aware of its unusual semantics.

Having objects like that is fine.

However, for the /language/ to have unusual (and unpredictable, from
something of a distance at least) semantics, rubs me very much the wrong
way.
But you can't /know/ that at all, unless you know that the object
isn't "underhanded" (however you define that!). And given some arbitrary
object, how can you know that?

I CAN know it with sufficient certainty that I can rely on it, and if I
get messed up on it-- its never happened yet-- then I can fire someone
or get a new library, after staring at an odd traceback and scratching
my head for a minute.

Python has very soft rules, I know that. Objects /can/ do all kinds of
crazy things. I'm okay with that.

But I can rely on certain behaviors being consistent: the basic behavior
of the language is very straight-forward. It has hooks where you can do
lots of Special stuff, and if objects are Special, they're described as
Special.

ORM's have a lot of Special objects, for example. That's fine: when
messing with ORM objects, I know I have to take care with the operators
I use against them, because I know I'm not using a /usual/ Python
object. SQLAlchemy for example. This is not a criticism of SQLAlchemy:
having magic objects lets it construct SQL in ways that are vastly
easier then if they stuck to more regularly behaving objects.

But, += is Python itself adding an unpredictable behavior into the core
language, with its own base types behaving

The *exact same criticism* can NOT be applied to

n = n + x

Because my criticism isn't about one choosing to do crazy stuff with the
object model. I've never advocated Python be strict about rules.

But for Python, all by itself, with nothing but built-in and basic
types, to have a situation where:

a = a + b
a += b

... does two very distinctly different actions, even if in many or
even most circumstances the end-result is probably the same and probably
fine, is my criticism.

--

Stephen Hansen
... Also: Ixokai
... Mail: me+list/python (AT) ixokai (DOT) io
... Blog: http://meh.ixokai.io/


-----BEGIN PGP SIGNATURE-----
Version: GnuPG v2.0.10 (Darwin)

iQEcBAEBAgAGBQJOUd0CAAoJEKcbwptVWx/l2AIH/1NEPuLyk5niUnLElXZma8hW
uTzbByRyjk549GI2PH0A36s93mQfGNGFwuKWwZdDf06qmPeftMS50pcUrZfERHit
MY9wUneuj3mZulJx1zhDx/egKxpIoSBwtxubjQlXbCAMxobINbmFevWuZFnTsww4
rRTT0KMemNSbqPtUmCVM5E3SsExA03eclR7NBxnDil3vY84hcJrVxugkYtKtbaiH
37KxKKqQMg+3fV9YV7bis8JAPz/YaWMniV7SbUnaQRGH4cjhMxmufHptX0DmmATc
oUs+jNwJFVnaunI0y/JYEue15X0hfgXg0w8pVgSX9fIbc2YkLOTd6XrCHfDneZ8=
=3Zec
-----END PGP SIGNATURE-----
 
S

Stephen Hansen

But, += is Python itself adding an unpredictable behavior into the core
language, with its own base types behaving

... very differently to fundamental, basic appearing operators.

Editing fail on my part.

Similarly:
But for Python, all by itself, with nothing but built-in and basic
types, to have a situation where:

a = a + b
a += b

... would be clearer if the second example were "x += y".
... does two very distinctly different actions, even if in many or
even most circumstances the end-result is probably the same and probably
fine, is my criticism.

--

Stephen Hansen
... Also: Ixokai
... Mail: me+list/python (AT) ixokai (DOT) io
... Blog: http://meh.ixokai.io/


-----BEGIN PGP SIGNATURE-----
Version: GnuPG v2.0.10 (Darwin)

iQEcBAEBAgAGBQJOUd+/AAoJEKcbwptVWx/lGAwIAKoreF1oT+yLAS8aiG0FShQa
7taetBaQWWna2JMwp3EP199EWpJddX5BRnZfza+xrZigsmSCgpkfVUPvlO2kaZqL
b5L8T18/nl9LapRf83NOZxrkTJBmTx5WXdiWovlNrzPSGprGgtAEmvWc0UewjPec
GSAzRjIdTKP2y5WvW6RDa9fzaMq4EWeXi2IkSAjCl/5fNFSbbEnV+6fhIeoRNVmX
lwOcvnDKonQBP95NcIp43ibvR3XKnFLw3yVTMI5LZ229D9xLHwM8hzLkQQHB0jBU
TDtDoJ8Pjypvgx3FnN435ggHjsFdC/bn61GuYMqSGHUw3m+UHs1I6mBedW7/Cyk=
=5dgp
-----END PGP SIGNATURE-----
 
S

Seebs

Am Sonntag, den 21.08.2011, 14:52 -0400 schrieb Roy Smith:
What is the difference in speed between a jump table that is searched
from top to bottom

A jump table isn't searched, it's jumped into. You don't look through the
table for a matching element, you grab the Nth element of the table.

-s
 
R

Richard D. Moores

But really, we're talking about tiny differences in speed. Such trivial
differences are at, or beyond, the limit of what can realistically be
measured on a noisy PC running multiple processes (pretty much all PCs
these days). Here are three runs of each on my computer:


[steve@sylar python]$ python2.5 -m timeit -s 'n=0' 'n = n+1'
1000000 loops, best of 3: 0.508 usec per loop
[steve@sylar python]$ python2.5 -m timeit -s 'n=0' 'n = n+1'
1000000 loops, best of 3: 0.587 usec per loop
[steve@sylar python]$ python2.5 -m timeit -s 'n=0' 'n = n+1'
1000000 loops, best of 3: 0.251 usec per loop


[steve@sylar python]$ python2.5 -m timeit -s 'n=0' 'n += 1'
1000000 loops, best of 3: 0.226 usec per loop
[steve@sylar python]$ python2.5 -m timeit -s 'n=0' 'n += 1'
1000000 loops, best of 3: 0.494 usec per loop
[steve@sylar python]$ python2.5 -m timeit -s 'n=0' 'n += 1'
1000000 loops, best of 3: 0.53 usec per loop

Look at the variation between runs! About 130% variation between the fastest
and slowest for each expression. And there's no reason to think that the
fastest results shown is as fast as it can get. The time is dominated by
noise, not the addition.


For what it's worth, if I try it with a more recent Python:

[steve@sylar python]$ python3.2 -m timeit -s 'n=0' 'n = n+1'
1000000 loops, best of 3: 0.221 usec per loop
[steve@sylar python]$ python3.2 -m timeit -s 'n=0' 'n = n+1'
1000000 loops, best of 3: 0.202 usec per loop
[steve@sylar python]$ python3.2 -m timeit -s 'n=0' 'n = n+1'
1000000 loops, best of 3: 0.244 usec per loop

[steve@sylar python]$ python3.2 -m timeit -s 'n=0' 'n += 1'
1000000 loops, best of 3: 0.49 usec per loop
[steve@sylar python]$ python3.2 -m timeit -s 'n=0' 'n += 1'
1000000 loops, best of 3: 0.176 usec per loop
[steve@sylar python]$ python3.2 -m timeit -s 'n=0' 'n += 1'
1000000 loops, best of 3: 0.49 usec per loop


I simply do not believe that we can justify making *any* claim about the
relative speeds of n=n+1 and n+=1 other than "they are about the same". Any
result you get, faster or slower, will depend more on chance than on any
real or significant difference in the code.

I couldn't resist giving it a try. Using Python 3.2.1 on a 64-bit
Windows 7 machine with a 2.60 gigahertz AMD Athlon II X4 620
processor, I did 18 tests, alternating between n=n+1 and n+=1 (so 9
each).

The fastest for n+=1 was
C:\Windows\System32> python -m timeit -r 3 -s "n=0" "n += 1"
10000000 loops, best of 3: 0.0879 usec per loop

The slowest for n+=1 was
C:\Windows\System32> python -m timeit -r 3 -s "n=0" "n += 1"
10000000 loops, best of 3: 0.0902 usec per loop

The fastest for n = n + 1 was
C:\Windows\System32> python -m timeit -r 3 -s "n=0" "n=n+1"
10000000 loops, best of 3: 0.0831 usec per loop

The slowest for n = n + 1 was
C:\Windows\System32> python -m timeit -r 3 -s "n=0" "n=n+1"
10000000 loops, best of 3: 0.0842 usec per loop

Coincidence?

All the data are pasted at http://pastebin.com/jArPSe56

Dick Moores
 
E

Emile van Sebille

On 8/22/2011 2:55 AM Richard D. Moores said...
I couldn't resist giving it a try. Using Python 3.2.1 on a 64-bit
Windows 7 machine with a 2.60 gigahertz AMD Athlon II X4 620
processor, I did 18 tests, alternating between n=n+1 and n+=1 (so 9
each).

The fastest for n+=1 was
C:\Windows\System32> python -m timeit -r 3 -s "n=0" "n += 1"
10000000 loops, best of 3: 0.0879 usec per loop

The slowest for n+=1 was
C:\Windows\System32> python -m timeit -r 3 -s "n=0" "n += 1"
10000000 loops, best of 3: 0.0902 usec per loop

The fastest for n = n + 1 was
C:\Windows\System32> python -m timeit -r 3 -s "n=0" "n=n+1"
10000000 loops, best of 3: 0.0831 usec per loop

The slowest for n = n + 1 was
C:\Windows\System32> python -m timeit -r 3 -s "n=0" "n=n+1"
10000000 loops, best of 3: 0.0842 usec per loop

Coincidence?

Naaa.. I just ran it twice -- once per ... _this_ is coincidence. :)

Microsoft Windows XP [Version 5.1.2600]
(C) Copyright 1985-2001 Microsoft Corp.

C:\Documents and Settings\Emile>python -m timeit -r 3 -s "n=0" "n=n+1"
10000000 loops, best of 3: 0.108 usec per loop

C:\Documents and Settings\Emile>python -m timeit -r 3 -s "n=0" "n += 1"
10000000 loops, best of 3: 0.108 usec per loop

C:\Documents and Settings\Emile>
 
E

Emile van Sebille

On 8/22/2011 9:35 AM Emile van Sebille said...
On 8/22/2011 2:55 AM Richard D. Moores said...
Coincidence?

Naaa.. I just ran it twice -- once per ... _this_ is coincidence. :)

Microsoft Windows XP [Version 5.1.2600]
(C) Copyright 1985-2001 Microsoft Corp.

C:\Documents and Settings\Emile>python -m timeit -r 3 -s "n=0" "n=n+1"
10000000 loops, best of 3: 0.108 usec per loop

C:\Documents and Settings\Emile>python -m timeit -r 3 -s "n=0" "n += 1"
10000000 loops, best of 3: 0.108 usec per loop

Actually, it's more likely I hit a minimum resolution issue -- I ran it
twenty more times and never got a different result.

Emile
 

Ask a Question

Want to reply to this thread or ask your own question?

You'll need to choose a username for the site, which only take a couple of moments. After that, you can post your question and our members will help you out.

Ask a Question

Members online

No members online now.

Forum statistics

Threads
473,768
Messages
2,569,574
Members
45,050
Latest member
AngelS122

Latest Threads

Top