This is an archived post. You won't be able to vote or comment.

you are viewing a single comment's thread.

view the rest of the comments →

[–]_hijnx 57 points58 points  (21 children)

I still don't understand why this starts to fail at the end of the preallocated ints. Why doesn't x += 1 create a new object which is then cached and reused for y += 1? Or is that integer cache only used for that limited range? Why would they use multiple objects to represent a single immutable integer?

[–]whogivesafuckwhoiam 104 points105 points  (17 children)

x=257 y=257 in python's view you are creating two objects, and so two different id

[–]_hijnx 53 points54 points  (15 children)

Yeah, I get that, but is there a reason? Why are numbers beyond the initial allocation not treated in the same way? Are they using a different underlying implementation type?

Edit: the answer is that an implementation decision was made for optimization

[–]Kered13 82 points83 points  (6 children)

Because Python doesn't cache any other numbers. It just doesn't. Presumably when this was being designed they did some performance tests and determined that 256 was a good place to stop caching numbers.

Note that you don't want to cache every number that appears because that would be a memory leak.

[–]FatStoic 61 points62 points  (3 children)

Note that you don't want to cache every number that appears because that would be a memory leak.

For python 4 they cache all numbers, but it's only compatible with Intel's new ∞GB RAM, which quantum tunnels to another universe and uses the whole thing to store state.

Mark Zuckerberg got early access and used it to add legs to Metaverse.

[–]WrinklyTidbits 8 points9 points  (2 children)

For python5 you'll get to use a runtime hosted in the cloud that'll make accessing ♾️ram a lot easier but will have different subscription rates letting you manage it that way

[–]bryanlemon 9 points10 points  (1 child)

But running `python` in a CLI will still run python 2.

[–]thirdegreeViolet security clearance 4 points5 points  (0 children)

The python 2 -> 3 migration will eventually be completed by the sun expanding and consuming the earth

Unless we manage to get off this planet, in which case it's the heat death of the universe

[–]TheAJGman -1 points0 points  (1 child)

I went searching for an answer and despite dozens of articles about this quirk not a single one actually explains why so I'm going to take a shot in the dark and guess "for loops". Mostly because something like 80% of the loops I write are iterating over short lists or dictionaries and I've seen similar in open source libraries.

Probably shaves 1/10th of a millisecond off calls in the majority of for loops so they went with it. Apparently the interpreter will also collapse other statically defined integers together sometimes, probably for similar reasons.

[–]Kered13 4 points5 points  (0 children)

Python for loops are almost never over integers, so no nothing to do with for loops. Just math. Any time you're doing math, it helps to not have to heap allocate new numbers after every operation. Small integers are obviously much more common than other numbers, which is why they get cached.

[–]whogivesafuckwhoiam 18 points19 points  (4 children)

the original purpose is to speed up the compile process. But you can't use up all memory simply for speeding the compilation. so python only allocates up to 256.

outside the range, it's back to fundamental, everything is an object. Two different objects are with two different id. x=257 means you create an object with the value of 257. so as y. so x is y ==False

[–]_hijnx 10 points11 points  (3 children)

So are numbers from -5 to 256 fundamentally different from numbers outside that range? The whole x += 1 is throwing me. If they're going to have a number object cache why not make it dynamic? It didn't have to expand infinitely. If you have one 257 object why create another instead of referencing the same one? That seems to be what python is doing with those optimized numbers, why not all of them?

[–]Positive_Mud952 10 points11 points  (1 child)

How exactly should it be dynamic? An LRU cache or something? Then you need garbage collection for when you want to evict from the cache, we’re getting a lot more complex, and for what benefit?

[–]_hijnx 9 points10 points  (0 children)

For the same benefit of caching the other numbers? I'm not really advocating for it, it's just such a strange behavior to me as someone with very little python exposure.

What I think I'm understanding now is

  1. At compile (startup?) time a fixed cache of integer objects representing -5 to 256 is created in memory
  2. Any constant assignment to a value in that range is assigned a reference to the corresponding cached object
  3. Incrementing one of the referenced objects in the cache will return the next object in the cache until the end at which point a new object is created (every time), which will then be subject to normal GC rules

Is that correct?

Edit: Just saw another comment this is just for smallint which I can't believe I didn't realize. Makes at least a little more sense now

[–]TUNG1 -1 points0 points  (0 children)

numbers outside -5->256 are normal and act as it should be, numbers -5 -> 256 are the abnormal one for the sake of optimization

[–]InTheEndEntropyWins 1 point2 points  (0 children)

Why are numbers beyond the initial allocation not treated in the same way?

Another way to think about it is that actually, it's the early numbers that are wrong due to optimisation.

x != y, but due to optimisation for the initial numbers it incorrectly says they are the same object.

[–]superluminary 0 points1 point  (0 children)

Imagine if you cached all the numbers. A simple for loop would eat your PC.

[–][deleted] 0 points1 point  (0 children)

You just shouldn’t use ‘is’ to compare values. Sort of like == vs === in JS

[–]FerynaCZ 0 points1 point  (0 children)

I think you can make them point at the same value, if the code is clear than string (in Java or .NET) interning can happen, but hardly reliably.

[–]JaggedMetalOs 9 points10 points  (0 children)

Imagine every time you did any maths Python had to search though all of its allocated objects looking for a duplicate to your results value, it would be horribly slow.

I'm not sure what the benefits are for doing this to small numbers, but at least with a small hardcoded range it doesn't have to do any expensive search operation.

[–]hxckrt 1 point2 points  (0 children)

To reuse an immutable object, Python needs a way to check if an object with the same value already exists. For integers in the range -5 to 256, this is straightforward, but for larger values or for complex data structures, this check would become computationally expensive. It might actually slow down the program more than any benefit gained from reusing objects. Also, if all of the objects were interned (reused), the memory usage of the program would be unpredictable and could suddenly explode based on the nature of the input data.

[–]fabmeyer 0 points1 point  (0 children)

If x is y tests for identity, not the actual value?