Базовый механизм представления памяти Python

Говорят, что представление памяти Python не копирует данные при нарезке. Было проведено множество тестов, некоторые из которых проводились на stackoverflow, "подтверждая" такое поведение.

При попытке возиться с ними я столкнулся со странным поведением, которое не мог объяснить:

>>> arr = bytearray(range(0,15))
>>> mem = memoryview(arr)
>>> mem[5:15] = mem[0:10]
>>> arr
bytearray(b'\x00\x01\x02\x03\x04\x00\x01\x02\x03\x04\x05\x06\x07\x08\t')

С одной стороны, memoryview «не» копирует данные. С другой стороны, это прекрасно работает!

Хотя я был счастлив, что это «сработало», я был опечален тем фактом, что это работает. Ну... потому что не должно.

Если бы у Python был буфер из 1 символа, результат должен был бы быть таким:

bytearray(b'\x00\x01\x02\x03\x04\x00\x01\x02\x03\x04\x00\x01\x02\x03\x04')

По сути, при написании 5-го символа он должен был перекрываться и читать 1-й символ, который был записан ранее. Пример этого наивного подхода:

>>> for i in range(10):
...    m[i+5] = m[i]
>>> a
bytearray(b'\x00\x01\x02\x03\x04\x00\x01\x02\x03\x04\x00\x01\x02\x03\x04')

Я попытался увеличить размер memoryview до больших значений, но он все еще работает, то есть python копирует данные в фоновом режиме, делая объекты memoryview совершенно бессмысленными.

Я где-то здесь не прав? Любое объяснение? Как тогда работает memoryview?


person Bharel    schedule 31.08.2017    source источник


Ответы (1)


Проверяет следующее:

    if (dptr + size < sptr || sptr + size < dptr)
        memcpy(dptr, sptr, size); /* no overlapping */
    else
        memmove(dptr, sptr, size);

memmove указан как безопасный для перекрытия источника и назначения. То, как это обеспечивает безопасность, варьируется от случая к случаю и от реализации к реализации, но один метод заключается в работе справа налево, а не слева направо, если слева направо будут перезаписаны еще не скопированные данные.

person user2357112 supports Monica    schedule 31.08.2017
comment
Мальчик ты быстрый. Однако возникает другой вопрос: почему бы не всегда использовать memmove? memmove уже проверяет перекрытие, так зачем проверять дважды? Разве не менее эффективно проверять дважды? Я считаю, что memmove прибегнет к memcopy, если они не перекрываются. Возможно, это из-за новых накладных расходов на кадр стека? - person Bharel; 01.09.2017
comment
@Bharel: Не знаю. Проверка, которую они делают, имеет даже неопределенное поведение, если dptr и sptr не указывают на один и тот же массив, поэтому действительно кажется, что они должны были оставить проверку для memmove, который может выполнить проверку без UB. Может быть, они измерили реальную разницу в производительности в какой-то реализации, или, может быть, они просто не доверяли memmove. Возможно, какой-то компилятор встраивает memcpy, а не memmove. - person user2357112 supports Monica; 01.09.2017
comment
Если они не указывают на один и тот же массив, он выходит за пределы размера (которые проверялись ранее), поэтому он прибегает к memcpy. - person Bharel; 01.09.2017
comment
Последний вопрос: как вы пришли к этому ответу так быстро? Какие шаги вы предприняли? (Дайте человеку рыбу, и вы накормите его на один день. Научите человека ловить рыбу, и вы накормите его на всю жизнь.) - person Bharel; 01.09.2017
comment
@Bharel: исходный код CPython находится в официальном репозитории Github. Большинство встроенных типов объектов реализованы в каталоге Objects, а файлы там, memoryobject.c выделяется тем, что звучит как он реализует memoryview... - person user2357112 supports Monica; 01.09.2017
comment
Отсюда знакомство с C API и соответствующими соглашениями об именах и комментариях указывает на memory_ass_sub в качестве функции, которая реализует назначение слайсов memoryview (и других назначений индексов), и оттуда нужно проследить путь кода до точки, где происходит копирование данных. - person user2357112 supports Monica; 01.09.2017
comment
Вы просто потрясающие. Благодарю вас! - person Bharel; 01.09.2017