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

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

Отправить сообщение
Вам кажется.

Нет.
В качестве контраргумента достаточно посмотреть на засилие электрона вокруг,

В качестве контр-аргумента подойдёт и rust. Но это не является контр-аргументом, т.к. и одно и второе не является исполнителем и не является хардварным.

которму до эффективности как до луны.

Даже если я приму эти попытки, то они в любом случае неудачны. Исполнителем в случае с электроном является хром. Хром является самым эффективным броузером, v8 является самым эффективным исполнителем js.

Компоновщик lld — отдельный проект в рамках llvm, как clang или lldb.


в рамках llvm
в llvm нет компоновщика.

Это явные противоречия. Да и попросту глупость, т.к. все проекты в рамках llvm зависят от llvm и являются его частью.

Rust по умолчанию пользуется lld, но можно компоновать и ld.

И что же из этого следует? В rustc появился линкер на rust? Нет.
llvm это llvm. Линкер же и в расте есть.

Откуда же в rustc линкер, если у него нет бинарных таргетов? линкера в rustc нет. Линкером там является llvm.

Есть лисп-машины

Нету. лисп-машина — это интерпретатор лиспа. Причём даже не хардварный. Об этом даже в википедии написано. Зачем люди продолжают повторять эту глупость(что лисп-машины исполняли какой-то лисп)?

Так же, упускается из виду фундаментальное обстоятельство. Никому и никогда ненужно просто исполнение. Нужно эффективное исполнение. И именно этим лисп-машины и были. Просто хардварные оптимизации характерные для lisp. Такая же история есть с java-байткодом в каких-нибудь arm.

Все эти ухищрения неэффективны. Именно поэтому они и умерли, а существовали только на начальных этапах. Как в том же arm. Исполнитель откровенно слаб, частота маленькая. Проще туда впихнуть побольше всякой логики и будет быстрее. Но чем дальше — тем хуже.

Именно поэтому и умер циск. Когда-то это казалось хорошей идеей, а сейчас воспринимается за глупость.

Никто rustc не декларирует как транслятор из rust в llvm-ir. Мало того, что это попросту глупо. Причин много. Начиная из того, что ir уже llvm, его внутренние представление. И это именно задача фронтенда генерировать промежуточное представление компилятора. Получается, что компилятор компилирует то, что потом компилирует компилятор. Какой смысл вообще разделять фронтенд и компилятор, если и то и компилятор? Заканчивая тем, что на уровне языка множество ссылок на именно генерацию программ. Те же модули существуют именно на бинарном уровне.

Даже если всё это исключить и принять эту логику, то это ничего не изменит. Генератор ir в rustc зависим от llvm. Именно llvm генерирует ir, а фронтенд использует api для генерации ir. Таким образом те части, которая в rustc написаны на rust сами генерировать ir не могут.

Единственная соломинка здесь — это mir. Но это тоже тупиковый путь. Это некая форма rust. Её ничто не умеет исполнять. Она декларируется как промежуточное представление. Это не таргет. Если это таргет, то зачем компилятору что-то делать с таргетом и куда-то его преобразовывать(пусть и внешней логикой)?

А чего вы минусуете? У вас есть какая-то контр-аргументация? Я что-то неправильно сказал?
C++ проиграет, если на Rust писать чисто.

Вопрос. Проиграл ли С++?

ни один программист C++ в здравом уме не решится и будет копировать строки.

Я не решился. Копировал строки. С++ не проиграл. Как вы это объясните?
Прямо как string_view, который все так долго ждали в плюсах.

Все его использовали ещё со времён. Где именно его ждали? В стандарте? Ну слайсы для раста уже 10лет ждут в стандарте, как всё остальное.

Поэтому и хвалит он раст, у которого компилятор не позволит такой баг допустить.

Как? Не даст взять ссылку на элемент и придётся(как автор делал) хранить индексы? Это неудобно, но ладно.

Проблема заключается в том, что я без проблем могу написать vector, который ссылки не теряет. Причём мне ненужно даже писать вектор, можно просто поменять ему аллокатор.

Это одна из фундаментальные проблем «безопасности» подобного рода. Эта безопасность защищает даже тогда, когда никакой защиты ненужно. Но продолжает вносить оверхед(как и по производительности, так и по удобству программиста).
Да, чтобы реализовать низкоуровневую работу с памятью нужно unsafe, что дальше?

Нужен не только unsafe, но и libc( в данном случае).
Да пожалуйста, смотрите

Проблема подобных ответов заключается в том, что в понятия «аналог» входит то, что является сравнимым хотя-бы по каким-то основным критериям. В данном случае основном критерия аллокатора — работать быстро. Этот аллокатор неконкурентоспособен не то что каким-то лучшим аллокатора, а даже самым банальным.

Очень рекомендую проверить свой код на такие случаи.

Допустим, вот так: godbolt.org/z/qmzVyN
Даже если устранить все различия в реализациях одного и того же кода (для этого в Rust зачастую придется обмазаться unsafe с ног до головы, нивелируя все его достоинства)

Аналогичная проблема возникла и тут. Даже наивный код в котором C++ и его stdlib очень просто победить. Не побеждается. И чем ниже уровень, тем больше проблем. Ведь rust задизайнен именно для наивногохайлевел программирования.

Да, можно говорить о том, что код на rust более безопасный. В С++ можно попробовать заменить list на vector. Но нельзя говорить о том, что эта безопасность достаётся бесплатно. Как и по части производительности, так и по части применяемых усилий. Очень много ложится на голову программиста.

Есть ещё один нюанс, о котором всё время забывают. Не бывает быстрых/медленных языков. Есть языки с большими/меньшими возможностями. Производительность/безопасность кода зависит исключительно от программиста. И именно на этом этапе проявляется различие языков. Какой-то язык одному уровню программистов даёт писать код быстрее/безопаснее, чем другим. Но для другого уровня — всё может быть наоборот.

Если мы начнём говорить про производительность, то тут прежде всего определяющим является умение программиста. А язык лишь средство выражения его умения/опыта. Язык должен дать возможность выразить программисту то, что он хочет и то как он это хочет. В этом основания проблема rust. В подобных случаях он не просто не помогает — он мешает. Причём порою мешает до невозможности.

Именно поэтому нет никакого смысла говорить о высокой(не в сравнении с питоном/swift, а объективно) производительности. Раст попросту не даст возможности программисту раскрыть свой потенциал. Именно поэтому уровень программистов будет относительно мал. Человек должен быть крайне фанатичен(в сторону rust) что-бы несмотря на все горечи и страдания пытаться писать на нём высокопроизводительный код.

компилятор rust'а может использовать менее эффективную декларацию о вызовах

Есть ещё одно обстоятельство касательно llvm. В нём вполне адекватные хайлевел-оптимизации. В ситуациях, когда дело касается числодробильного кода- он крайне слаб.

За примерами ходить ненужно. Тот же rapidjson собранный gcc в полтора раза быстрее собранного clang/llvm.
Производительность однопоточного С++ кода я привёл, чтобы была точка отсчета для связи с приведёнными результатами на других машинах.

Нет, вы именно сравнивали. Как минимум автор статьи декларировал это сравнение явно, а вы просто сравнивали. Причём сравнивали непонятно где. И почему-то 200мс сустайма у вас вопросов не вызвало как и запуск не под линуксом.

Многопоточного С++ кода тогда ещё по-моему не было.

Я уже отвечал на этот вопрос. Дело в том, что добавление многопоточности — это не оптимизация в ситуации, когда и тот и тот язык умеет в многопоточность. Тут можно долго говорить о просьбе автора, но факт остаётся фактом. Это обстоятельство нужно уточнять и сравнивать несравнимое нельзя.

Rust ориентирован на безопасность когда. Наивное решение на Rust и наивное решение на C++ (которое ещё и работает) требуют разных уровней знания языка.

Насколько я вижу в интернете, везде вся эта безопасность декларируется как «zero-cost».

Кроме того, использование вами string_view и других последних фишек языка, как-то не выглядит очень наивным.

Я уже так же отвечал на этот вопрос. Единственное место, которое я паписал «не очень наивно» — это main::next. Именно там сосредоточено почти всё использование string_view.

Какой у вас опыт разработки на C++?

Смотря как считать. Если брать именно С++, то может года 2-3 суммарно.

Подход, применяемый в rapidjson, и так отличается от того, что применяется в serde.


Например, SIMD intrinsic'и в serde пока ещё не применяются.

Я вижу этот rapidjson первый(или второй) раз в жизни, как и саму задачу парсинга json«а. Полистав код я вижу только одно, что я уже и говорил. Этой библиотеке очень и очень далеко до оптимальной.

По поводу simd. Они там мало что дают. Да и сделано это крайне наивно.

Видимо это и является причиной, почему я с трудом догнал его по производительности на своей машине даже с zero-copy.

Нет, не является. Я проверил. Они там не включены по умолчанию, а если их включить, то они только замедляют код.

Пожалуйста, объясните, каким образом это не zero-copy? x.name.GetString() возвращает указатель на буфер, если есть такая возможность. Или я не прав?

Нет, это не zero-copy. В данном случае я использую sv как обёртку над „указатель + длинна“, которую мне отдают библиотка. Использую я это для красивого сравнения строк через ==, а не через memcmp(который за меня вызовет sv).

Все строки, которые выходят из это функции — это std::string, в который копируются данные. Везде и всюду, где использует sv — он используется как удобная обёртка над указателем + длинна. Никакой zero-cost функции он не несёт.

Причины я обозначил. Я писал аналогичный rust-коду автора код. Единственное место, где я отошёл от этого правила — это main::next и чтение файла. Причина банальна — так проще.

zero-cost есть в многопоточной реализации, именно так строки отправляют через очередь на парсинг.

Это инвариант типов String и str — они содержат валидный utf-8, если не содержат — UB.

Ещё раз. Это локальные проблемы отдельной библиотеки. Реальная жизнь на строки никакого подобного инварианта не накладывает. Строки это не только utf-8.

Но есть. Платформа WSL.

Это явно не обозначенная автором платформа.

И, после общения в таком тоне, не очень хочется.

Не хочется запускать код на той платформе, которую обозначил автор?

В статье представлена достаточно наивная реализация решения.

Я предоставил такую же наивную реализацию на С++.

Производительность Rust кода теоретически не должна отличаться от C++.

Нужно сравнивать равные решения, иначе это всё не имеет смысла. Вы пытаетесь догнать наивную С++-версию. Сравнивайте с наивным rust-кодом. Вы же что делаете? Вы добавляете хаки, оптимизации и прочее, что не будет использоваться в обыденной жизни. В обыденной жизни будет использоваться базовый подход.

Далее, вы ускоряете версию в которой проблем меньше, чем у С++. Вы пытаетесь добавить zero-copy парсинг, убрать копирование, добавить более оптимальные хешмапы, cow и прочее. Но проблема никуда не делал — это сравнение глупое. Ведь всё это(и даже больше) есть в С++-коде и я там же могу этим заняться. И если вы даже наивную С++ до сих пор не догнали, то что будет в том случае? В случая, когда она будете не наивная.

Нужно всегда сравнивать что-то в равных условиях. Подобный вашему подход работает в ситуации сравнению с тем же jua. jua не может во всё это и на этом можно играть.

Я разбираюсь почему она отличается.

Ну нет же. В С++ есть zero-copy-парсинг? Нету. Там даже поточного парсинга нету. Задача «понять почему отличается» — это не пытаться найти другой подход и как-то догнать. Задача понять заключается в том, что-бы узнать. Почему в одном и том же подходе оно отличается. Почему С++ без всего этого работает быстро, причём работает быстрее даже самой оптимизированной вашей версии.

Эм, а что и многопоточная версия есть?

Да. Я специально после написания этой ветке пошел и сделал его.

У вас всё в порядке? Обычное желание выжать максимум из кода. Не надо придумывать какие-то странные мотивации.

Вы не выжимаете максимум — вы сравниваете. Сравниваете неадекватно. Сравниваете одно/много-поточные версии. Сравниваете наивные и не-наивные решения.

Выше вы писали, что хотите разобраться. Сейчас вы пишите, что вы просто пытаетесь догнать любыми методами. И в этом проблема. Все эти ухищрения, в том числи и многопоточность, можно добавить в С++. Это вам никак не поможет разобраться.

Это проблема вашего подхода. Обозначу проблему ещё раз. Нет смысла делать быстрее за счёт изменения подхода применяемого в С++. Вы этим ничего ничего не добиваетесь и причина простая. Всё это подходы можно точно так же добавить в С++ и они на него повлияют точно так же, даже больше.

Поправил баг (ну не работал я с serde никогда в zero-copy режиме), время выполнения поднялось на 50ms

Я в очередной раз спрошу. Зачем? История аналогичная потоком. Вы пытаетесь похакать, добавить jemalloc, выкинуть стандартную библиотеку, добавить «zero-copy режиме». И все эти ухищрения нужны для того, что-бы ещё даже не догнать С++-версию без всего это.

Зачем вы сравнивали однопоточную и многопоточную версию? Думали, что я не добавлю многопоточную? Зачем вы занимаетесь этим шаманством? Вы думаете, я не добавлю его в С++-код? К чему эта пыль в глаза?
И это zero-cost. Чтобы получить гарантированно валидный String из неконтролируемых внешних данных

Как строка связана с utf8? Как минимум существуют строки без юникода, а даже если с юникодом — существуют разные форматы юникода.

Это не базовая логика.

Это базовая логика. Почему именно она у вас медленнее неважно. Факт есть факт — она медленнее, со всеми вытекающими.

Это — 1) валидация utf-8, 2) моя ошибка с десериализацией Cow (значение всегда копировалось), 3) оверхед на работу с потоками.


1) уберите. 2) у меня в С++ коде всё копируется и нет проблем. 3) у меня никого оврехеда нет. Почему?

Заодно добавил кастомный аллокатор, чтобы немного отыграть проигрыш на флагах mmap.

Зачем вы добавили С++-зависимость к коду на rust? Ваши биндинги не могут во флаги?

На моей машине версия с аллокатором обгоняет ваш код на 70ms, без аллокатора на 50ms.

Нет. Я уже говорил, что у вас 180ms systime. Такого быть не должно.
Раст, как и С++, предполагает, что программист знает что делает. Если написано &str — значит программист хочет валидный utf-8, что предполагает обязательную валидацию пользовательского ввода. Догадаться, что программист хочет максимально быстрый код, Раст, естественно, не может.

Не совсем понимаю причём тут пользовательский ввод, когда читаются бинарные данные из файла. Если мне понадобиться валидация utf-8 — я вопрошу её явно. Раст ведь декларирует zero-cost-абстракции?

Так же, я говорил об ином. Для того, что-бы догнать наивный, кое-как написанный С++-код приходится что-то оптимизировать, хакать, страдать. И даже всего этого недостаточно.

serde ориентирован на десериализацию в strongly typed персистентные структуры, а не на потоковый парсинг.

У меня используется парсер в dom-режиме. В sax-режиме этот парсер куда быстрее, но до «максимально» и даже «быстрого» ему очень далеко. Не вижу смысла заниматься оптимизацией изначального тупикового подхода.

Эту задачу нетривиально распараллелить линейно. Поэтому приходится тратить время на раскидывание миллиона объектов по нескольким потокам (оверхед около 300нс на объект, но накапливается), вместо того, чтобы разбить файл на несколько частей и отдать каждую своему потоку.

Всё это не имеет отношения к проблеме. У вас базовая логика почти в 2раза медленнее. Это значит, что абсолютно неважно какой там оверхед и как сложно что-то распараллелить.

Есть базовое обстоятельство. Трупут парсинга объекта и его обработки почти в два раза ниже. В этот самый трупут всё и упирается. На сколько угодно потоков этот трупут можно множить, но он останется лишь в районе 0.5. Ведь у С++ есть столько же потоков.

Это просто фокус. Работает он следующим образом. Есть трупут на одном потоке у С++ — это 1.0. Есть раст, у него трупут на потоке 0.5. Есть некий коэффициент(какой угодно, но положительный). Из этого следует, что добавляя потоки(один, два, десять) — мы когда-нибудь дойдём до этой единице.

Если же взять одинаковое кол-во потоков, одинаковый коэффициент роста за поток, то первый случай будет всегда быстрее. И быстрее в те самые 2 раза.
merge я реализовал простым перебором. Не знаю правильно это или нет.

Только сейчас увидел:
$ time ../habr2_cpp/a.out 1.json
[...]
real 0m0.653s
user 0m0.453s
sys 0m0.188s

Судя по sys — с системой явно что-то не то.

Многопоточная С++-версия
//https://github.com/jarro2783/cxxopts.git
//https://github.com/Tencent/rapidjson.git
//https://github.com/cameron314/concurrentqueue.git
#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>
#include <algorithm>
#include <cxxopts.hpp>
#include <concurrentqueue/concurrentqueue.h>
#include <future>

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

struct debtor_t {
  std::unordered_set<std::string> companies;
  std::unordered_set<std::string> phones;
  double debt;
};


struct debtors_t {
  std::unordered_map<std::string, debtor_t &> by_phone_index;
  std::list<debtor_t> all;
};

void process_object(debtors_t & debtors, rec_t & rec) {
  
  auto & index = debtors.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(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 : debtors.all.emplace_back());
}


rec_t & extract_data(rapidjson::Document & doc, 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;  
}

void print(const std::list<debtor_t> & all) {
  for(size_t it = 0; auto & d: 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());
  }
}

auto merge(std::vector<std::list<debtor_t>> & results) {
  std::list<debtor_t> result, tmp;
  for(auto & x: results) result.splice(end(result), x);
  if(result.empty()) return result;
  for(size_t c = 1; c;) {
    tmp.splice(std::begin(tmp), result, begin(result), ++begin(result));
    auto & a = tmp.front();
    c = 0;
    re:
    for(auto & phone: a.phones) {
      for(auto it = begin(result); it != end(result); ++it) {
        if(auto & b = *it; b.phones.find(phone) != end(b.phones)) {
          a.companies.merge(b.companies);
          a.phones.merge(b.phones);
          a.debt += b.debt;          
          result.erase(it);
          ++c;
          goto re;
        }
      }      
    }
    result.splice(end(result), tmp);
  }
  assert(tmp.empty());
  return result;  
}



moodycamel::ConcurrentQueue<std::string_view> q;

struct config {
  static inline size_t bulk = 128;
  static inline size_t concurrency = std::min(4u, std::thread::hardware_concurrency());
};

auto work() {
  debtors_t debtors;
  std::vector<std::string_view> pool(config::bulk);
  rec_t rec;
  
  for(bool end_flag = false; !end_flag;) {
    auto n = q.try_dequeue_bulk(begin(pool), config::bulk);
    rapidjson::Document doc;
    for(size_t it = 0; it < n; ++it) {
      auto & str = pool[it];
      if((end_flag = str.empty())) continue;
      doc.Parse(data(str), size(str));
      process_object(debtors, extract_data(doc, rec));      
    }
    
    if(end_flag) q.enqueue({});
  }
  return debtors.all;
}


auto parse(int argc, char* argv[]) {
  cxxopts::Options options{*argv};
  options.add_options()
    ("b", "", cxxopts::value(config::bulk))
    ("c", "threads", cxxopts::value(config::concurrency))
    ("f", "file path", cxxopts::value<std::string>());
  auto pr = options.parse(argc, argv);
  if(pr.count("f") == 1) return pr["f"].as<std::string>();
  printf("%s\n", options.help().c_str());
  exit(0);
}


int main(int argc, char * argv[]) {
  std::filesystem::path path = parse(argc, argv);
  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 next = [it = file]() mutable {
    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{};
      }
    };
  };
  
  std::vector<std::future<std::list<debtor_t>>> tp(config::concurrency);
  for(auto & f: tp) f = std::async(std::launch::async, work);
  
  std::vector<std::string_view> pool;
  auto max = config::bulk * config::concurrency;
  pool.reserve(max);
  while(true) {
    auto str = next();
    if(pool.size() == max || str.empty()) {
      q.enqueue_bulk(begin(pool), pool.size());
      pool.clear();
    }
    if(str.empty()) {
      q.enqueue({});
      break;
    }
    pool.emplace_back(str);    
  }
  std::vector<std::list<debtor_t>> all;
  std::transform(begin(tp), end(tp), std::back_inserter(all), [](auto & f) {
    return f.get();
  });
  assert(all.size() == config::concurrency);
  print(merge(all));
}



Результат был понятен изначально:

//C++ 4 потока
real 0m0,137s
user 0m0,557s
sys 0m0,012s

//C++ 1 поток + taskset на 1 ядро
real 0m0,526s
user 0m0,510s
sys 0m0,016s

//далее 400 мегабайт json
//C++ 4 потока + taskset на 4 ядра
real 0m0,547s
user 0m2,057s
sys 0m0,042s

//C++ 1 поток + taskset на 1 ядро
real 0m2,074s
user 0m2,034s
sys 0m0,040s

//rust + taskset на 4 ядра
real 0m0,956s
user 0m3,696s
sys 0m0,070s



Правильный рост, никакого оверхеда, никакого изменения кода. Написано максимально наивно.
К слову, то, насколько просто можно распараллелить програму, тоже играет свою роль.

Ещё важнее то, насколько просто написать оптимальную(если мы говорим про оптимальные программы) программу. В одном случае берётся стандартная библиотека и просто пишется код «как попало» без каких-то попыток что-то оптимизировать. Единственная функция, которая там написана хоть как-то адекватно — это main::next. В другом случае требуются замена стандартных функций, множество хаков, тюнинга библиотек и прочего. Но даже этого недостаточно.

На тему распараллеливания. Тут всё куда сложнее. В подавляющем большинстве случаев попытка распараллелить задачу малоэффективна, либо попросту бессмысленна. Подобные мы видим и тут.

В данном случае распараллеливание достигается за счёт запуска отдельных инстансов задачи. Но проблема никуда не уходит. Трупут каждого потока так и остался лишь чуть большим от 0.5 от С++.

Запустить задачи в параллель везде и всюду так же просто, особенно используя готовые библиотеки для организации пайпов. Но в данном случае, как и в большинстве других, всё упирается в трупут парсинг+process_object. И от этого никуда не уйти.

Если задача ~линейно параллелится, то cputime не должно расти. Если же оно растёт — что-то делается не так.

Информация

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