самая быстрая упаковка данных в Python (и Java) - PullRequest
20 голосов
/ 27 марта 2012

( Иногда наш хост неверен; наносекунды имеют значение;)

У меня есть сервер Python Twisted, который общается с некоторыми серверами Java, и профилирование показывает, что он тратит ~ 30% времени выполнения в кодировщике / декодере JSON; его задача - обрабатывать тысячи сообщений в секунду.

Этот доклад на YouTube поднимает интересные применимые вопросы:

  • Форматы сериализации - независимо от того, какой из них вы используете, все они дорого. Мера. Не используйте маринад. Не очень хороший выбор. Найденный буферы протокола медленные. Они написали свою собственную реализацию BSON, которая в 10-15 раз быстрее, чем тот, который вы можете загрузить.

  • Вы должны измерить. Витесс обменял один из своих протоколов на HTTP реализация. Хотя это было в C, это было медленно. Так они разорвались HTTP и сделал прямой вызов сокета с использованием Python, и это было 8% дешевле на глобальном процессоре. Оболочка для HTTP действительно дорогая.

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

Во всяком случае, я контролирую как Python, так и Java моего API для передачи сообщений и могу выбрать сериализацию, отличную от JSON.

Мои сообщения выглядят так:

  • переменное количество длинных; где-то между 1 и 10К из них
  • и две уже текстовые строки UTF8; от 1 до 3 КБ

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

Другой конец этого потока, конечно же, Java-сервер; Я не хочу выбирать то, что отлично подходит для конца Python, но переносит проблемы в конец Java, например. производительность или извилистый или нестабильный API.

Я, очевидно, буду заниматься своим профилированием. Я спрашиваю здесь в надежде, что вы опишите подходы, о которых я бы не подумал, например. используя struct и самый быстрый тип строк / буферов.

Несколько простых тестовых кодов дают удивительные результаты:

import time, random, struct, json, sys, pickle, cPickle, marshal, array

def encode_json_1(*args):
    return json.dumps(args)

def encode_json_2(longs,str1,str2):
    return json.dumps({"longs":longs,"str1":str1,"str2":str2})

def encode_pickle(*args):
    return pickle.dumps(args)

def encode_cPickle(*args):
    return cPickle.dumps(args)

def encode_marshal(*args):
    return marshal.dumps(args)

def encode_struct_1(longs,str1,str2):
    return struct.pack(">iii%dq"%len(longs),len(longs),len(str1),len(str2),*longs)+str1+str2

def decode_struct_1(s):
    i, j, k = struct.unpack(">iii",s[:12])
    assert len(s) == 3*4 + 8*i + j + k, (len(s),3*4 + 8*i + j + k)
    longs = struct.unpack(">%dq"%i,s[12:12+i*8])
    str1 = s[12+i*8:12+i*8+j]
    str2 = s[12+i*8+j:]
    return (longs,str1,str2)

struct_header_2 = struct.Struct(">iii")

def encode_struct_2(longs,str1,str2):
    return "".join((
        struct_header_2.pack(len(longs),len(str1),len(str2)),
        array.array("L",longs).tostring(),
        str1,
        str2))

def decode_struct_2(s):
    i, j, k = struct_header_2.unpack(s[:12])
    assert len(s) == 3*4 + 8*i + j + k, (len(s),3*4 + 8*i + j + k)
    longs = array.array("L")
    longs.fromstring(s[12:12+i*8])
    str1 = s[12+i*8:12+i*8+j]
    str2 = s[12+i*8+j:]
    return (longs,str1,str2)

def encode_ujson(*args):
    return ujson.dumps(args)

def encode_msgpack(*args):
    return msgpacker.pack(args)

def decode_msgpack(s):
    msgunpacker.feed(s)
    return msgunpacker.unpack()

def encode_bson(longs,str1,str2):
    return bson.dumps({"longs":longs,"str1":str1,"str2":str2})

def from_dict(d):
    return [d["longs"],d["str1"],d["str2"]]

tests = [ #(encode,decode,massage_for_check)
    (encode_struct_1,decode_struct_1,None),
    (encode_struct_2,decode_struct_2,None),
    (encode_json_1,json.loads,None),
    (encode_json_2,json.loads,from_dict),
    (encode_pickle,pickle.loads,None),
    (encode_cPickle,cPickle.loads,None),
    (encode_marshal,marshal.loads,None)]

try:
    import ujson
    tests.append((encode_ujson,ujson.loads,None))
except ImportError:
    print "no ujson support installed"

try:
    import msgpack
    msgpacker = msgpack.Packer()
    msgunpacker = msgpack.Unpacker()
    tests.append((encode_msgpack,decode_msgpack,None))
except ImportError:
    print "no msgpack support installed"

try:
    import bson
    tests.append((encode_bson,bson.loads,from_dict))
except ImportError:
    print "no BSON support installed"

longs = [i for i in xrange(10000)]
str1 = "1"*5000
str2 = "2"*5000

random.seed(1)
encode_data = [[
        longs[:random.randint(2,len(longs))],
        str1[:random.randint(2,len(str1))],
        str2[:random.randint(2,len(str2))]] for i in xrange(1000)]

for encoder,decoder,massage_before_check in tests:
    # do the encoding
    start = time.time()
    encoded = [encoder(i,j,k) for i,j,k in encode_data]
    encoding = time.time()
    print encoder.__name__, "encoding took %0.4f,"%(encoding-start),
    sys.stdout.flush()
    # do the decoding
    decoded = [decoder(e) for e in encoded]
    decoding = time.time()
    print "decoding %0.4f"%(decoding-encoding)
    sys.stdout.flush()
    # check it
    if massage_before_check:
        decoded = [massage_before_check(d) for d in decoded]
    for i,((longs_a,str1_a,str2_a),(longs_b,str1_b,str2_b)) in enumerate(zip(encode_data,decoded)):
        assert longs_a == list(longs_b), (i,longs_a,longs_b)
        assert str1_a == str1_b, (i,str1_a,str1_b)
        assert str2_a == str2_b, (i,str2_a,str2_b)

дает:

encode_struct_1 encoding took 0.4486, decoding 0.3313
encode_struct_2 encoding took 0.3202, decoding 0.1082
encode_json_1 encoding took 0.6333, decoding 0.6718
encode_json_2 encoding took 0.5740, decoding 0.8362
encode_pickle encoding took 8.1587, decoding 9.5980
encode_cPickle encoding took 1.1246, decoding 1.4436
encode_marshal encoding took 0.1144, decoding 0.3541
encode_ujson encoding took 0.2768, decoding 0.4773
encode_msgpack encoding took 0.1386, decoding 0.2374
encode_bson encoding took 55.5861, decoding 29.3953

bson , msgpack и ujson все установлено через easy_install

Я бы полюбил , чтобы мне показали, что я делаю это неправильно; что я должен использовать интерфейсы cStringIO или как бы то ни было, вы все ускорите!

Должен быть способ сериализации этих данных, который на порядок быстрее?

Ответы [ 6 ]

6 голосов
/ 20 ноября 2012

В итоге мы решили использовать msgpack.

Если вы используете JSON, выбор библиотеки на Python и Java имеет решающее значение для производительности:

На Java http://blog.juicehub.com/2012/11/20/benchmarking-web-frameworks-for-games/ говорит:

Производительность была абсолютно ужасной, пока мы не обменяли JSON Lib (json-simple) на ObjectMapper Джекона. Это принесло RPS от 35 до 300+ - 10-кратное увеличение

6 голосов
/ 27 марта 2012

Несмотря на то, что JSon является гибким, он является одним из самых медленных форматов сериализации в Java (возможно, также и с python) в нано-секундах. Я бы использовал двоичный формат в собственном порядке байтов (вероятно, с прямым порядком байтов)

Вот библиотека, где я делаю именно то, что AbstractExcerpt и UnsafeExcerpt Типичное сообщение занимает от 50 до 200 нс для сериализации, отправки или чтения и десериализации.

3 голосов
/ 11 января 2016

Я знаю, что это старый вопрос, но он все еще интересен. Мой самый последний выбор - использовать Cap’n Proto , написанный тем же человеком, который делал protobuf для Google. В моем случае это привело к уменьшению времени и громкости примерно на 20% по сравнению с JSON-кодером / декодером Джексона (сервер-сервер, Java с обеих сторон).

3 голосов
/ 27 марта 2012

Возможно, вы сможете ускорить структурный регистр

def encode_struct(longs,str1,str2):
    return struct.pack(">iii%dq"%len(longs),len(longs),len(str1),len(str2),*longs)+str1+str2
  1. Попробуйте использовать модуль массива python и метод tostring, чтобы преобразовать ваши длинные значения в двоичную строку. Затем вы можете добавить его, как вы сделали со строками
  2. Создайте struct.Struct объект и используйте его. Я считаю, что это более эффективно

Вы также можете посмотреть на:

http://docs.python.org/library/xdrlib.html#module-xdrlib

Ваш самый быстрый метод кодирует 1000 элементов за 0,122 секунды. Это 1 элемент в .1222 миллисекундах. Это довольно быстро. Я сомневаюсь, что вы будете намного лучше без переключения языков.

2 голосов
/ 27 марта 2012

Буферы протокола довольно быстрые и имеют привязки для Java и Python.Это довольно популярная библиотека, которая используется в Google, поэтому ее следует хорошо тестировать и оптимизировать.

0 голосов
/ 27 марта 2012

Поскольку данные, которые вы отправляете, уже хорошо определены, не рекурсивны и не вложены, почему бы просто не использовать простую строку с разделителями.Вам просто нужен разделитель, который не содержится в строковых переменных, возможно, '\ n'.

"10\n1\n2\n3\n4\n5\n6\n7\n8\n9\n10\nFoo Foo Foo\nBar Bar Bar"

Затем просто используйте простой метод разделения строк.

string[] temp = str.split("\n");
ArrayList<long> longs = new ArrayList<long>(long.parseLong(temp[0]));
string string1 = temp[temp.length-2];
string string2 = temp[temp.length-1];
for(int i = 1; i < temp.length-2 ; i++)
    longs.add(long.parseLong(temp[i]));

примечание Выше написано в веб-браузере и не проверялось, поэтому могут существовать синтаксические ошибки.

Для текстового интерфейса;Я бы предположил, что описанный выше самый быстрый метод.

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