Функция детерминанта матрицы 3x3 - делаем быстрее

Я пишу большую программу, и получение определителей матриц 3x3 как можно быстрее очень важно для ее хорошей работы. Я читал, что могу использовать numPy для этого, но я подумал, что, возможно, написание собственного кода будет более образовательным, поскольку я нахожусь на третьем семестре CompSci.

Итак, я написал две функции и использую time.clock() (я на машине с Win7), чтобы определить, сколько времени требуется каждой функции для возврата значения.

Это первая функция:

def dete(a):
   x = (a[0][0] * a[1][1] * a[2][2]) + (a[1][0] * a[2][1] * a[3][2]) + (a[2][0] * a[3][1] * a[4][2])
   y = (a[0][2] * a[1][1] * a[2][0]) + (a[1][2] * a[2][1] * a[3][0]) + (a[2][2] * a[3][1] * a[4][0])
   return x - y

А это вторая функция:

def det(a):
    a.append(a[0]); a.append(a[1]);
    x = 0
    for i in range(0, len(a)-2):
        y=1;        
        for j in range(0, len(a)-2):    
            y *= a[i+j][j]      
        x += y

    p = 0
    for i in range(0, len(a)-2):
        y=1;
        z = 0;
        for j in range(2, -1, -1):  
            y *= a[i+z][j]  
            z+=1        
        z += 1
        p += y  
    return x - p

Они оба дают правильные ответы, однако первый кажется немного быстрее, что заставляет меня думать, что, поскольку циклы for более элегантны в использовании и обычно быстрее, я делаю что-то неправильно - я сделал циклы слишком медленными и толстыми. . Я пытался его обрезать, но кажется, что операции *= и += занимают слишком много времени, их слишком много. Я еще не проверял, насколько быстро numPy решает эту проблему, но я хочу лучше писать эффективный код. Любые идеи о том, как сделать эти циклы быстрее?


person Protagonist    schedule 26.02.2012    source источник
comment
Кажется, немного быстрее? Используйте timeit и профилировщик, чтобы показать, точно насколько быстрее.   -  person S.Lott    schedule 26.02.2012
comment
Итак, циклы for обычно быстрее, чем простое развернутое прямое вычисление? Хм... кажется, мне действительно нужно многое узнать о Python;)   -  person Christian Rau    schedule 26.02.2012
comment
Если вы хотите вычислить определитель 3x3, нет ничего более элегантного, чем эта оптимизированная формула из вашей первой функции.   -  person Christian Rau    schedule 26.02.2012


Ответы (5)


Циклы более элегантны и более универсальны, но они «обычно не быстрее», чем пара встроенных умножений в одном выражении.

Во-первых, forloop в python должен собрать объект, над которым вы будете взаимодействовать (вызов range), а затем вызвать метод этого итератора для каждого элемента в цикле.

Таким образом, в зависимости от того, что вы делаете, если встроенная форма достаточно быстра, чтобы вы могли ее сохранить, а если она все еще слишком медленна (как это обычно бывает, когда мы выполняем числовые вычисления в Python), вы должны использовать числовую библиотеку ( например, NumpY), который может вычислять определители в собственном коде. В случае такого кода числовых манипуляций вы можете работать в сотни раз быстрее, используя собственный код.

Если вам нужны какие-то числовые вычисления, которые не могут быть выполнены с помощью уже созданной библиотеки, если вы ищете скорость (например, для манипулирования пикселями при обработке изображений), вы можете написать расширение, которое работает в собственном коде (используя либо C , Cython или что-то еще), чтобы было быстро.

С другой стороны, если скорость не имеет решающего значения, и вы даже заметили, что встроенное выражение просто «немного быстрее», просто используйте полный цикл — вы получите более читаемый и поддерживаемый код — что, в конце концов, является основной причиной использования Python.

В приведенном вами конкретном примере вы можете получить некоторое увеличение скорости в коде цикла, жестко закодировав вызовы "диапазона" для кортежей - например, изменив: for i in range(0, len(a)-2): на for i in (0, 1, 2) - обратите внимание, что, как и во встроенном случае, вы теряете способность работать с матрицами разных размеров.

person jsbueno    schedule 26.02.2012
comment
Большое спасибо за довольно исчерпывающий ответ, я обязательно попробую все упомянутые вами варианты, и если мне нужно, чтобы код работал быстрее, я переделаю его на Java. Мне очень нравится делать что-то на Python, это так приятно. - person Protagonist; 26.02.2012

Во-первых, отмечу, что оптимизация скорости микромасштаба должна происходить на другом языке. Поэтому вам лучше использовать библиотеку, которая использует функциональность, написанную на языке C.

О ваших циклах for: Распространенным методом является развертывание (небольших) циклов для ускорения, поэтому не всегда быстрее позволять циклам выполнять свою работу. Обычно он просто более общий (а большинство общих алгоритмов на самом деле медленнее, чем специализированные).

Как указано в комментариях, это не увеличит скорость в python при замене - на *, но может увеличить скорость, если задействовано меньше арифметических операций. Поэтому я опубликую факторизованный термин здесь:

def dete(a):
    return (a[0][0] * (a[1][1] * a[2][2] - a[2][1] * a[1][2])
           -a[1][0] * (a[0][1] * a[2][2] - a[2][1] * a[0][2])
           +a[2][0] * (a[0][1] * a[1][2] - a[1][1] * a[0][2]))

Как видите, здесь 5 +/- и 9 *, тогда как в оригинальной версии 5 +/- и 12 *. Также обратите внимание, что эта версия обращается к a только 15 раз, тогда как исходная версия обращалась к нему 18 раз.

В сумме это дает 3 арифметических операции и 3 доступа к переменным меньше, чем версия с полным умножением.

person Nobody moving away from SE    schedule 26.02.2012
comment
Изменение * для серии сумм в коде Python было бы медленнее — причина, по которой Python медленный для арифметических манипуляций, заключается в том, что операции включают самоанализ и вызов метода — для каждой операции. Это занимает в сотни раз больше циклов, чем разница между умножением и сложением, привязанным к процессору. - person jsbueno; 26.02.2012
comment
Хорошо, я удалил это, это было скорее примечание из моего опыта C ++ :) - person Nobody moving away from SE; 26.02.2012
comment
@jsbueno Изменение * для ряда сумм на любой современной архитектуре будет медленнее. Есть причина, по которой ALU имеют встроенные операции умножения, и есть причина, по которой компиляторы JIT используют их. - person David Souther; 26.02.2012
comment
Для базовых типов данных процессора, таких как числа с плавающей запятой или целые числа, это может быть верным, но большинство матричных операций используют свои преимущества в типах данных более высокого уровня, которые все еще намного медленнее в *, чем в -. Но у ОП, вероятно, есть только матрицы этих простых типов. - person Nobody moving away from SE; 26.02.2012
comment
Для справки: исключение общих терминов, как первоначально предложил Никто, действительно уменьшает общее количество требуемых операций. Вы можете сократить число арифметических операций до 14 по сравнению с 17, так что это не просто замена умножения на вычитание, и это выигрыш, даже если мы оговорим, что * стоит столько же, сколько -. - person DSM; 26.02.2012
comment
Хорошо, спасибо за действительно быстрый ответ. Я постараюсь согласиться с этим, если мне нужно больше скорости, я перепишу программу на Java (Java - это язык моего курса, я изучаю Python самостоятельно). - person Protagonist; 26.02.2012

Пока вы разворачиваете его, как было предложено выше, вы также можете объединить два блока в один, так как беглый взгляд не обнаружил зависимостей между двумя блоками (поправьте меня, если я ошибаюсь)

person malexmave    schedule 26.02.2012

Практически невозможно, чтобы циклы были быстрее, чем явные длинные выражения, поэтому неудивительно, что первый вариант быстрее. Я сомневаюсь, что вы могли бы придумать smt быстрее, чем первая функция.

person Roman Bodnarchuk    schedule 26.02.2012

вы можете развернуть циклы и воспользоваться тем фактом, что вы обрабатываете матрицы 3x3, а не матрицы nxn.

С помощью этой оптимизации вы избавляетесь от определения размера матрицы. Вы обмениваете гибкость на небольшое ускорение. Вы можете просто записать конкретные формулы для каждой ячейки результирующей матрицы. Кстати: (С++) Компиляторы делают такие вещи для оптимизации.

Я бы посоветовал это сделать только в том случае, если вы действительно уверены, что такая небольшая оптимизация стоит специализированного кода. Чтобы убедиться, что вы оптимизируете правильную часть своего кода, используйте, например. инструменты профилирования см. http://docs.python.org/library/profile.html или timeit.

person Jörg Beyer    schedule 26.02.2012