Быстрый Pythonic способ преобразования ByteArray в строку с заменами на месте

#python #python-3.x #performance

#python #python-3.x #Производительность

Вопрос:

Пожалуйста, рассмотрите следующий код. Цель здесь — создать типичный дамп двоичных данных ascii, заменив непечатаемые символы на '.' .

Оба сегмента кода выводят один и тот же результат. Второй кажется более «питоническим», но (по моим измерениям) в 2-3 раза медленнее первого, предположительно потому, что он создает больше временных объектов.

Поскольку я собираюсь делать это много миллионов раз, производительность имеет значение. Есть ли более быстрый Pythonic способ сделать это?

 ba = bytearray.fromhex("01610262") # non-printable binary data, 'a', binary, 'b'
for i in range(len(ba)):
    if not chr(ba[i]).isprintable():
        ba[i] =  ord('.')
text = ba.decode("ascii")
print(text)  # prints ".a.b"

ba = bytearray.fromhex("01610262")
text = bytes(map(lambda b: ord('.') if not chr(b).isprintable() else b, ba)).decode("ascii")
print(text)  # prints ".a.b"
  

Версия с измерением производительности:

 ITERATIONS=1000000

start = time.time()
for z in range(ITERATIONS):
    ba = bytearray.fromhex("01610262")
    for i in range(0, len(ba)):
        if not chr(ba[i]).isprintable():
            ba[i] =  ord('.')
    text = ba.decode("ascii")
    #print(text)
end = time.time()
print("first elapsed time:", (end-start))

start = time.time()
for z in range(ITERATIONS):
    ba = bytearray.fromhex("01610262")
    text = bytes(map(lambda b: ord('.') if not chr(b).isprintable() else b, ba)).decode("ascii")
    #print(text)
end = time.time()
print("second elapsed time:", (end-start))
  

Выводит:

 first elapsed time: 2.4349358081817627
second elapsed time: 5.805044889450073
  

Обновить:
Обнаружил, что запуск теста синхронизации из командной строки вместо моей IDE (к которой был подключен отладчик) как увеличил производительность, так и радикально уменьшил разницу в производительности.

Принятый ответ (с использованием таблицы перевода) намного быстрее при запуске из командной строки.

Решение Марата «.join() в комментариях находится между первым и вторым вариантами, приведенными выше, при запуске из командной строки (но намного хуже в моем отладчике [с использованием расширений Visual Studio Code Python].)

Комментарии:

1. timeit показывает гораздо менее значительную разницу между этими подходами, 735n против 864n. Разница может быть связана с тем, как распределяются переменные, а не с эффективностью метода

2. ''.join(chr(b) if b < 127 else '.' for b in ba) только немного быстрее (645n), но определенно более удобочитаем

3. не могли бы вы создать лямбда-функцию вне map функции. Это улучшит скорость

4. @Marat — спасибо за предложение присоединиться. В моих тестах он упал между первым и вторым по времени — при запуске из команды. При запуске в моей IDE (которая подключает отладчик), это на порядок медленнее.

Ответ №1:

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

 btrans = bytes.maketrans(b'x01x02',b'..')
for z in range(ITERATIONS):
    ba = bytearray.fromhex("01610262")
    text = ba.translate(btrans).decode('ascii')
    #print(text)
end = time.time()
print("third elapsed time:", (end-start))

first elapsed time: 1.4424219131469727
second elapsed time: 1.1425127983093262
third elapsed time: 0.3709402084350586
  

Комментарии:

1. Настройка таблицы перевода стоила увеличения производительности. Спасибо! @jonathan