Подтвердить что ты не робот

Самая быстрая упаковка данных в Python (и Java)

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

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

Этот разговор от youtube вызывает интересные применимые моменты:

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

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

  • Измерение

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

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

Мои сообщения выглядят следующим образом:

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

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

Другой конец этого потока - сервер Java, конечно; Я не хочу выбирать то, что отлично подходит для конца Python, но перемещает проблемы на конец Java, например. производительность или мучительный или flaky 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 или, тем не менее, вы ускоряете все это!

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

4b9b3361

Ответ 1

В конце концов, мы решили использовать msgpack.

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

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

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

Ответ 2

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

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

Ответ 3

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

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

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

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

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

Ответ 4

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

Ответ 5

Я знаю, что это старый вопрос, но он все еще интересен. Мой последний выбор заключался в том, чтобы использовать Capn Proto, написанный тем же парнем, который сделал protobuf для google. В моем случае это приводит к уменьшению как времени, так и объема примерно на 20% по сравнению с кодеком/декодером Jackson JSON (от сервера к серверу, Java с обеих сторон).

Ответ 6

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

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

Затем просто используйте простой метод String Split.

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]));

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

Для текста; Я бы предположил, что это самый быстрый метод.