ВРЕМЯ. Является ли это элементом природы или выдуманной концепцией? Поистине удивительно, как мы пытаемся это воспринять и осмыслить. Мы как род человеческий ценим время больше всего на свете, даже деньги, в конце концов, потерянные деньги можно снова заработать, но потерянное время ушло навсегда.

Время и прилив никого не ждут.

Со временем, имеющим такую ​​высокую ценность, логически следует его сохранение. Главное, чтобы время не было потеряно. До сих пор мы лишь смутно обсуждали важность времени. Давайте немного углубимся. В современном мире, наполненном технологиями, данных очень много, я имею в виду МНОГО. Когда в этом огромном море данных выполняется какая-то работа, такая как анализ, в этом участвует некоторый код. Я имею в виду, да, это очевидно, не так ли? Как бы просто это ни казалось, при написании кода, управляющего такими огромными объемами данных, возникают некоторые сложности. Если код, который вы пишете, неэффективен, то вы тратите много времени впустую, помните, что данные настолько велики, что время, необходимое для запуска кода, будет пропорционально большим. Итак, как мы можем писать код эффективно? Что мы можем сделать, чтобы наш код работал быстрее? Короткий ответ — распараллеливание или даже лучше векторизация. Теперь вам должно быть интересно, что означают эти термины, не волнуйтесь, давайте рассмотрим их подробно. Вы поймете, что они довольно просты.

Чтобы понять, что это за концепции и как их реализовать, давайте рассмотрим пример. Возьмем простую задачу — перемножить элементы двух массивов и сложить их вместе. Например, скажем, есть массив a=[1,2] и другой массив b=[3,4]. Наша цель — умножить элементы обоих массивов, т. е. 1*3 и 2*4 (обратите внимание, что мы умножаем элементы, имеющие одинаковый индекс). После умножения мы складываем все полученные продукты, то есть (1 * 3) + (2 * 4), что составляет 3 + 8 = 11. Допустим, мы сохраняем в переменной c (что делает c=11).

Здесь массивы имеют только два элемента, поэтому мы можем вычислить их напрямую. Какие массивы имеют миллион элементов? Это квалифицируется как большие данные, не так ли? Так как же нам подойти к выполнению нашей задачи? Давайте попробуем написать код и посмотреть. Здесь я использую Python, так как реализовать вещи на Python довольно просто благодаря множеству библиотек, находящихся в нашем распоряжении.

Во-первых, давайте импортируем библиотеки. Есть пара библиотек, с которыми вы, возможно, не знакомы, но когда мы будем их использовать, я объясню это ясно.

import numpy as np import time import multiprocessing

Numpy — очень полезная библиотека, когда дело доходит до вычислений. Вы обнаружите, что можете выполнять некоторые мощные операции всего одной строкой кода. Мы импортируем время, чтобы отметить, сколько времени требуется для выполнения основной части кода.

Теперь давайте создадим два массива «a» и «b» по миллиону элементов в каждом. Давайте случайным образом распределим между ними значения с помощью функции rand().

a=np.random.rand(1000000) b=np.random.rand(1000000)

Теперь, когда у нас есть два готовых и загруженных массива, давайте попробуем придумать способ вычисления «с» и достижения нашей цели. Первое и самое простое решение, которое приходит нам в голову, — это запустить цикл for в диапазоне от миллиона и умножить каждый элемент в обоих массивах, а затем итеративно добавлять их к c. Код будет выглядеть примерно так:

def func(a,b): c=0 for i in range(1000000): c+=a[i]*b[i] return c

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

Лучший способ решить эту проблему — использовать распараллеливание. Давайте забудем, что наши учебники или гугл определяют как распараллеливание, и попробуем разобраться. Слово «параллельный» означает, что существует как минимум пара сущностей (какими бы эти сущности нас не касались в данный момент), которые существуют одновременно, т.е. в одно и то же время. Теперь вы, возможно, слышали некоторые причудливые термины, такие как двухъядерные, четырехъядерные и восьмиъядерные процессоры. Это означает, что процессор состоит из двух (двойных), четырех (четверных) или восьми (окта) частей. Использование этих нескольких ядер для одновременных вычислений или обработки называется параллельной обработкой или просто параллелизмом или распараллеливанием. Это также называется параллельными вычислениями, разница в том, что параллельные вычисления связаны с программной вычислительной частью, а параллельная обработка связана с аппаратным обеспечением или, скорее, с многоядерной частью. Код, который мы написали ранее, не использует эту многоядерную возможность, которая есть в большинстве современных компьютеров. Таким образом, мы можем использовать эту концепцию для одновременного запуска нескольких процессов и одновременного вычисления одной и той же части кода с использованием нескольких ядер ЦП. Это значительно сокращает время, необходимое для выполнения.

В python у нас есть библиотека многопроцессорной обработки, с помощью которой мы можем выполнять многопроцессорную или параллельную обработку. Мы делаем это с помощью класса Pool в этой библиотеке, в котором инициализируется многопроцессорность. Объект Pool() создаст набор процессов, называемых рабочими, и они будут работать параллельно. Здесь я собираюсь использовать метод map_async в этом классе, поскольку он не покидает текущую основную программу, а вычисления выполняются в фоновом режиме. Код будет выглядеть так:

if __name__ == '__main__': pool = multiprocessing.Pool() r = pool.apply_async(func, args = (a,b)) c=r.get()

Здесь func — это метод, определенный ранее.

Обратите внимание, что здесь r — это объект, поэтому использование метода get() даст нам значение после вычисления. После успешной реализации мы можем значительно сократить время выполнения. Теоретически это может сократить время в N раз, где N — количество ядер, используемых для вычислений. Таким образом, если у вас есть восьмиядерная система, вы можете сократить время вычислений как минимум в 8 раз.

Теперь вы можете быть довольны, но вам не кажется, что это немного сложно? Попробуйте параллельную обработку самостоятельно, и вы обнаружите, что это немного сложно. Теперь, если я скажу вам, что есть более простой способ, и все, что нужно, это одна строка кода, вы мне поверите? Может быть, а может и нет. Но это правда. Есть способ, в котором нам нужно написать только одну небольшую строку кода, и он работает намного быстрее, чем даже параллельные вычисления, я имею в виду НАМНОГО быстрее. Этот, казалось бы, утопический код можно реализовать с помощью векторизации. Итак, что такое векторизация? Это своего рода параллельная обработка, но она неявна. Вам не нужно делать это явно. На данный момент у нас есть то, что сначала мы повторили одно и то же утверждение миллион раз, затем мы сделали то же самое, но разделили рабочую нагрузку между несколькими ядрами ЦП. Теперь концепция векторизации заключается в том, что ЦП имеет несколько векторных инструкций, которые заставляют его повторять одну и ту же операцию одновременно с несколькими частями данных. Это некоторые предопределенные инструкции, которые работают параллельно. Использование векторизации устраняет потребность в циклах. так что теперь нам не нужно итеративно повторять выполнение одного и того же оператора, а просто использовать предопределенную функцию. Итерация выполняется с помощью простой инструкции машинного уровня, которая снова выполняется на нескольких ядрах. Векторизация — мощнейшая концепция, реализация которой очень упростит написание кода, а также резко и резко, если позволите добавить, сократит время выполнения. Векторизованный код для нашей цели выглядит просто:

c=np.dot(a,b)

Здесь dot() — это метод в библиотеке numpy, с помощью которого результат вычисляется напрямую, без необходимости в цикле. То, что мы пытаемся сделать здесь, является алгебраическим скалярным произведением двух векторов.

Давайте рассмотрим все три метода одновременно и сравним их. Код:

import numpy as np
import time
import multiprocessing

#A function func is defined to multiply each elements in 'a' and 'b' into c
def func(a,b):
    c=0
    for i in range(1000000):
        c+=a[i]*b[i]
    return c


if __name__ == '__main__':
    a=np.random.rand(1000000) #Randomly assigning a million values to 'a'
    b=np.random.rand(1000000)
    print() #Blank line for better presentation of output
    #Traditional for-loop
    t1=time.time() #t1 is the time when this part of the code starts to execute
    c=func(a,b)
    t2=time.time() #t2 is the time when the for-loop computation is done

    print('Value of c : '+str(c))
    print('Time taken for traditional for-loop code :  '+ str(1000*(t2-t1)) + ' ms' )
    print()
    #t2-t1 gives the exact time taken for computation.
    #We multiply it by 1000 to view the result in milliseconds
.

    #Parallel Processing Code
    t1=time.time() #t1 is the time when this part of the code starts to execute

    pool = multiprocessing.Pool()
    c = pool.apply_async(func, args = (a,b))

    t2=time.time()
    print('Value of c : '+str(c.get()))
    print('Time taken for parallel processing code :  '+ str(1000*(t2-t1)) + ' ms' )
    print()

    #Vectorization
    t1=time.time()
    c=np.dot(a,b)
    t2=time.time()

    print('Value of c : '+str(c))
    print('Time taken for vectorized code           :  '+ str(1000*(t2-t1)) + ' ms' )
    print()

После выполнения этого кода мы можем увидеть результат следующим образом:

Как мы видим, многопроцессорная обработка намного быстрее, чем наш традиционный метод, но, что наиболее важно, векторизация дает нам удивительные результаты и намного быстрее, чем остальные.

Я узнал о векторизации в курсе «Нейронные сети и глубокое обучение» от deeplearning.ai на Coursera. Замечательный преподаватель курса г-н Эндрю Н.Г. объяснил концепцию векторизации и показал, насколько она быстрее, чем циклы, и как устраняет необходимость в них. Это пробудило во мне интерес, и я хотел посмотреть, насколько хорошо векторизация справляется с параллельной обработкой, что и побудило меня написать эту статью.

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

Наконец, я хотел бы поблагодарить вас за то, что вы были со мной до конца этого поста. Если вы хотите обсудить что-то конкретное, оставьте ответ внизу. В ближайшие дни мы опубликуем много полезных блогов, следите за обновлениями нашего блога. А пока оставайтесь умными и экономьте время.

Первоначально опубликовано на http://thelastbyteblog.wordpress.com 9 июня 2020 г.