Почему numpy быстрее, чем мой код на c / c ++ для суммирования массива с плавающей точкой? - PullRequest
0 голосов
/ 26 июня 2018

Я проверял эффективность моей простой разделяемой библиотеки C и сравнивал ее с имплементацией numpy.

Создание библиотеки : следующая функция определена в sum_function.c :

float sum_vector(float* data, int num_row){
    float value = 0.0;
    for (int i = 0; i < num_row; i++){
        value += data[i];
    }
    return value;
}

Компиляция библиотеки : общая библиотека sum.so создается

clang -c sum_function.c
clang -shared -o sum.so sum_function.o

Измерение : простой numpyсоздается массив, и сумма его элементов вычисляется с использованием вышеуказанной функции.

from ctypes import *
import numpy as np

N = int(1e7)
data = np.arange(N, dtype=np.float32)

libc = cdll.LoadLibrary("sum.so")
libc.sum_vector.restype = c_float
libc.sum_vector(data.ctypes.data_as(POINTER(c_float)),
                c_int(N))

Вышеуказанная функция занимает 30 мс.Однако, если я использую numpy.sum, время выполнения составляет всего 4 мс.

Так что мой вопрос: что делает numpy намного быстрее, чем моя реализация на C?Я не могу думать ни о каком улучшении с точки зрения алгоритма для вычисления суммы вектора.

Ответы [ 2 ]

0 голосов
/ 27 июня 2018

Вам необходимо включить оптимизацию

В дополнение к этому вы должны проверить, может ли компилятор использовать автовекторизацию.Если вы хотите распространять скомпилированный двоичный файл, вы можете добавить несколько путей кода (AVX2, SS2), чтобы получить работоспособную и эффективную версию на всех платформах.

Небольшой обзор различных реализаций и их производительности.Если вы не можете превзойти реализацию numy sum (двоичную версию, установленную через pip) на недавнем процессоре, вы сделали что-то не так, но также помните о различной точности реализации и зависимости от компилятора (fastmath).Мне было лень устанавливать Clang, но я использовал Numba, у которого также есть бэкэнд LLVM (такой же, как у Clang).

import numba as nb
import numpy as np
import time

#prints information about SIMD vectorization
import llvmlite.binding as llvm
llvm.set_option('', '--debug-only=loop-vectorize')


@nb.njit(fastmath=True) #eq. O3, march-native,fastmath
def sum_nb(ar):
  s1=0. #double

  for i in range(ar.shape[0]):
    s1+=ar[i+0]

  return s1

N = int(1e7)
ar = np.random.rand(N).astype(np.float32)

#Numba solution float32 with float64 accumulator
#don't measure compilation time
sum_1=sum_nb(ar)
t1=time.time()
for i in range(1000):
  sum_1=sum_nb(ar)

print(time.time()-t1)

#Numba solution float64 with float64 accumulator
#don't measure compilation time
arr_64=ar.astype(np.float64)
sum_2=sum_nb(arr_64)
t1=time.time()
for i in range(1000):
  sum_2=sum_nb(arr_64)

print(time.time()-t1)

#Numpy solution (float32)
t1=time.time()
for i in range(1000):
  sum_3=np.sum(ar)

print(time.time()-t1)

#Numpy solution (float32, with float64 accumulator)
t1=time.time()
for i in range(1000):
  sum_4=np.sum(ar,dtype=np.float64)

print(time.time()-t1)

#Numpy solution (float64)
t1=time.time()
for i in range(1000):
  sum_5=np.sum(arr_64)

print(time.time()-t1)


print(sum_1)
print(sum_2)
print(sum_3)
print(sum_4)
print(sum_5)

Производительность

#Numba solution float32 with float64 accumulator: 2.29ms
#Numba solution float64 with float64 accumulator: 4.76ms
#Numpy solution (float32): 5.72ms
#Numpy solution (float32) with float64 accumulator:: 7.97ms
#Numpy solution (float64):: 10.61ms
0 голосов
/ 26 июня 2018

Существует множество причин, которые могут быть связаны, даже в зависимости от используемого компилятора.Ваш numy backend во многих случаях является C / C ++.Другими словами, вы должны понимать, что такие языки, как C ++, обеспечивают гораздо большую эффективность и связь с оборудованием, но также требуют больших знаний.C ++ меньше, чем C, если вы используете STL, как в комментарии @ PaulMcKenzie.Это процедуры, которые оптимизированы для производительности во время выполнения.

Следующая вещь - это выделение памяти.Теперь ваш вектор кажется достаточно большим, чтобы распределитель внутри <std::vector> выровнял память в куче.Память в стеке может остаться невыровненной, сохраняя std::accumulate даже медленно.Вот идея, как можно написать такой распределитель, чтобы избежать этого: https://github.com/kvahed/codeare/blob/master/src/matrix/Allocator.hpp. Это часть библиотеки восстановления изображений МРТ, которую я написал как аспирант.

Слово на SIMD: та же библиотека, другой аспект,https://github.com/kvahed/codeare/blob/master/src/matrix/SIMDTraits.hpp Как сделать современную арифметику совсем не тривиально.

Обе приведенные выше концепции достигают кульминации https://github.com/kvahed/codeare/blob/master/src/matrix/Matrix.hpp,, где вы легко превосходите любой стандартизированный код на конкретной машине.

И последнее, но не менее важное: флаги компилятора и компилятора.Ваш исполняемый код должен быть после отладки, вероятно, скомпилирован -O2 -g или даже -O3.Если у вас есть хорошее тестовое покрытие, вы можете даже уйти с -Ofast, что снижает точность математических операций.Помимо численной интеграции я никогда не был свидетелем проблем.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...