Как стать автором
Обновить
-17
@twinklederead⁠-⁠only

Пользователь

Отправить сообщение
Я тоже ошибся в своей попытке локализовать проблемы. Мне почему-то показалось, что эти вектора статической длинны, но судя по всему это что-то аналогичное sso.

Как только у меня появится время займусь оптимизацией С++-варианта.
Хм. На каком входе не читает?

Сравнение
[
{"company":"Рога и копыта", "debt": 800, "phones": [123, 234, 456]},
{"company":"Первая коллекторская", "debt": 1200, "phones": [2128506, 456, 789, 123, 412, 12412]},
{"company":"Святой престол", "debt": "666", "phones": 666},
{"company": "Казачий спас", "debt": 1500, "phones": [234567, "345678"], "phone": 666},
{"company": {"name": "Шестерочка"}, "debt": 2550, "phones": 788, "phone": 789},
]


2.json:
PROCESSED: 5 objects in 169.14µs, 0 errors found
-------------------------------
#0: debt: 4550
companies: {"Первая коллекторская", "Шестерочка", "Рога и копыта"}
phones: {"234", "12412", "123", "2128506", "788", "412", "789", "456"}
-------------------------------
#1: debt: 2166
companies: {"Святой престол", "Казачий спас"}
phones: {"234567", "345678", "666"}


Запустил и уже подумал, что ошибся. Но нет — не ошибся. Оно попросту выдаёт каждый раз разный результат.

2.json:
PROCESSED: 5 objects in 647.78µs, 0 errors found
-------------------------------
#0: debt: 2166
companies: {"Казачий спас", "Святой престол"}
phones: {"345678", "666", "234567"}
-------------------------------
#1: debt: 3750
companies: {"Шестерочка", "Первая коллекторская"}
phones: {"789", "412", "788", "123", "12412", "2128506", "456"}
-------------------------------
#2: debt: 800
companies: {"Рога и копыта"}
phones: {"123", "234", "456"}



/usr/bin/time там для измерения потребления памяти. Остальное надо было убрать из вывода.

Дело не в выводе.

$ time taskset -c 0 ./target/release/habr -f 1.json
1.json:
PROCESSED: 1000000 objects in 825.626717ms, 0 errors found
-------------------------------
#0: debt: 910000000
companies: {"Рога и копыта", "Первая коллекторская", "Шестерочка"}
phones: {"789", "788", "123", "234", "2128506", "456"}
-------------------------------
#1: debt: 433200000
companies: {"Казачий спас", "Святой престол"}
phones: {"345678", "666", "234567"}

real 0m0,828s
user 0m0,790s
sys 0m0,038s


А в этом. Какой смысл сравнивать однопоточную и многопоточную версию? Сравнивайте однопоточные. Хотите сравнить многопоточные? Сравнивайте их.

Да и сравнение идёт крайне наивно, ведь нужно учитывать ещё и cputime. В условиях реального применения всегда будут желающие потратить cputime.
У меня два замечания. Оно работает неправильно(как минимум оно не читает более чем 4 телефона). Зачем вы сравниваете:
User time (seconds): 1.15
User time (seconds): 0.48

Какой в этом смысл?
Так JSON и есть бинарный формат — все разделители байты, остальное в ut8, единственная проблема — нет отдельных делимиторов верхнего уровня, и нет блочности, поэтому распараллелить расчет можно только после первичного поточного парсинга.

Он не бинарный формат в том смысле в котором понимают бинарным форматы. Формально да, все форматы являются бинарными.

Основных отличий много. В бинарном формат строгий/однозначный, а значит в данных ненужно хранить структуру. У вас же оно хранится. Бинарный формат компактней. Ненужно ничего никуда преобразовывать(строки в числа, числа в строки и прочее). Подобные форматы заранее спроектированы таким образом, что-бы хранить данные в наиболее удобной для обработки вида. Про блочность вы уже сказали. Продолжать можно долго.

СУБД и рядом не стояли по производительности на сопоставимом железе, поэтому для бигдаты и не используют реляционку, банально не тянет.

Нужно сравнивать. К тому же БД не обязательно должна быть реляционной. Аналогично, сомневаюсь, что кто-то в бигдате использует json. Для хранения отдельных документов? Возможно. Но то отдельные документы. К тому по эти документам строятся внешние индексы/иерархия.
Я проверял на 4х потоках. На том и том параметре. Разницы между параметрами у меня минимальна.
Я тут многопоточку написал, на 4-х ядрах она точно разделает сишку,

Нет, но близко. ~0.7 против ~0.55. Нужно больше потоков.
То есть, в сухом остатке, можно считать, что мы догнали C++?

Нет, почти в 2раза медленнее.

Материал тянет на статью, может кто напишет? :)

Мой код откровенно позорный. Так же я не заниматься оптимизациями. Если вам нужно быстро читать/писать/считать — вы явно выбрали не тот формат хранения и алгоритм. Используйте базу данных для таких задач.

Я примерно предполагаю как ускорить С++-версию ещё раза в 2. Но если использовать бинарный, строгий формат данных — можно получить результат в разы быстрее текущих даже для самой базовой, не оптимизированной версии. Мы просто тратим силы впустую.

real 0m1,242s — luajit
real 0m1,611s — rust(старый)
real 0m0,963s — rust(новый)
У вас:
1.6 — luajit
1.3 — rust(новый)

1.288 — коэффициент между нами.
Если поделить ваши результаты на коэффициент, то мы получим 1sec(для rust новый). Что соотносится с моими измерениями.

Версия С++ у вас должна выполнятся примерно 0.71sec. Rust(старый) должна выполняться примерно 2sec.

Я их на винде не смог собрать

Я адаптирую код под gcc8.
Версия без mmap
$ time ./target/release/habr -f 1.json
1.json:
PROCESSED: 1000000 objects in 961.843328ms, 0 errors found
-------------------------------
#0: debt: 910000000
companies: {"Рога и копыта", "Первая коллекторская", "Шестерочка"}
phones: {"789", "788", "123", "234", "2128506", "456"}
-------------------------------
#1: debt: 433200000
companies: {"Казачий спас", "Святой престол"}
phones: {"345678", "666", "234567"}

real 0m0,963s
user 0m0,958s
sys 0m0,005s



Версия с mmap
$ time ./target/release/habr -f 1.json
1.json:
PROCESSED: 1000000 objects in 998.797718ms, 0 errors found
-------------------------------
#0: debt: 910000000
companies: {"Рога и копыта", "Первая коллекторская", "Шестерочка"}
phones: {"789", "788", "123", "234", "2128506", "456"}
-------------------------------
#1: debt: 433200000
companies: {"Казачий спас", "Святой престол"}
phones: {"345678", "666", "234567"}

real 0m1,000s
user 0m0,994s
sys 0m0,006s



У меня она работает немного медленнее.

mmap(NULL, 99000002, PROT_READ, MAP_SHARED, 3, 0) = 0x7fd176971000
Попробуйте изменить флажки на «как у меня». Здесь mmap нужен не столько для скорости, а сколько для уменьшения потребления памяти. PROT_READ + MAP_POPULATE он сразу мапит пейджкеш в юзерспейс. Без каких-либо аллокация и копирования. В данном случае файл не используется весь, а читается последовательно. Эффект с zero-copy и отсутствия раздувания памяти будет заметен мало.

$ time luajit main.lua
done 1.241739 sec.
[{"debt":910000000,"companies":{"Шестерочка":true,"Рога и копыта":true,"Первая коллекторская":true},"phones":{"788":true,"456":true,"789":true,"2128506":true,"123":true,"234":true}},{"debt":433200000,"companies":{"Святой престол":true,"Казачий спас":true},"phones":{"234567":true,"666":true,"345678":true}}]

real 0m1,242s
user 0m1,173s
sys 0m0,069s


$ time ./target/release/habr -f 1.json
1.json:
PROCESSED: 1000000 objects in 1.609574904s, 0 errors found
-------------------------------
#0: debt: 910000000
companies: {"Первая коллекторская", "Шестерочка", "Рога и копыта"}
phones: {"788", "123", "456", "234", "2128506", "789"}
-------------------------------
#1: debt: 433200000
companies: {"Святой престол", "Казачий спас"}
phones: {"234567", "345678", "666"}

real 0m1,611s
user 0m1,599s
sys 0m0,010s


Топ 15 вызовов rust-версии
13.48% habr habr [.] habr::main
8.54% habr libc-2.29.so [.] _int_free
5.91% habr habr [.] serde_json::value::de::<impl serde::de::Deserialize<'de> for serde_json::value::Value>::deserialize
5.13% habr libc-2.29.so [.] malloc
4.89% habr libc-2.29.so [.] realloc
3.79% habr libc-2.29.so [.] _int_malloc
3.70% habr habr [.] <serde_json::read::SliceRead<'a> as serde_json::read::Read<'a>>::parse_str
3.28% habr habr [.] <std::collections::hash::map::DefaultHasher as core::hash::Hasher>::write
3.05% habr libc-2.29.so [.] _int_realloc
2.99% habr habr [.] <serde_json::value::de::<impl serde::de::Deserialize<'de> for serde_json::value::Value>::deserialize::ValueVisitor as serde::de::Visitor<'de>>::visit_map
2.97% habr habr [.] std::collections::hash::table::make_hash
2.92% habr habr [.] core::str::run_utf8_validation
2.86% habr libc-2.29.so [.] __memmove_avx_unaligned_erms
2.80% habr libc-2.29.so [.] cfree@GLIBC_2.2.5
2.32% habr habr [.] <alloc::collections::btree::map::BTreeMap<K, V>>::insert
2.21% habr libc-2.29.so [.] __memcmp_avx2_movbe



rust по библиотекам
65,31% habr
34,07% libc-2.29.so
0,59% [unknown]
0,03% ld-2.29.so



Процент cjson во времени выполнения lua
22,67% cjson.so


Ну вобще это не совсем честно делать memory map.

Он мало что даёт в этом случае.

Тогда и в когде на раст надо его использовать.

Предложите автору изменения. Я не знаю как это сделать.
Там всё header-only. github.com/Tencent/rapidjson — нужно куда-то клонировать и добавить путь до rapidjson/include

git clone https://github.com/Tencent/rapidjson.git
g++ main.cpp -std=c++2a -Irapidjson/include -Ofast -march=native -fwhole-program
$ time ./a.out 1.json
-------------------------------
#0: debt: 910000000.000000
companies: {Шестерочка, Рога и копыта, Первая коллекторская}
phones: {234, 789, 123, 456, 788, 2128506}
-------------------------------
#1: debt: 433200000.000000
companies: {Казачий спас, Святой престол}
phones: {345678, 666, 234567}

real 0m0,545s
user 0m0,498s
sys 0m0,047s


В любом дистрибутиве boost уже должен стоять. Если нет:

Вот версия без буста
#include <rapidjson/document.h>
#include <filesystem>
#include <sys/mman.h>
#include <fcntl.h>
#include <unordered_map>
#include <charconv>
#include <string>
#include <unordered_set>
#include <list>
#include <array>
#include <vector>

struct rec_t {
  std::string company;
  std::vector<std::string> phones;
  double debt;
};

struct debtor_t {
  static inline std::unordered_map<std::string, debtor_t &> by_phone_index;
  static inline std::list<debtor_t> all;
  std::unordered_set<std::string> companies;
  std::unordered_set<std::string> phones;
  double debt;
};

void process_object(rec_t & rec) {
  
  auto & index = debtor_t::by_phone_index;
  auto op = [&](debtor_t & d) {
    d.companies.insert(std::move(rec.company));
    for(const auto & phone: rec.phones) {
      d.phones.insert(phone);
//       if(!index.contains(phone))
      if(index.find(phone) == end(debtor_t::by_phone_index))
        index.emplace(phone, d);
    }
    d.debt += rec.debt;
  };
  
  auto di = end(index);
  for(const auto & phone: rec.phones) {
    di = index.find(phone);
    if(di != end(index)) break;    
  }
  op((di != end(index)) ? di->second : debtor_t::all.emplace_back());
}



rec_t & extract_data(rapidjson::Document & doc) {
  static rec_t rec;
  rec.phones.clear();
  rec.company.clear();
  rec.debt = 0;
  
  for(auto & x: doc.GetObject()) {
    std::string_view key{x.name.GetString(), x.name.GetStringLength()};
    auto & value = x.value;
    
    auto val2str = [](auto & value) -> std::string {
      std::array<char, 32> mem;  
      
      switch(value.GetType()) {
        case rapidjson::kStringType: return {value.GetString(), value.GetStringLength()};
        case rapidjson::kNumberType: {
          auto [endp, _] = std::to_chars(begin(mem), end(mem), value.GetUint64());
          return {begin(mem), endp};
        }
        default: return {};
      }
    };
    
    auto val2num = [](auto & value) -> double {
      switch(value.GetType()) {
        case rapidjson::kStringType: return strtod(value.GetString(), nullptr);
        case rapidjson::kNumberType: return value.GetDouble();
        default: return {};
      }
    };
    
    auto push_phone = [&](auto & value) {
      if(auto phone = val2str(value);!phone.empty())
        rec.phones.emplace_back(phone);
    };
    
    if(key == "company") {
      if(value.IsString()) {
        rec.company = {value.GetString(), value.GetStringLength()};
      } else if(value.IsObject()) {
        auto name = value.GetObject().FindMember("name");
        if(name != value.GetObject().MemberEnd()) rec.company = val2str(name->value);
      }
    } else if(key == "phone") {
      push_phone(value);
    } else if(key == "phones") {
      if(value.IsArray()) for(auto & x: value.GetArray()) push_phone(x);
      push_phone(value);
    } else if(key == "debt") rec.debt = val2num(value);
  }
  
  return rec;  
}

int main(int argc, char * argv[]) {
  if(argc < 2) return -1;
  std::filesystem::path path = argv[1];
  
  auto file_size = std::filesystem::file_size(path);
  auto file_data = (const char *)mmap(nullptr, file_size + 4096, PROT_READ, MAP_POPULATE|MAP_PRIVATE|MAP_FILE, open(path.c_str(), O_RDONLY), 0);
  std::string_view file{file_data, file_size};
  
  auto it = file;
  
  auto next = [&] {
    auto p = it.data();
    const char * begin = nullptr;
    size_t braces = 0;
    while(true) {
      switch(p = strpbrk(p, "{}"); p ? *p : 0) {
        case '{': if(!braces++) begin = p; ++p; break;
        case '}': ++p; if(!--braces) {
          it = it.substr(size_t(p - it.begin()));
          return std::string_view{begin, size_t(p - begin)};
        } break;
        default: it = it.substr(it.size()); return std::string_view{};
      }
    };
  };
  
  rapidjson::Document doc;
  
  
  while(true) {
    auto str = next();
    if(str.empty()) break;
    doc.Parse(data(str), size(str));
    process_object(extract_data(doc));
  }
  
  for(size_t it = 0; auto & d: debtor_t::all) {
    printf("-------------------------------\n");
    printf("#%lu: debt: %f\n", it++, d.debt);
    std::string companies, phones;
    for(auto & x: d.companies) companies += (x + ", ");
    for(auto & x: d.phones) phones += (x + ", ");
    if(companies.ends_with(", ")) companies.resize(companies.size() - 2);
    if(phones.ends_with(", ")) phones.resize(phones.size() - 2);
    printf("companies: {%s}\nphones: {%s}\n", companies.c_str(), phones.c_str());
  }
}



У меня эта версия(0.55 секунды) где-то в 3раза быстрее раста(1.6 секунды).

Написал С++-версию. Можете добавить к сравнению.

Код
#include <rapidjson/document.h>
#include <filesystem>
#include <sys/mman.h>
#include <fcntl.h>
#include <unordered_map>
#include <charconv>
#include <string>
#include <unordered_set>
#include <list>
#include <array>
#include <vector>
#include <boost/algorithm/string/join.hpp>


struct rec_t {
  std::string company;
  std::vector<std::string> phones;
  double debt;
};

struct debtor_t {
  static inline std::unordered_map<std::string, debtor_t &> by_phone_index;
  static inline std::list<debtor_t> all;
  std::unordered_set<std::string> companies;
  std::unordered_set<std::string> phones;
  double debt;
};

void process_object(rec_t & rec) {
  
  auto & index = debtor_t::by_phone_index;
  auto op = [&](debtor_t & d) {
    d.companies.insert(std::move(rec.company));
    for(const auto & phone: rec.phones) {
      d.phones.insert(phone);
//       if(!index.contains(phone))
      if(index.find(phone) == end(debtor_t::by_phone_index))
        index.emplace(phone, d);
    }
    d.debt += rec.debt;
  };
  
  auto di = end(index);
  for(const auto & phone: rec.phones) {
    di = index.find(phone);
    if(di != end(index)) break;    
  }
  op((di != end(index)) ? di->second : debtor_t::all.emplace_back());
}



rec_t & extract_data(rapidjson::Document & doc) {
  static rec_t rec;
  rec.phones.clear();
  rec.company.clear();
  rec.debt = 0;
  
  for(auto & x: doc.GetObject()) {
    std::string_view key{x.name.GetString(), x.name.GetStringLength()};
    auto & value = x.value;
    
    auto val2str = [](auto & value) -> std::string {
      std::array<char, 32> mem;  
      
      switch(value.GetType()) {
        case rapidjson::kStringType: return {value.GetString(), value.GetStringLength()};
        case rapidjson::kNumberType: {
          auto [endp, _] = std::to_chars(begin(mem), end(mem), value.GetUint64());
          return {begin(mem), endp};
        }
        default: return {};
      }
    };
    
    auto val2num = [](auto & value) -> double {
      switch(value.GetType()) {
        case rapidjson::kStringType: return strtod(value.GetString(), nullptr);
        case rapidjson::kNumberType: return value.GetDouble();
        default: return {};
      }
    };
    
    auto push_phone = [&](auto & value) {
      if(auto phone = val2str(value);!phone.empty())
        rec.phones.emplace_back(phone);
    };
    
    if(key == "company") {
      if(value.IsString()) {
        rec.company = {value.GetString(), value.GetStringLength()};
      } else if(value.IsObject()) {
        auto name = value.GetObject().FindMember("name");
        if(name != value.GetObject().MemberEnd()) rec.company = val2str(name->value);
      }
    } else if(key == "phone") {
      push_phone(value);
    } else if(key == "phones") {
      if(value.IsArray()) for(auto & x: value.GetArray()) push_phone(x);
      push_phone(value);
    } else if(key == "debt") rec.debt = val2num(value);
  }
  
  return rec;  
}

int main(int argc, char * argv[]) {
  if(argc < 2) return -1;
  std::filesystem::path path = argv[1];
  
  auto file_size = std::filesystem::file_size(path);
  auto file_data = (const char *)mmap(nullptr, file_size + 4096, PROT_READ, MAP_POPULATE|MAP_PRIVATE|MAP_FILE, open(path.c_str(), O_RDONLY), 0);
  std::string_view file{file_data, file_size};
  
  auto it = file;
  
  auto next = [&] {
    auto p = it.data();
    const char * begin = nullptr;
    size_t braces = 0;
    while(true) {
      switch(p = strpbrk(p, "{}"); p ? *p : 0) {
        case '{': if(!braces++) begin = p; ++p; break;
        case '}': ++p; if(!--braces) {
          it = it.substr(size_t(p - it.begin()));
          return std::string_view{begin, size_t(p - begin)};
        } break;
        default: it = it.substr(it.size()); return std::string_view{};
      }
    };
  };
  
  rapidjson::Document doc;
  
  
  while(true) {
    auto str = next();
    if(str.empty()) break;
    doc.Parse(data(str), size(str));
    process_object(extract_data(doc));
  }
  
  for(size_t it = 0; auto & d: debtor_t::all) {
    printf("-------------------------------\n");
    printf("#%lu: debt: %f\n", it++, d.debt);
    using boost::algorithm::join;
    printf("companies: {%s}\nphones: {%s}\n", join(d.companies, ",").c_str(), join(d.phones, ",").c_str());
  }
}

Информация

В рейтинге
Не участвует
Зарегистрирован
Активность