-
Notifications
You must be signed in to change notification settings - Fork 1k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Make Line and Grid Qubit hashes faster for common set ops #6886
Comments
We do have a custom Cirq/cirq-core/cirq/devices/line_qubit.py Lines 40 to 44 in 686766f
We've already improvement performance quite a bit with these methods, but it's certainly possible there's more room for improvement. I'm not sure what kind of operations you are using for circuit construction, but almost everything we do internally uses |
The implementation I made of def __eq__(self, other) -> bool:
return isinstance(other, LineQubit) and self._x == other._x This was a little more than twice as fast. I hadn't seen the The test was a basic something like circuit = cirq.Circuit()
for q in cirq.LineQubit.range(10000):
circuit.append(cirq.X(q)) Almost half the time was spent on this line https://github.com/quantumlib/Cirq/blob/main/cirq-core/cirq/circuits/moment.py#L195. So, twice the speed on the op that was almost half the time, for 25% improvement overall. Of course it's faster to create the circuit with Another option that could improve this use case for LineQubit explicitly: we could create a |
Actually, it turns out we're duplicating work; there's already a |
And it also turns out that the issue isn't Cirq/cirq-core/cirq/devices/line_qubit.py Lines 35 to 38 in 686766f
|
Actually it's not even the hashing. It's something weird in Python itself. The more random a hash key is, the more trouble it has unioning a set. Which, is supposedly the opposite of what's supposed to happen. def test_speed():
# Note in Python, hash(i) == i for i < 2**60
s = set()
k = 8
t = time.perf_counter()
for i in range(10000):
q = i * k
q = i * k + random.randint(0, k-1)
s |= s.union({q})
duration = time.perf_counter() - t
assert duration < 0 Here, the smaller I make I also made a test with dummy objects and also implemented |
I see. Not exactly, but close enough. https://github.com/python/cpython/blob/main/Objects/setobject.c says something something Algorithm D, something something linear probing, something something cache locality. It looks like the algorithm doesn't do the linked-list-per-bucket thing, but rather keep a single item per bucket, start at bucket Anyway, I guess all that is why Python's hash for an int is the int itself; it's faster for sets of small numbers, which are quite common. So, all that said...should we change the hash function here from |
(Note: the original issue was about the
__eq__
function. Upon further analysis, captured in the comments below, the hash key turns out to be the primary factor, so the issue title and description has been changed accordingly.)Set operations are common with qubits, and Python's Set ops are faster when hash values are faster the fewer bucket collisions there are. Given most Line Qubits are defined on small contiguous integers, the fewest collisions occur when
hash(q[i]) == i
. A similar approach could be used for Grid Qubits.The open question would be what to do with dimension. We could ignore it, assuming qubits of the same index but different dimensions in the same set would be extremely rare, but it would also be extremely slow if they were, as not only would there be quadratic behavior due to bucket collisions (which is actually not so bad in the Set source for reasonable sized sets since it's in C) but it would also require frequent
__eq__
calculations, which jump into Python and have a huge constant multiplier for Set ops. Another option would be to use a hash ofi * 101 + dim
and hope 101 is enough. Or, we could ignore the issue and leave the hash code alone. (This also opens a question of whether, perhaps, just the qubit keys should be part of the operation, rather than the full qubit, in which case it's not even possible to have a represent a circuit that has two qubits of different dimensions sharing an index, but that's a much larger design question and change).Experimentation shows set ops are around 50% faster. In particular this speeds up construction of wide circuits, where checking for qubit overlap can take half of the construction time.
The text was updated successfully, but these errors were encountered: