2017-02-09 8 views
4

Я читаю исходный код библиотеки Pythonnumpy и находит следующие снимки. Кажется, что он выполняет элементарные операции над векторами (numpy.ndarray). Например, numpy.multiply([1,2,3],[4,5,6]) получите результат [4,10,18]Как избыточное if-else помогает оптимизировать?

#define BASE_UNARY_LOOP(tin, tout, op) \ 
    UNARY_LOOP { \ 
     const tin in = *(tin *)ip1; \ 
     tout * out = (tout *)op1; \ 
     op; \ 
    } 
#define UNARY_LOOP_FAST(tin, tout, op) \ 
    do { \ 
    /* condition allows compiler to optimize the generic macro */ \ 
    if (IS_UNARY_CONT(tin, tout)) { \ 
     if (args[0] == args[1]) { \ 
      BASE_UNARY_LOOP(tin, tout, op) \ 
     } \ 
     else { \ 
      BASE_UNARY_LOOP(tin, tout, op) \ 
     } \ 
    } \ 
    else { \ 
     BASE_UNARY_LOOP(tin, tout, op) \ 
    } \ 
    } \ 
    while (0) 

Это выглядит очень странно для меня, особенно комментарий внутри UNARY_LOOP_FAST. Что здесь происходит, используя if A then X else X логику для оптимизации?

+0

Кроме того, если что-то необходимо сделать только один раз, то почему 'делать {...}, а (0); '? – sameerkn

+0

@sameerkn do {...} while (0) является общим и имеет цель, см. [Зачем использовать явно бессмысленные инструкции do-while и if-else в макросах C/C++?] (Https://stackoverflow.com/questions/ 154136/почему использование по-видимому, бессмысленно-делать-то время, и, если-иначе-заявления-в-CC-макросов). –

+0

Возможно, это говорит компилятору рассмотреть частный случай, когда массив 'tout' является таким же, как' tin', например 'np.sin (a, out = a)'. – hpaulj

ответ

0

Этот клип от

https://github.com/numpy/numpy/blob/master/numpy/core/src/umath/loops.c.src

Этот конкретный клип определяет зацикливание макросы для одного аргумента ufunc, что-то вроде np.abs.

Комментарий до этого клипа

/* цикл с непрерывной специализации * оп должен быть код работает на tin in и * результат сохраняется в tout * out * в сочетании с NPY_GCC_OPT_3 чтобы Автовекторизация * должен быть только используется там, где его стоит избегать кода наворотов */

ufunc конструкция позволяет использовать как np.sin(a, out=b). По-видимому, это говорит компилятору рассмотреть частный случай, когда массив tout такой же, как tin, например np.sin(a, out=a).

Аналогично быстрая двоичное ufunc макросы позволяет идентичность среди трех массивов, np.add(a, b, out=c), которые могли бы реализовать его как c=a+b, a += b, b+=a.


Эти временных разниц предполагают, что есть умеренное количество оптимизации в случае, когда args[0] == args[1]

In [195]: a=np.ones((100,100)) 
In [197]: %%timeit b=np.ones((100,100)) 
    ...: np.sin(a, out=b) 
1000 loops, best of 3: 343 µs per loop 

In [198]: %%timeit b=np.ones((100,100)) 
    ...: np.sin(b, out=b) 
1000 loops, best of 3: 279 µs per loop 
+0

Возможно ли, что временные разности в результате последнего случая не нуждаются в 'malloc' новом пространстве для b? – bbvan

+0

'b' предопределено в обоих случаях. В любом случае времена являются наводящими, а не доказательством. – hpaulj

2

Без больше контекста, вы не можете сказать, какого рода оптимизаций стало возможным с помощью numpy фрагмента кода, но это, вероятно, похож на этот упрощенный пример:

#define LOOP(op) for (int i = 0; i < n; i++) op 

void f(int *a, int *b, int n, int c) { 
    if (c == 1) { 
    LOOP(a[i] += b[i] * c); 
    } 
    else { 
    LOOP(a[i] += b[i] * c); 
    } 
} 

Современный компилятор может eliminate the multiplication in the first branch. В приведенном выше примере вы могли бы просто написать LOOP(a[i] += b[i]) в первой ветке, но если оператор if является частью другого макроса, принимающего op в качестве параметра, это невозможно.

Основная идея - заставить компилятор генерировать код для нескольких путей, некоторые из которых имеют предварительные условия, которые могут быть использованы для определенных оптимизаций.

+0

Я бы подумал, что сама ветка намного дороже, чем операция умножения. – Lundin

+0

Но ветвь выполняется только один раз. Умножение много раз, после разветвления. – hpaulj