Возвращает индексы первых вхождений максимальных значений вдоль указанной оси. Если ось равна None, индекс соответствует выровненной матрице.#
Если вы дошли до этого места, вы хотите углубиться в использование более продвинутых инструментов. Обычно это не требуется для начинающих участников и большинства повседневных задач разработки. Эти инструменты используются реже, например, перед выпуском новой версии NumPy или при внесении крупных или особенно сложных изменений.
Некоторые из этих инструментов используются в тестах непрерывной интеграции NumPy. Если вы видите сбой теста, который происходит только при использовании инструмента отладки, эти инструкции должны помочь вам воспроизвести сбой теста локально.
Поскольку не все эти инструменты используются регулярно и доступны только на некоторых системах, ожидайте различий, проблем или особенностей; мы будем рады помочь, если вы застрянете, и ценим любые улучшения или предложения для этих рабочих процессов.
Поиск ошибок C с дополнительными инструментами#
Большинству разработок не потребуется больше, чем типичная цепочка инструментов отладки, как показано в Отладка. Но, например, утечки памяти могут быть особенно незаметными или сложными для локализации.
Мы не ожидаем, что большинство участников будут запускать эти инструменты. Однако вы можете обеспечить, чтобы мы могли легче отслеживать такие проблемы:
Тесты должны покрывать все пути кода, включая пути ошибок.
Старайтесь писать короткие и простые тесты. Если у вас очень сложный тест, рассмотрите создание дополнительного более простого теста. Это может быть полезно, потому что часто легко найти, какой тест вызывает проблему, а не какую строку теста.
Никогда не используйте
np.emptyесли данные читаются/используются. Valgrind обнаружит это и сообщит об ошибке. Когда значения не важны, можно генерировать случайные значения вместо них.
Это поможет нам выявить любые упущения до выпуска вашего изменения и означает, что вам не нужно беспокоиться об ошибках подсчета ссылок, которые могут быть пугающими.
Отладочная сборка Python#
Отладочные сборки Python легко доступны, например, через менеджер пакетов системы на Linux, но также доступны на других платформах, возможно, в менее удобном формате. Если вы не можете легко установить отладочную сборку Python из менеджера пакетов системы, вы можете собрать её самостоятельно, используя pyenv. Например, чтобы установить и глобально активировать отладочную сборку Python 3.13.3, нужно сделать:
pyenv install -g 3.13.3
pyenv global 3.13.3
Обратите внимание, что pyenv install собирает Python из исходного кода, поэтому необходимо убедиться, что зависимости Python установлены перед сборкой, см. документацию pyenv для инструкций по установке на конкретных платформах. Можно использовать pip для установки зависимостей Python, которые могут понадобиться для сеанса отладки. Если на pypi, вам нужно будет собрать зависимости из
исходного кода и убедиться, что ваши зависимости также скомпилированы как отладочные сборки.
Часто отладочные сборки Python называют исполняемый файл Python pythond вместо
python. Чтобы проверить, установлена ли у вас отладочная сборка Python, можно запустить
например, pythond -m sysconfig чтобы получить конфигурацию сборки для исполняемого файла Python.
Отладочная сборка будет построена с отладочными параметрами компилятора в
CFLAGS (например, -g -Og).
Запуск тестов Numpy или интерактивного терминала обычно так же прост, как:
python3.8d runtests.py
# or
python3.8d runtests.py --ipython
и уже упоминались в Отладка.
Сборка Python для отладки поможет:
Найдите ошибки, которые в противном случае могут вызывать случайное поведение. Один пример — когда объект всё ещё используется после его удаления.
Сборки Python для отладки позволяют проверить правильность подсчёта ссылок. Это работает с использованием дополнительных команд:
sys.gettotalrefcount() sys.getallocatedblocks()
Отладочные сборки Python позволяют упростить отладку с помощью gdb и других отладчиков C.
Использовать вместе с pytest#
Запуск набора тестов только с отладочной сборкой Python не обнаружит много ошибок самостоятельно. Дополнительным преимуществом отладочной сборки Python является то, что она позволяет обнаруживать утечки памяти.
Инструмент, облегчающий это, — pytest-leaks, которые можно установить с помощью pip. К сожалению, pytest сам может приводить к утечке памяти, но хороших результатов обычно можно
(в настоящее время) достичь, удалив:
@pytest.fixture(autouse=True)
def add_np(doctest_namespace):
doctest_namespace['np'] = numpy
@pytest.fixture(autouse=True)
def env_setup(monkeypatch):
monkeypatch.setenv('PYTHONHASHSEED', '0')
из numpy/conftest.py (Это может измениться с новыми pytest-leaks версии
или pytest обновления).
Это позволяет удобно запускать набор тестов или его часть:
python3.8d runtests.py -t numpy/_core/tests/test_multiarray.py -- -R2:3 -s
где -R2:3 является pytest-leaks команда (см. ее документацию),
-s вызывает вывод на печать и может быть необходимым (в некоторых версиях захваченный
вывод обнаруживался как утечка).
Обратите внимание, что некоторые тесты известны (или даже разработаны) как создающие утечки ссылок, мы стараемся помечать их, но ожидайте некоторых ложных срабатываний.
valgrind#
Valgrind является мощным инструментом
для обнаружения определённых проблем доступа к памяти и должен
запускаться на сложном коде C.
Базовое использование valgrind обычно требует не более:
PYTHONMALLOC=malloc valgrind python runtests.py
где PYTHONMALLOC=malloc необходимо, чтобы избежать ложных срабатываний от самого Python.
В зависимости от системы и версии valgrind вы можете увидеть больше ложных срабатываний.
valgrind поддерживает "подавления" для игнорирования некоторых из них, и Python действительно имеет файл подавления (и даже опцию времени компиляции), который может помочь, если вы сочтете это необходимым.
Valgrind помогает:
Найти использование неинициализированных переменных/памяти.
Обнаружение нарушений доступа к памяти (чтение или запись за пределами выделенной памяти).
Найти много утечек памяти. Обратите внимание, что для большинство утечки в подходе сборки python debug (и
pytest-leaks) гораздо более чувствителен. Причина в том, чтоvalgrindможет обнаружить только если память определенно потеряна. Если:dtype = np.dtype(np.int64) arr.astype(dtype=dtype)
Имеет некорректный подсчёт ссылок для
dtype, это ошибка, но valgrind не может её обнаружить, потому чтоnp.dtype(np.int64)всегда возвращает один и тот же объект. Однако не все dtypes являются синглтонами, поэтому это может приводить к утечке памяти для разных входных данных. В редких случаях NumPy используетmallocа не распределители памяти Python, которые невидимы для отладочной сборки Python.mallocобычно следует избегать, но есть некоторые исключения (например,PyArray_Dimsструктура является публичным API и не может использовать аллокаторы Python.)
Хотя использование valgrind для обнаружения утечек памяти медленное и менее чувствительное, оно может быть удобным: вы можете запускать большинство программ с valgrind без изменений.
На что следует обратить внимание:
Valgrind не поддерживает numpy
longdouble, это означает, что тесты будут завершаться неудачей или помечаться как ошибки, хотя они полностью корректны.Ожидайте некоторые ошибки до и после запуска вашего кода NumPy.
Кэширование может означать, что ошибки (в частности, утечки памяти) могут не обнаруживаться или обнаруживаются только позже, в несвязанное время.
Большое преимущество valgrind в том, что у него нет требований, кроме самого valgrind (хотя вы, вероятно, захотите использовать отладочные сборки для лучших трассировок).
Использовать вместе с pytest#
Вы можете запустить набор тестов с valgrind, что может быть достаточно, когда вас интересуют только несколько тестов:
PYTHONMALLOC=malloc valgrind python runtests.py \
-t numpy/_core/tests/test_multiarray.py -- --continue-on-collection-errors
Обратите внимание на --continue-on-collection-errors, что в настоящее время необходимо из-за
отсутствия longdouble поддержка, вызывающая сбои (это обычно не требуется, если вы не запускаете полный набор тестов).
Если вы хотите обнаружить утечки памяти, вам также потребуется --show-leak-kinds=definite
и, возможно, больше опций valgrind. Так же, как для pytest-leaks некоторые
тесты известны тем, что вызывают утечки ошибок в valgrind и могут быть или не быть помечены
как таковые.
Мы разработали pytest-valgrind который:
Сообщает об ошибках для каждого теста отдельно
Сужает поиск утечек памяти до отдельных тестов (по умолчанию valgrind проверяет утечки памяти только после остановки программы, что очень неудобно).
Пожалуйста, обратитесь к его README для получения дополнительной информации (включает пример команды для NumPy).
C отладчики#
Когда NumPy аварийно завершает работу или при внесении изменений в низкоуровневый код NumPy на C или C++, часто удобно запускать Python под C-отладчиком для получения дополнительной информации. Отладчик может помочь понять сбой интерпретатора (например, из-за ошибки сегментации), предоставляя стек вызовов C в месте сбоя. Стек вызовов часто даёт ценную информацию для понимания природы сбоя. C-отладчики также очень полезны во время разработки, позволяя интерактивную отладку в C-реализации NumPy.
Разработчики NumPy часто используют оба gdb и lldb для отладки NumPy. Как правило, gdb часто проще использовать в Linux, в то время как lldb проще
использовать в среде Mac. У них разные пользовательские интерфейсы, поэтому вам нужно будет
научиться использовать тот, на который вы попадете. gdb to lldb карта команд является удобным справочником по выполнению общих рецептов в обоих отладчиках.
Сборка с отладочными символами#
The spin инструмент рабочего процесса разработки. имеет встроенную поддержку работы с обоими gdb и lldb через spin gdb и spin lldb команды.
Примечание
Сборка с -Dbuildtype=debug имеет несколько важных эффектов, о которых следует знать:
Утверждения включены: Этот тип сборки не определяет
NDEBUGмакрос, что означает, что любые утверждения на уровне C в коде будут активны. Это очень полезно для отладки, так как может помочь определить, где возникает неожиданное условие.Флаги компилятора могут потребовать переопределения: Некоторые цепочки инструментов компилятора, особенно от
conda-forge, может устанавливать флаги оптимизации, такие как-O2по умолчанию. Они могут переопределитьdebugтип сборки. Чтобы обеспечить настоящую отладочную сборку в таких средах, может потребоваться вручную сбросить или переопределить этот флаг.
Для получения более подробной информации по обоим пунктам см. руководство meson-python по отладочным сборкам.
Для обоих отладчиков рекомендуется собирать NumPy либо в debug или
debugoptimized профиль сборки meson. Чтобы использовать debug вы можете передать опцию
через spin build:
spin build -- -Dbuildtype=debug
использовать debugoptimized вы передаете -Dbuildtype=debugoptimized вместо этого.
Вы можете передать дополнительные аргументы в meson setup кроме buildtype используя
тот же синтаксис позиционных аргументов для spin build.
Запуск тестового скрипта#
Предположим, у вас есть тестовый скрипт с именем test.py который находится в test папка
в том же каталоге, что и исходный код NumPy. Вы можете выполнить тестовый
скрипт, используя spin сборка NumPy с помощью следующего заклинания:
spin gdb ../test/test.py
Это запустит gdb. Если вас интересует только стек вызовов при сбое,
введите "r" и нажмите Enter. Ваш тестовый скрипт выполнится, и если произойдет сбой,
введите "bt" для получения трассировки. Для lldb, инструкции аналогичны, просто замените spin gdb с spin lldb.
Вы также можете устанавливать точки останова и использовать другие более продвинутые техники. См. документацию вашего отладчика для получения подробностей.
Одна из распространённых проблем с точками останова в NumPy заключается в том, что некоторые пути кода многократно срабатывают во время импорта numpy модуль. Это может затруднить или утомить поиск первого «реального» вызова после завершения импорта NumPy и numpy модуль полностью инициализирован.
Одним из обходных решений является использование скрипта, подобного этому:
import os
import signal
import numpy as np
PID = os.getpid()
def do_nothing(*args):
pass
signal.signal(signal.SIGUSR1, do_nothing)
os.kill(PID, signal.SIGUSR1)
# the code to run under a debugger follows
Этот пример устанавливает обработчик сигнала для SIGUSR1 сигнал, который
ничего не делает, а затем вызывает os.kill в процессе Python с SIGUSR1
сигнал. Это вызывает срабатывание обработчика сигнала и, что критично, также вызывает оба
gdb и lldb остановить выполнение внутри kill системный вызов.
Если вы запустите lldb вы должны увидеть вывод примерно такого вида:
Process 67365 stopped
* thread #1, queue = 'com.apple.main-thread', stop reason = signal SIGUSR1
frame #0: 0x000000019c4b9da4 libsystem_kernel.dylib`__kill + 8
libsystem_kernel.dylib`__kill:
-> 0x19c4b9da4 <+8>: b.lo 0x19c4b9dc4 ; <+40>
0x19c4b9da8 <+12>: pacibsp
0x19c4b9dac <+16>: stp x29, x30, [sp, #-0x10]!
0x19c4b9db0 <+20>: mov x29, sp
Target 0: (python3.13) stopped.
(lldb) bt
* thread #1, queue = 'com.apple.main-thread', stop reason = signal SIGUSR1
* frame #0: 0x000000019c4b9da4 libsystem_kernel.dylib`__kill + 8
frame #1: 0x000000010087f5c4 libpython3.13.dylib`os_kill + 104
frame #2: 0x000000010071374c libpython3.13.dylib`cfunction_vectorcall_FASTCALL + 276
frame #3: 0x00000001006c1e3c libpython3.13.dylib`PyObject_Vectorcall + 88
frame #4: 0x00000001007edd1c libpython3.13.dylib`_PyEval_EvalFrameDefault + 23608
frame #5: 0x00000001007e7e6c libpython3.13.dylib`PyEval_EvalCode + 252
frame #6: 0x0000000100852944 libpython3.13.dylib`run_eval_code_obj + 180
frame #7: 0x0000000100852610 libpython3.13.dylib`run_mod + 220
frame #8: 0x000000010084fa4c libpython3.13.dylib`_PyRun_SimpleFileObject + 868
frame #9: 0x000000010084f400 libpython3.13.dylib`_PyRun_AnyFileObject + 160
frame #10: 0x0000000100874ab8 libpython3.13.dylib`pymain_run_file + 336
frame #11: 0x0000000100874324 libpython3.13.dylib`Py_RunMain + 1516
frame #12: 0x000000010087459c libpython3.13.dylib`pymain_main + 324
frame #13: 0x000000010087463c libpython3.13.dylib`Py_BytesMain + 40
frame #14: 0x000000019c152b98 dyld`start + 6076
(lldb)
Как видите, трассировка стека C находится внутри kill системный вызов и
lldb активен, позволяя интерактивно устанавливать точки останова. Поскольку
os.kill вызов происходит после numpy модуль уже полностью инициализирован, это означает, что любые точки останова, установленные внутри kill будет происходить
после numpy завершил инициализацию.
Использовать вместе с pytest#
Вы также можете запустить pytest тесты под отладчиком. Это требует использования отладчика в несколько более ручном режиме, поскольку spin еще не автоматизирует этот процесс. Сначала запустите spin build чтобы гарантировать наличие полностью
собранной копии NumPy, управляемой spin. Затем, чтобы запустить тесты под lldb
вы бы сделали что-то вроде этого:
spin lldb $(which python) $(which pytest) build-install/usr/lib/python3.13/site-packages/numpy/_core/tests/test_multiarray.py
Это выполнит тесты в test_multiarray.py в lldb после ввода
'r' и нажатия Enter. Обратите внимание, что эта команда взята из сессии с использованием Python
3.13 на Mac. Если вы используете другую версию Python или операционную систему,
структура каталогов внутри build-install может немного отличаться.
Вы можете устанавливать точки останова, как описано выше. Проблема с тем, что точки останова часто срабатывают во время импорта NumPy, также применима - рассмотрите возможность рефакторинга вашего рабочего процесса тестирования в тестовый скрипт, чтобы вы могли применить обходное решение с использованием
os.kill описанного выше.
Обратите внимание на использование $(which python) чтобы гарантировать, что отладчик получает путь к
исполняемому файлу Python. Если вы используете pyenv, вам может потребоваться заменить which
python с pyenv which python, поскольку pyenv полагается на скрипты-прослойки, которые which не знает о.
Средства проверки компилятора#
The санитайзер компилятора наборы инструментов, поставляемые как GCC, так и LLVM, предлагают средства для обнаружения многих распространенных ошибок программирования во время выполнения. Санитайзеры работают путем инструментирования кода приложения во время сборки, чтобы дополнительные проверки во время выполнения срабатывали. Обычно санитайзеры запускаются в ходе регулярного тестирования, и если проверка санитайзера завершается неудачей, это приводит к сбою теста или краху, а также к отчету о характере сбоя.
Хотя возможно использовать санитайзеры с «обычной» сборкой CPython — лучше, если вы сможете настроить среду Python на основе сборки Python из исходного кода с инструментированием санитайзеров, а затем использовать инструментированный Python для сборки NumPy и запуска тестов. Если весь стек Python инструментирован с использованием той же среды выполнения санитайзера, становится возможным выявлять проблемы, возникающие в стеке Python. Это позволяет обнаруживать утечки памяти в NumPy из-за неправильного использования памяти, выделенной в CPython, например.
Собрать Python с инструментацией санитайзера#
См. раздел в руководстве разработчика Python по этой теме для
больше информации о сборке Python из исходного кода. Чтобы включить санитайзер адресов,
вам нужно будет передать --with-address-sanitizer в configure вызов скрипта при сборке Python.
Вы также можете использовать pyenv для автоматизации
процесса сборки Python и быстрой активации или деактивации установки Python
с использованием интерфейса командной строки, аналогичного виртуальным
окружениям. С pyenv вы можете установить сборку Python 3.13 с инструментированием ASAN
вот так:
CONFIGURE_OPTS="--with-address-sanitizer" pyenv install 3.13
Если вас интересует санитайзер потоков, cpython_sanity docker images может также быть более быстрым выбором, который обходит сборку Python из исходного кода, хотя может быть неудобно выполнять отладку внутри docker-образа.
Использовать вместе с spin#
Однако, как бы вы ни собирали Python, как только у вас есть инструментированная сборка Python, вы можете установить зависимости разработки и тестирования NumPy и собрать NumPy с инструментацией адресного санитайзера. Например, чтобы собрать NumPy с debug
профилировщик и санитайзер адресов, вы бы передали дополнительные опции сборки в
meson например:
spin build -- -Dbuildtype=debug -Db_sanitize=address
После завершения сборки вы можете использовать другие spin команда, подобная spin
test и spin gdb как и в любой другой сборке Python.
Особые соображения#
Некоторые тесты NumPy намеренно приводят к malloc возвращая NULL. В своей
конфигурации по умолчанию некоторые санитайзеры компилятора помечают это как
ошибку. Вы можете отключить эту проверку, передав allocator_may_return_null=1 в санитайзер в качестве опции. Например, с адресным санитайзером:
ASAN_OPTIONS=allocator_may_return_null=1 spin test
Вы можете увидеть утечки памяти, исходящие от интерпретатора Python, особенно на
MacOS. Если отчеты об утечках памяти не полезны, вы можете отключить обнаружение утечек,
передав detect_leaks=0 в ASAN_OPTIONS. Вы можете передать более одной
опции, используя список, разделенный двоеточиями, например:
ASAN_OPTIONS=allocator_may_return_null=1:halt_on_error=1:detect_leaks=1 spin test
The halt_on_error опция может быть особенно полезной – она вызывает аварийное завершение исполняемого файла Python при обнаружении ошибки, вместе с отчётом об ошибке, включающим трассировку стека.
Вы также можете посмотреть на compiler_sanitizers.yml конфигурация рабочего процесса
GitHub actions. Она описывает несколько различных CI-заданий, которые выполняются как
часть тестов NumPy с использованием Thread, Address и Undefined Behavior санитайзеров.