News for hackers. Stuff that matters.
Re:Complexity of Python Operations (2013) by deathanatos on Wednesday January 13, 2016 @09:43PM (#10899321)
Some of these don't seem quite right. For example, set difference:

  Difference    | s - t        | O(len(t))     |
Worst case, we do something like `set(range(1000)) - {1}`; the bulk of this is going to be making a copy of s from which to subtract t, which is O(|s|). (If this were `s -= t`, I'd agree, O(|t|).) (Same logic applies to the other set operations.)

  Clear         | d.clear()    | O(1)	     | similar to s = {} or = dict()
I would actually guess d.clear() is O(|d|) in CPython: you're doing to need to deref each item in the dictionary[1]. Similarly, earlier it states,

  Binding a value to any name is O(1).
If there's already a value stored in the variable that gets unref'd, leading to more derefs… (though I don't know how you'd express that in O(…); perhaps we might say that it might be a larger constant time than you expect!)

  Note that for i in range(...) is O(len(...)); so for i in range(1,10) is O(1).
I feel like O(…) notation's purpose in life is to show how an algorithm's complexity in time or space responds to changes in its inputs. Saying a constant expression such as `for i in range(1,10)` is "O(1)" somewhat misses the point, I feel, as there aren't any real inputs to vary. (As opposed to `for i in range(n)`, where the `n` is an unknown input, and the run time of the loop is going to be O(n).)

  check ==, !=  | s != t       | O(min(len(s),lent(t))
(this is for sets again) This one isn't wrong, but there's an interesting opportunity for the notation here, I feel. CPython keeps track of the set's length along with the data internally[2], and checks that prior to iteration over the set[3]; so you could notate this `O(|s|) == O(|t|)` as the worst case. This is because in the worst case, |s| and |t| must be equal; if they're not, the internal size comparison fails, and you're O(1) (how long to check the sizes). (A lot of language implementations' set implementations do this I think; in fact, I'd say most programmers would expect it, because they'd expect len(a_set) to be O(1).)

Also, I think some of these are great examples of just how easy CPython's implementation is to read in many cases.

[1]: see this, for example: https://github.com/python/cpython/blob/0eb5f5996feed30ade8c2...

[2]: https://github.com/python/cpython/blob/0eb5f5996feed30ade8c2...

[3]: https://github.com/python/cpython/blob/0eb5f5996feed30ade8c2...

Re:Complexity of Python Operations (2013) by viraptor on Wednesday January 13, 2016 @10:10PM (#10899383)
How do you deal with GC in reality though? I mean, if you include it in calculations, then any operation which derefs an object is at least `O(number_of_heap_objects)`. Or in a compacting GC anything that creates a new object potentially is at least `O(heap_size)`.

I think it makes perfect sense to ignore this.

Edit: actually it's higher than `O(number_of_heap_objects)` for refcount, because you can trigger a cycle collector which is unlikely to be `O(N)`

Re:Complexity of Python Operations (2013) by deathanatos on Wednesday January 13, 2016 @10:22PM (#10899423)
I thought about trying to generalize the previous post to a generic GC, but it proved more difficult, so I left it out.

In my first post, I was mostly focused specifically on CPython, which does ref-counting with some GC to catch cycles. I was ignoring the real GC and cycles in my post; my understanding of d.clear() is that the ref counting happens and happens immediately, so at best it's still O(|d|). My main point was that in the implementation you're likely to be using, it happens at the time you run the code, and can thus change the order notation of the code (and perhaps how you expect it to perform, though you did have to construct d, which was O(|d|) at best to begin with…).

In a generic GC, I was thinking it might be fair game to ignore the GC. If GC can be done completely concurrently with the remainder of the app, and doesn't end up effectively stealing resources (mostly, CPU, I guess) from the app, then I think I might be persuaded to call it "free", or at least close to it. One might cynically call a "never free; just alloc and let the OS free on process death" a very naive form of garbage collection, and there, it is definitely close to free.

Re:Complexity of Python Operations (2013) by fnl on Wednesday January 13, 2016 @10:18PM (#10899403)