Эффективный байт умножением с плавающей запятой

На входе у меня есть подписанный массив байтов barr (обычно с прямым порядком байтов, но это, вероятно, не имеет значения) и число с плавающей запятой f для умножения barr.

Мой подход состоит в том, чтобы преобразовать barr в целое число val (используя функцию int.from_bytes), умножить его, выполнить проверки переполнения и "обрезать" умноженное на val при необходимости, а затем преобразовать его обратно в массив байтов.

def multiply(barr, f):
        val = int.from_bytes(barr, byteorder='little', signed=True)
        val *= f
        val = int (val)
        val = cropInt(val, bitLen = barr.__len__()*8)
        barr = val.to_bytes(barr.__len__(), byteorder='little', signed=True)
        return barr

def cropInt(integer, bitLen, signed = True):
        maxValue = (2**(bitLen-1)-1) if signed else (2**(bitLen)-1)
        minValue = -maxValue-1 if signed else 0
        if integer > maxValue:
            integer = maxValue
        if integer < minValue:
            integer = minValue
        return integer

Однако этот процесс чрезвычайно медленный при обработке большого объема данных. Есть ли лучший, более эффективный способ сделать это?


person SlowerPhoton    schedule 08.11.2016    source источник
comment
Попробуйте использовать модуль struct для анализа последовательностей байтов. Также вы можете удалить расчеты maxValue и minValue из функции cropInt, чтобы увеличить ее скорость.   -  person Stanislav Ivanov    schedule 08.11.2016
comment
К сожалению, каждый массив байтов может иметь разную длину, поэтому мне нужно считать его каждый раз (но поскольку они в основном имеют одинаковую длину, я мог бы создать кратковременную память для вычислений).   -  person SlowerPhoton    schedule 08.11.2016
comment
Из быстрой timeit сессии большая часть времени уходит на int.from_bytes() и int.to_bytes(), которые будет сложно сделать быстрее   -  person Guillaume    schedule 08.11.2016
comment
Вы имели в виду более эффективный?   -  person Jim Mischel    schedule 08.11.2016
comment
Да, прости. Пост и его заголовок отредактированы   -  person SlowerPhoton    schedule 09.11.2016


Ответы (1)


Чистый Python довольно неэффективен для любых числовых вычислений, потому что из-за того, что каждое число рассматривается как объект, каждая операция включает в себя множество «внутренних» шагов.

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

В вашем случае, поскольку производительность имеет значение, вы можете использовать NumPy — фактический пакет Python для числовой обработки. .

С его помощью приведение, умножение и преобразование будут выполняться в собственном коде за один проход каждый (и после того, как я знаю NumPy лучше, чем я, возможно, с еще меньшим количеством шагов) - и должно дать вам улучшение скорости на 3-4 порядка. для этой задачи:

import numpy as np
def multiply(all_bytes, f, bitlen, signed=True): 

    # Works for 8, 16, 32 and 64 bit integers:
    dtype = "%sint%d" % ("" if signed else "",   bitlen)
    max_value = 2 ** (bitlen- (1 if signed else 0)) - 1

    input_data = np.frombuffer(all_bytes, dtype=dtype)
    processed = np.clip(input_data * f, 0, max_value)
    return bytes(processed.astype(dtype))

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

Строка, которая идет dtype = "%sint%d" % ("" if signed else "", bitlen), создает имя типа данных, используемое NumPy, из количества переданных битов. Поскольку имя представляет собой просто строку, оно интерполирует строку, добавляя или не добавляя префикс «u», в зависимости от типа данных. без знака, и поставить количество битов в конце. Типы данных NumPy можно проверить по адресу: https://docs.scipy.org/doc/numpy/user/basics.types.html

Работая с массивом из 500000 8-битных целых чисел со знаком, я получаю следующие тайминги:

В [99]: %time y = numpy_multiply(data, 1.7, 8) Процессорное время: пользовательское 3,01 мс, системное: 4,96 мс, общее: 7,97 мс Время стены: 7,38 мс

В [100]: %time x = original_multiply(data, 1.7, 8) Время ЦП: пользователь 11,3 с, система: 1,86 мс, всего: 11,3 с Время стены: 11,3 с

(То есть после изменения вашей функции для работы со всеми байтами одновременно) - ускорение в 1500 раз, как я указал в первом черновике.

person jsbueno    schedule 08.11.2016
comment
К вашему сведению, это не дает того же результата, что и метод OP. (но я не знаю почему...) - person Guillaume; 08.11.2016
comment
И на самом деле он медленнее, чем метод OP. - person Guillaume; 08.11.2016
comment
Извините - это не может быть медленнее, чем метод OP, но если вы пытаетесь использовать одно число за раз - это требует всего объема данных, необходимых OP сразу. - person jsbueno; 08.11.2016
comment
Я хочу вам верить, но время говорит об обратном :) : с произвольными my_bytes = b"1EZR3" и my_float = 8.0 я получаю 5,972544721647864 для вашего и 3,836732462648797 для OP. Но я не могу получить объяснение этой разницы. - person Guillaume; 08.11.2016
comment
1000000 раз импорт np явно не включен в цикл - person Guillaume; 08.11.2016
comment
Нет - ставить 1000000 байт в вызове функции, а не 5 байт в вызове функции. - person jsbueno; 08.11.2016
comment
@Gullaume - в любом случае, спасибо за комментарий - это действительно было неправильно, поскольку OP обрезает значения, а я просто обрезал наиболее важные байты после умножения. - person jsbueno; 08.11.2016
comment
Я сделал тот же тест с my_bytes = b"1EZR3"*10000 , я получил 138,9779563455486 для вас и OverflowError: int too large to convert to float на val *= f для OP. С my_bytes = b"1EZR3"*10 я получаю 12,382181490880612 для вас и 4,78680513379004 для OP (так что OP лучше масштабируется, пока не достигнет предела плавающей запятой) - person Guillaume; 08.11.2016
comment
Извините, вы по-прежнему не можете использовать функцию, которую я передал. (Сейчас я поставил версию incorec перед исправленной). Если вы этого не видите, функция OP работает по одному числу за раз, в то время как приведенный мной пример с NumPy будет умножать все его данные. Я добавляю тест quck для 500000 8-битных чисел к ответу сейчас - - person jsbueno; 08.11.2016
comment
Спасибо всем за помощь и тестирование времени выполнения. Я очень ценю дух этого сайта. Однако я не совсем понимаю этот кусок кода: dtype = "%sint%d" % ("" if signed else "", bitlen), что это значит? - person SlowerPhoton; 08.11.2016
comment
Я добавил абзац к ответу, чтобы объяснить эту строку сейчас. - person jsbueno; 08.11.2016
comment
Разве это не должно быть похоже на dtype = "%sint%d" % ("" if signed else "u", bitlen) тогда? - person SlowerPhoton; 08.11.2016
comment
Также необходимо также вычислить min_value = -max_value - 1 и заменить 0 в np.clip(input_data * f, 0, max_value) на min_value, так как в вашей реализации -100*0,2 всегда устанавливается равным нулю. - person SlowerPhoton; 09.11.2016
comment
Вы имели в виду неэффективно в первом предложении? - person Jim Mischel; 09.11.2016