Некоторое время назад я прислал безобидный PR, который исправлял поведение
list.insert в nogil сборках CPython. Изменений на 3 строчки. И в ревью случилось два интересных момента.Во-первых, я случайно удалил оптимизацию.
Было:
PyObject **items;
items = self->ob_item;
items[i+1] = items[i];
items[where] = Py_NewRef(v);
0x0000000000132860 <+96>: sub rdx,0x1
0x0000000000132864 <+100>: sub rax,0x8
0x0000000000132868 <+104>: mov rcx,QWORD PTR [rax]
0x000000000013286b <+107>: mov QWORD PTR [rax+0x8],rcx
0x000000000013286f <+111>: cmp rsi,rdx
0x0000000000132872 <+114>: jle 0x132860 <ins1+96>
Стало:
self->ob_item[i+1] = self->ob_item[i];
self->ob_item[where] = Py_NewRef(v);
0x0000000000132858 <+88>: mov rdx,QWORD PTR [r12+0x28]
0x000000000013285d <+93>: lea rcx,[rax*8+0x0]
0x0000000000132865 <+101>: mov rdi,QWORD PTR [rdx+rax*8]
0x0000000000132869 <+105>: sub rax,0x1
0x000000000013286d <+109>: mov QWORD PTR [rdx+rcx*1+0x8],rdi
0x0000000000132872 <+114>: cmp rsi,rax
0x0000000000132875 <+117>: jle 0x132858 <ins1+88>
В питоне есть аналогичная оптимизация. Использовать
items = self.items
for _ in whatever:
some_func(items)
более оптимально, чем
for _ in whatever:
some_func(self.items)
если у вас много объектов в
whatever.
LOAD_NAME 3 (self)
LOAD_ATTR 8 (items) # <- won't happen when `items = self.items`
CALL 1
Во-вторых, когда мне указали на ошибку, я понял, что я так до конца и не понял разницу между
FT_ATOMIC_STORE_PTR_RELAXED и FT_ATOMIC_STORE_PTR_RELEASE.А вот тут потребуется пояснительная бригада. Смотрите, при использовании nogil, у нас теперь несколько потоков, который выполняют сишный код (который генерирует нужный ASM). И там есть своя специфика. Начнем с того, что есть специальное понятие – Memory Ordering. Базово – как и в каком порядке будут идти обращения от CPU к памяти. Что становится критически важно, когда у нас появляется multi-threading.
Базово, у нас может быть несколько видов memory ordering:
-
memory_order_relaxed – Relaxed operation: there are no synchronization or ordering constraints imposed on other reads or writes, only this operation's atomicity is guaranteed-
memory_order_release – A store operation with this memory order performs the release operation: no reads or writes in the current thread can be reordered after this store. All writes in the current thread are visible in other threads that acquire the same atomic variable and writes that carry a dependency into the atomic variable become visible in other threads that consume the same atomicПолный референс.
__atomic_store_n(&x->ob_item[0], &first, __ATOMIC_RELEASE) будет скомпилировано в
lea rdx, [rsp+12]
mov QWORD PTR [rax], rdx
__atomic_store_n(&x->ob_item[0], &first, __ATOMIC_RELAXED) скомпилируется в
mov rax, QWORD PTR [rbx]
mov QWORD PTR [rax], rdx
Как можно увидеть: в первом случае происходит непосредственное вычисление адреса
rsp+12 инструкцией lea. А во втором случае – мы просто работаем со значением в регистре rbx.Полный пример на godbolt. Тема для меня новая, продолжаю изучать.
Обсуждение: а вы думали, что nogil – оно просто?
| Поддержать | YouTube | GitHub | Чат |