Одним тёплым холодным зимним вечером, хотелось согреться в офисе и проверить теорию одного коллеги, что C++ vector мог бы быстрее справиться с задачей, чем CPython list.
В компании мы разрабатываем продукты на базе Django и случилось так, что нужно было обработать один большой массив словарей. Коллега предположил, что реализация на C++ была бы гораздо быстрее, а меня не покидало чувство, что Гвидо и сообщество наверное немного круче нас в Си и возможно уже решили и обошли все подводные камни, реализовав всё гораздо быстрее.
Для проверки теории, я решил написать небольшой тестовый файл, в котором решил прогнать в цикле вставку 1М словарей одинакового содержания в массив и в vector 100 раз подряд.
Результаты хоть и были ожидаемые, но так же и внезапные.
Так уж вышло, что мы активно используем Cython, поэтому в целом результаты будут отличаться на полностью CPython реализации.
Стенд
- Calculate Linux onegreyonewhite 4.18.14-calculate #1 SMP PREEMPT Sat Oct 13 21:03:27 UTC 2018 x86_64 Intel® Core(TM) i7-4770 CPU @ 3.40GHz GenuineIntel GNU/Linux
- Python 2.7 и 3.6
- Cython 0.28.3
- gcc (Gentoo 7.3.0-r3 p1.4)
Скрипт
К слову, здесь пришлось повозиться. Чтобы получить максимально реальные числа (т.е. не просто сделать супероптимизировано, но и так, что мы потом сможем это использовать без танцев с бубном), пришлось всё делать основном скрипте, а все дополнительные .h свести к минимуму.
Первая проблема заключалась в том, что обёртка Cython для vector не хочет работать в таком виде:
# Так не хотел
ctypedef vector[object] dict_vec
# И так не завелось (ошибка появлялась на vector.push_back(dict()))
ctypedef vector[PyObject*] dict_vec
# И даже так, что удивительно (просто говорит, что не может object привести к PyObject.)
ctypedef vector[PyObject] dict_vec
При всём при этом получали ошибку, что невозможно привести dict к PyObject. Конечно же это проблемы Cython, но так как мы его используем, нам нужно решить эту конкретную проблему.
Пришлось сделать маленький костылик в виде
#include "Python.h"
static PyObject * convert_to_pyobject(PyObject *obj)
{
return obj;
}
Самое удивительное, что это заработало. Больше всего меня пугает, что я до конца не понимаю почему и какие последствия влечёт.
cython_experiments.h
#include "Python.h"
static PyObject * convert_to_pyobject(PyObject *obj)
{
return obj;
}
cython_experiments.pyx
# -*- coding: utf-8 -*-
# distutils: language = c++
# distutils: include=['./']
# distutils: extra_compile_args=["-O1"]
from __future__ import unicode_literals
import time
from libc.stdlib cimport free
from cpython.dict cimport PyDict_New, PyDict_SetItemString
from cpython.ref cimport PyObject
from libcpp.string cimport string
from libcpp.vector cimport vector
cdef extern from "cython_experiments.h":
PyObject* convert_to_pyobject(object obj)
ctypedef vector[PyObject*] dict_vec
range_attempts = 10 ** 6
# Insert time
cdef test_list():
t_start = time.time()
data_list = list()
for i from 0 <= i < range_attempts:
data_list.append(dict(
name = 'test_{}'.format(i),
test_data = i,
test_data2 = str(i),
test_data3 = range(10),
))
del data_list
return time.time() - t_start
cdef test_vector():
t_start = time.time()
cdef dict_vec *data_list
data_list = new dict_vec()
data_list.resize(range_attempts)
for i from 0 <= i < range_attempts:
data = PyDict_New()
PyDict_SetItemString(data, 'name', 'test_{}'.format(i))
PyDict_SetItemString(data, 'test_data', i)
PyDict_SetItemString(data, 'test_data2', str(i))
PyDict_SetItemString(data, 'test_data3', range(10))
data_list.push_back(convert_to_pyobject(data))
free(data_list)
return time.time() - t_start
# Get statistic
times = dict(list=[], vector=[])
attempts = 100
for i from 0 <= i < attempts:
times['list'].append(test_list())
times['vector'].append(test_vector())
print('''
Attempt: {}
List time: {}
Vector time: {}
'''.format(i, times['list'][-1], times['vector'][-1]))
avg_list = sum(times['list']) / attempts
avg_vector = sum(times['vector']) / attempts
print('''
Statistics:
attempts: {}
list avg time: {}
vector avg time: {}
'''.format(attempts, avg_list, avg_vector))
Попытка 1
Очень хочется, чтобы можно было собирать *.whl для проекта и чтобы это всё завелось на практически любой системе, поэтому сперва был выставлен флаг оптимизации в 0. Это привело к странному результату:
Python 2.7
Statistics:
attempts: 100
list avg time: 2.61709237576
vector avg time: 2.92562381506
Немного поразмыслив, решил что мы всё равно используем флаг -O1, поэтому выставил всё же его и получил:
Python 2.7
Statistics:
attempts: 100
list avg time: 2.49274396896
vector avg time: 0.922211170197
Как-то немного взгруснулось: всё же вера в профессионализм Гвидо и Ко меня подвела. Но потом, я заметил что как-то подозрительно жрёт память скрипт и к концу он подъедал примерно 20Гб ОЗУ. Проблема была в следующем: в итоговом скрипте, можно наблюдать функцию free, после прохода цикла. На этой итерации его ещё не было. Тогда я подумал...
А не отключить ли мне gc?
Между попытками я сделал gc.disable() и после попытки gc.enable(). Запускаю сборку и скрипт и получаю:
Python 2.7
Statistics:
attempts: 100
list avg time: 1.00309731514
vector avg time: 0.941153049469
В целом, разница не большая, поэтому я подумал, что нет смысла переплачивать стараться как-то извратиться и просто использовать CPython, но собирать его по прежнему Cython'ом.
Наверное у многих возник вопрос: "А что там с памятью?" Самое удивительное (нет), что ничего. Она росла с такой же скоростью и в таком же количестве. На ум пришла статья, но лезть в исходники Python совсем не хотелось. Да и означало это лишь одно — проблема в реализации вектора.
Финал
После долгих мучений с приведением типов, а именно, чтобы вектор принимал в себя pointer на словарь, был получен тот самый итоговый скрипт и с включённым gc я получал в среднем разницу в 2.6 раза (вектор быстрее) и относительно хорошую работу с памятью.
Вдруг до меня дошло, что я собираю всё только под Py2.7 и даже не попробовал сделать что-либо с 3.6.
И вот тут я реально удивился (после предыдущих результатов, удивление было закономерным):
Python 3.6
Statistics:
attempts: 100
list avg time: 0.8771139788627624
vector avg time: 1.075702157020569
Python 2.7
Statistics:
attempts: 100
list avg time: 2.61709237576
vector avg time: 0.92562381506
При всём при этом, gc по прежнему работал, память не отжиралась и это был один и тот же скрипт. Понимая, что через уже чуть больше года, нужно будет распрощаться с 2.7, мне всё равно не давало покоя, что между ними такая разница. Чаще всего, я слышал/читал/экспериментировал и Py3.6 был медленнее Py2.7. Однако ребята из Cython-разработчиков сделали что-то невероятное и поменяли ситуацию на корню.
Итог
После этого эксперимента, мы решили сильно не заморачиваться с поддержкой Python 2.7 и переделкой каких-либо частей приложений на C++, просто потому что оно того не стоит. Всё уже написали до нас, нам остаётся это лишь правильно применить для решения конкретной задачи.
UPD 24.12.2018:
По совету iCpu и после выпадов в сторону, что проверяется непонять что и как, постарался переписать C++ часть максимально удобным для разработки в дальнейшем способом, а так же минимизировать абстракции. Получилось ещё хуже:
cython_experiments.h
#include "Python.h"
#include <vector>
#include <algorithm>
#ifndef PyString_AsString
#define PyString_AsString PyUnicode_AsUTF8
#define PyString_FromString PyUnicode_FromString
#endif
typedef struct {
char* name;
bool reverse;
} sortFiled;
class cmpclass {
public:
cmpclass(std::vector<char*> fields) {
for (std::vector<char*>::iterator it = fields.begin() ; it < fields.end(); it++){
bool is_reverse = false;
char* name;
if (it[0] == "-"){
is_reverse = true;
for(int i=1; i<strlen(*it); ++i)
name[i] = *it[i];
}
else {
name = *it;
}
sortFiled field = {name, is_reverse};
this->fields_to_cmp.push_back(field);
}
}
~cmpclass() {
this->fields_to_cmp.clear();
this->fields_to_cmp.shrink_to_fit();
}
bool operator() (PyObject* left, PyObject* right) {
//
bool result = false;
for (std::vector<sortFiled>::iterator it = this->fields_to_cmp.begin() ; it < this->fields_to_cmp.end(); it++){
//
PyObject* str_name = PyString_FromString(it->name);
PyObject* right_value = PyDict_GetItem(right, str_name);
PyObject* left_value = PyDict_GetItem(left, str_name);
if(!it->reverse){
result = left_value < right_value;
} else {
result = (left_value > right_value);
}
PyObject_Free(str_name);
if(!result)
return false;
}
return true;
}
private:
std::vector<sortFiled> fields_to_cmp;
};
void vector_multikeysort(std::vector<PyObject *> items, PyObject* columns, bool reverse)
{
std::vector<char *> _columns;
for (int i=0; i<PyList_GET_SIZE(columns); ++i) {
PyObject* item = PyList_GetItem(columns, i);
char* item_str = PyString_AsString(item);
_columns.push_back(item_str);
}
cmpclass cmp_obj(_columns);
std::sort(items.begin(), items.end(), cmp_obj);
if(reverse)
std::reverse(items.begin(), items.end());
}
std::vector<PyObject *> _test_vector(PyObject* store_data_list, PyObject* columns, bool reverse = false)
{
int range_attempts = PyList_GET_SIZE(store_data_list);
std::vector<PyObject *> data_list;
for (int i=0; i<range_attempts; ++i) {
data_list.push_back(PyList_GetItem(store_data_list, i));
}
vector_multikeysort(data_list, columns, reverse);
return data_list;
}
cython_experiments.pyx
# -*- coding: utf-8 -*-
# distutils: language = c++
# distutils: include=['./']
# distutils: extra_compile_args=["-O2", "-ftree-vectorize"]
from __future__ import unicode_literals
import time
from libc.stdlib cimport free
from cpython.dict cimport PyDict_New, PyDict_SetItemString
from cpython.ref cimport PyObject
from libcpp.string cimport string
from libcpp.vector cimport vector
import gc
cdef extern from "cython_experiments.h":
vector[PyObject*] _test_vector(object store_data_list, object columns, int reverse)
range_attempts = 10 ** 6
store_data_list = list()
for i from 0 <= i < range_attempts:
store_data_list.append(dict(
name = 'test_{}'.format(i),
test_data = i,
test_data2 = str(i),
test_data3 = range(10),
))
# Insert time
def multikeysort(items, columns, reverse=False):
items = list(items)
columns = list(columns)
columns.reverse()
for column in columns:
# pylint: disable=cell-var-from-loop
is_reverse = column.startswith('-')
if is_reverse:
column = column[1:]
items.sort(key=lambda row: row[column], reverse=is_reverse)
if reverse:
items.reverse()
return items
cdef test_list():
t_start = time.time()
data_list = list()
for i in store_data_list:
data_list.append(i)
data_list = multikeysort(data_list, ('name', '-test_data'), True)
for i in data_list:
i
del data_list
return time.time() - t_start
cdef test_vector():
t_start = time.time()
data_list = _test_vector(store_data_list, ['name', '-test_data'], 1)
for i in data_list:
i
return time.time() - t_start
# Get statistic
times = dict(list=[], vector=[])
attempts = 10
gc.disable()
for i from 0 <= i < attempts:
times['list'].append(test_list())
times['vector'].append(test_vector())
gc.collect()
print('''
Attempt: {}
List time: {}
Vector time: {}
'''.format(i, times['list'][-1], times['vector'][-1]))
del store_data_list
avg_list = sum(times['list']) / attempts
avg_vector = sum(times['vector']) / attempts
print('''
Statistics:
attempts: {}
list avg time: {}
vector avg time: {}
'''.format(attempts, avg_list, avg_vector))
Python 3.6
Statistics:
attempts: 10
list avg time: 0.2640914678573608
vector avg time: 2.5774293661117555
Есть идеи, что можно было бы улучшить в копараторе, чтобы это работало быстрее?
iCpu
Почему всего лишь -O1? Хотя бы уж -Ofast -ftree-vectorize -msse2
Код для вектора тоже было бы вежливо предоставить. А так мы сравниваем бульдога с носорогом.
onegreyonewhite Автор
У Python нет vector. Это биндинг из Cython, который берет из
класс
vector
iCpu
Нет, не описал. «Мы используем -O1» — это не аргумент.
onegreyonewhite Автор
Наверно мало в тексте было написано:
Были проблемы при установке на машинах со слегка отличной архитектурой, поэтому поставили флаг
-O1
. В любом случае, наличие большей оптимизации производительности не принесло или добавило её незначительно (меньше 0.01с), поэтому мы используем для сборки такой уровень оптимизации. Как я уже написал в тексте, была цель сделать максимально приближено к условиям реальной эксплуатации.Это вот вопрос уже интереснее, но не думаю, что профит был бы намного больше. Хотя думаю стоит проверить и такой вариант, для чистоты эксперимента. Впрочем, такой вариант использовать станет очень неудобно, поэтому будет не соблюдено главное требование — удобство при разработке. Т.к. этот код работать будет в библиотеке, то наверняка его захочется потом перегрузить и т.п. В случае с python/cython это решается просто, а с чистым C++ будет много лишней возни, как, например, наследование и т.п. Можно конечно только одну функцию вставки/обработки/сортировки использовать, но не настолько большой профит будет (да и не факт, что будет).
Sazonov
Если при включении оптимизации выше чем O1 в релизной сборке у вас начинаются проблемы, то это прямой показатель того, что что-то где-то криво запрограммировано.
onegreyonewhite Автор
До этого было -O3. Решили плавно наращивать и смотреть по отзывам. Разницы в целом не было, кроме -O0.
iCpu
Зависит от квалификации программиста и сценариев использования. Тут у недавнего поста уравнение Френеля для миллиона нормалей считается 3-5 секунд. habr.com/company/otus/blog/433588
Ну, вот, смотрите, берём самый тупой пример, переписанный с вашего. Я использую старые версии компилятора, в которых нет any, поэтому данные будут храниться либо в map<string,string>, либо в структуре.
Pappageno
Ответ несостоятелен, автор имеет ввиду march, который никого отношения к O не имеет.
Это не исходник, а неведомая портянка с хрен пойми зачем туда впихнутым Qt. При этом ладно бы core, но там gui.
Вменяемость цифр крайне сомнительна.
Тезис несостоятелен, MSVC является самым слабым компилятором — это первое. Второе, определяющим тут является качество имплементации stdlib.
Как это соотносится с:
Опять же, запустить портянку без заморочек не представляется возможным. Полезность её крайне сомнительна.
onegreyonewhite Автор
Так я и не говорил, что такие числа по времени будут у всех на всех процессорах. Я не совсем понял какие выводы следовало бы сделать?
Но я все равно хочу попробовать вынести весь C++ код в .h и посмотреть как это повлияет.
Pappageno
Это небезопасно.
0xd34df00d
Чем?
Pappageno
Ну во-первых. Тут полностью неадекватная модерация — авторы не пропускают критику, а модераторов либо нет, либо они мотивированы поступать аналогично. Поэтому, в данном случае, этой некий нейтральный(с уклоном в сторону автора) ответ призванный пройти модерацию.
Во-вторых, небезопасно это потому, что fast — это fast-math, который разрешает компилятору изменения порядка вычислений для плавучки, что небезопасно т.к. изменения порядка изменяет результаты вычислений.
0xd34df00d
Я упустил, что
-Ofast
включает-ffast-math
. Почему-то думал, что это-O3
плюс мелочёвка. Да, вы правы.0xd34df00d
Зачем
-msse2
, если можно-march=native
?zolkko
У вас
vector<Py_Object *>
создаётся черезnew
, а освобождается черезfree()
.И соответственно
PyDict_new
создаётся, ноPy_DECREF
нет. По этому pyo3 рулит, но это не точно =]onegreyonewhite Автор
Спасибо за заметку. В понедельник попробую это проверить в сочетании с тем, что предложил iCpu по поводу полного переноса кода с вектором в C++.
Pappageno
А ведь я не поленился. Самое интересное тут то, что питон я никогда в глаза не видел. К тому же мне непонятно — что там на С++ собрались люди писать, если они не то что С++ не знают — они даже какие-то базовые основы программирования не особо знают.
Если кратко. Я взял этот код, благо википедия помогла его запустить. Я посмотрел на два сгенерированных листинга и увидел PyDict_SetItemString и PyDict_SetItem. Далее, я загуглил PyDict_SetItemString и оказалось, что эта функция создаёт пистон-объект из си-строки. Вторая же функция принимает пистон-строку, да мало того, что пистон-строку — он ещё «кэширует» все литералы.
И что же я сделал? Сделал невозможное — поменял одно на другое. Генератор создал пистон-строку(что очевидно, т.к. в коде используется пистон-строка) и закэшировал(по принципу си) её. Всё аналогично генерации из питона.
И о чудо — test_vector стала быстрее. Т.к. у вас никаких тестов нет — проверить валидность я не смог. В любом случае несостоятельность статьи нотариально доказана.
Теперь по поводу общей глупости в статье. Ну во-первых, что измеряют эти тесты? Они измеряют вот это:
И это очевидно. Какое отношение бенчмарк, где создание dict занимает 99% времени измеряет оверхед на list? Никаким. И во-вторых — тут никак не сослаться на ошибку, ведь проверяется это очень просто:
И о чудо — код с вектором в 3раза быстрее.
О таких «мелочах» как:
Я даже говорить не буду.
iCpu
Не то, чтобы я защищаю автора, — он сам за себя ответит, но статья как чисто прикладная задача очень интересна. Если нужно создавать объекты — их нужно создавать (привет, К.О.), а разные сценарии работы требуют разные способы инициализации данных. К примеру, правильное использование Cython требует наличие головы и рук, которые должны расти хотя бы из жопы, а не в неё. То же с тестами.
Так что с некоторыми аргументами автора я полностью согласен: если на питоне он может это написать относительно качественно и быстро — не надо давать ему гранату, пусть и дальше пишет на питоне.
Pappageno
Чем?
Что из этого следует и зачем это было написано? Кто-то говорил о какой-то инициализации данных? Нет.
И? К чему это? Причём тут Cython?
Никаких аргументов нет и небыло. Статья целиком и полностью несостоятельна — сравнение некорректно, выводы некорректны.
onegreyonewhite Автор
Скорее выводы из этого комментария несостоятельны, потому как я в статье очень конкретно обозначил, что цель написать не просто супероптимизировано, но так чтобы это укладывалось в рамках задачи: массив словарей.
Если просто поотключать всё из кода, то он безусловно будет работать около 0мс, но зачем такой код нужен?
Цель же статьи не сравнить C++ и Python, а сравнить возможность и необходимость решения задачи, используя те или иные возможности C++ расширения.
Pappageno
Пациент пошел ставить минусы всем моим комментам. Это настолько глупо.
Основания для этих нелепых попыток?
К чему написан этот нелепый набор слов? Я где-то говорил о каких-то оптимизациях? Нет.
К чему написан этот нелепый набор слов? Я где-то говорил, что нужно что-то отключать? Нет. Я сообщил о несостоятельности бенчмарков и сравнения, причины были обозначены — есть что возразить — возражайте под оригинальным комментом, цитируя то, на что возражаете.
К чему написан очередной нелепый набор слов? Выше мною была доказана несостоятельность как ваша, так и ваших попыток что-то сравнивать.
onegreyonewhite Автор
Кроме поверхностных выводов, вы статью читали (не только сам скрипт)? Вопрос стоял вполне себе конкретно: нужно было обработать один большой массив словарей (не map'ов, не структур, а конкретно словарей типа dict из Python), да и сделать это так, чтобы потом не пришлось танцевать с бубном, чтобы использовать этот массив в Python-коде. Цели переписать всё на C++ не было.
А как работать с данными, если их нет? Даже если бы данные где-либо инициализировались до этого, то после потребовалось бы их всё равно туда складывать, поэтому задача приближена не к синтетическим тестам, а к реальной эксплуатации, то как оно реально будет использоваться.
Более того, основной посыл статьи чётко обозначен:
Даже если мы всю реализацию переместим в C/C++ — это ничего не даст, потому что по прежнему мы будем работать в Python и оперировать его объектами. Для C/C++ есть своё место здесь — вести расчёты или полностью выносить критический функционал, однако это может стоить ещё дороже, ведь по факту, придётся делать то же, что уже реализовано целым сообществом разработчиков Python и не факт, что мы справимся лучше.
Pappageno
Я слушаю людей, просто я не понимаю на что надо отвечать. Вы выкатываете мне какие-то нелепые тезисы, обвиняете меня в том, чего я не делаю. Оправдываетесь тем, что к делу отношения не имеет.
Вы опять пытаетесь выдать голоса в своей голове за мои тезисы. Про «не словари» говорил вам не я, про оптимизации говорил вам не я.
Моя претензия по поводу словарей состояла в том. Давайте я вам объясню попроще. Мы хотим сравнивать температуру чая и кофе. Как вы это делаете? Вы взяли два стакана с водой(при этом, как оказалось, разной температуры), капнули в один каплю чая, во второй каплю кофе и получили какие-то результаты. И вы пошли дальше, вы взяли не по одному, а по 4 стакана, слили их в ведро и произвели описанные выше манипуляции.
Очевидно, что человек обладающей банальной логикой сможет осознать проблему происходящего, вернее всю нелепость происходящего. Тут не нужно знать питон, С++, нужно иметь элементарный кругозор доступный любому программисту, особенно тому, который пытается что-то измерять, что-то оценивать. Ничего более я не говорил.
Т.е. вопрос звучит так. Если основная работа у вас в словарях, то зачем и для чего вы начали сравнивать вектор/питон-список? Какой в этой смысл?
Вы опять всё перепутали. Это было в другом комменте от другого человека. По поводу моих претензий — они заключались в том, что декларируя сравнения С++-вектора/питон-списка вы не сравнивали вектор/список, а сравнивали хрен пойми что.
Абсолютно нелепое заявление. В контексте сравнения вектора/списка никого не должно это интересовать. Пример со стаканами я вам привёл. К тому же, эта инициализация у вас была не идентична и даже тут вы засыпались.
И? Это выводы основанные на несостоятельных измерениях и интерпретациях. Да и сама постановка задачи нелепа. Давайте за секунду пройдём по вектору указателей 1кк раз.
Ладно, я могу предположить, что вы не знали о том, что эта задача исполняется на порядки быстрее, а значит время отработки логики вектора в задаче единицы, доли процента. Очевидно, что если мы ускорим список в 10раз — мы ускорим программу на 10%, но это не значит, что список равен вектору и наоборот.
Тут куда не глянь везде треш. Т.е. просто непонятно как вы пришли к подобным выводам, причём тут вектор, причём тут это сравнение. Зачем говорить о векторе, а потом съезжать «нам неважно что там с вектором». Слишком сложно для меня.
Вы уже всю реализацию переместили в С/С++. В этом суть cython. Далее уже сравнивается нативные для С/С++ решения, либо другие решения на С/С++ созданные для питона.
Кстати, в понятие «всю» не входит никакой питон. Я могу предположить, что в cython можно реализовать какие-то модули для обычного питона и через это интегрировать их с уже существующем кодом на питоне.
Где здесь? В мире питона? Ну дак вы говорили не об этом, вы утверждали, что «использование С++ ничего не даёт»(зачем вам тогда cython, если оно ничего не даёт? Или вы про ручное переписывание? Ну дак зачем в cython этот функционал? Его авторы идиоты и делают ненужно?). Теперь вы утверждаете обратное.
Это как в случае выше? Очень весомый аргумент.
Это очень глупый тезис. Разработчики питона реализовывали логику языка питон, его примитивов и прочего. Очевидно, что для решения любой дачи не нужны эти примитивы и вы сами это подтвердили. Вы можете использовать питон-лист, а можете С++-вектор.
Мне даже не хочется обсуждать эту примитивную подмену понятий. Вы пишете код, который решает конкретную задачу и это задача не та, которую решают авторы питона. Эти задачи могут где-то пересекаться, но очень редко.
К тому же, у «разработчики С++» так же не глупые и могут написать нормальный список, нормальную мапу и прочее. Только тут нет тех ограничений, которые есть в пистоне(как и любом скриптовом языке).
Я вообще первый раз вижу питон, но посмотрев на описание pylong я увидел, что там каике-то 256битные числа(или что-то типа того). Я посмотрел на строки, которые в utf-8. Очевидно, что подобные решения удобны для некоего общего случая использования, но они чего-то стоят, они очень много стоят.
Нужны ли вам в ключах utf8? Сомневаюсь. Нужны ли вам числа шире 64бит? Сомневаюсь. И абсолютно неважно, кто является разработчиков питона — написать быстрее utf8-строки, либо 256битные числа — невозможно. Под быстрыми имеется ввиду относительно базовых.
В этом смысл С/С++-решений. Вы не платит за то, что вам ненужно. Вам нужны 256битные числа? Пожалуйста, какие угодно. Не нужны? Берите базовые и быстрее. Нужны utf8строки? Берите. Не нужны — берите быстрее базовые.
onegreyonewhite Автор
Вот это интересно. Надо будет в пн попробовать добавить в скрипт и посмотреть что выйдет.
iroln
Если C++ не знаете, идёте в документацию по Cython и читаете:
onegreyonewhite Автор
Намёк на то, что можно было вместо free() использовать del data_list? Есть ли от этого какая-то практическая польза?
iroln
Нельзя использовать free для удаления объектов, созданных через new! free нужно использовать только для освобождения памяти, выделенной через malloc. Это же азы, ну как так то, ребята?
И вообще, я не вижу никакой практической пользы в создании объекта вектора динамически. Зачем вы вообще это делаете?
onegreyonewhite Автор
Эксперимент. В рамках Django это нормальное явление, поэтому так и сделали.
Да, это мой косяк в силу недостатка знания. В пн попробую реализовать через malloc и обновлю статью.
iroln
Зачем malloc?! Зачем пытаться создавать вектор динамически? std::vector сам управляет памятью под свои элементы, это по сути динамический массив, вам не надо заморачиваться вообще с управлением памятью для этого контейнера и создавать его объект в куче. Просто создаёте объект вектора на стеке и резервируете необходимое количество памяти под количество элементов, которые вам надо хранить, с помощью vector.reserve и всё.