Обновить
18

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

4
Подписчики
Отправить сообщение

в общем случае при сложении 2-х строк конструируется 3-я строка — я сразу пишу через append() чтобы исключить лишние операции. Понятно что rvalue скорее всего оптимально складываются, но мне уже привычнее через append() тем более что и при переключении между языками (С++/Java/Kotlin StringBuilder) удобно сохранять эти привычки.

да, действительно, иногда удобнее иметь только одну входящую структуру которую можно было бы со временем расширять.

Добавил везде группировку параметров в LaunchParameters + юнит тесты на все возможные варианты колбэков.

Сейчас уже создал кучу юнит тестов, нагрузочных тестов и постоянно их гоняю, на каждое изменение в Qt Creator нажимаю кнопку "Прогнать все юнит тесты" пока чай хожу пить или ещё чем занимаюсь. Если поймаю падение, то пойду копать&чинить. Пока не падало (под GCC 9.4.0, Clang-9, Clang-13, MSVC++ 14.32)

В библиотеке продвигается термин Протектор (обычно реализуется как weak_ptr).. Да, писатель из меня ещё тот :).. Старался донести до пользователей что их код может исполняться неизвестно когда и хорошо бы озаботиться тем чтобы он не запустился если уже некоторые используемые объекты разрушились (например подписчик уже удалился, или объект который лямбду создавал). С Протектором это скрыто под капотом что кажется удобным (кроме того что приходится ещё один параметр заполнять)

С терминами у меня тоже беда, да - у нас канал(channel) это на что можно подписаться, поток(stream) это итераторы данных от 1 писателя к 1 читателю. Попробую может на Хабре тоже самое на English написать в следующие длинные праздники.

Привет. Извини за долгий ответ — стараюсь хобби заниматься только в выходные, и для хобби использовать домашнюю почту.

Спасибо за замечание.

На самом деле чтобы заработать UB надо было бы что-нибудь разнообразное поконструировать на одном и том же buffer_

(там где (2) ничего не создаётся на buffer_ - просто связывается время жизни std::shared_ptr<SharedHolder> и указателя на пофиг что, поизучай чем занимается (8)ой конструктор: https://en.cppreference.com/w/cpp/memory/shared_ptr/shared_ptr).

У меня в коде std::launder использовался чтобы помешать компилятору оптимизировать.

Ну, ок, спасибо, второй std::launder не повредит, добавил.

Вроде в этом примере почти также как сейчас у меня: https://en.cppreference.com/w/cpp/memory/destroy_at

Привет. Померил производительность.

На моём компе в релиз сборке clang-13 на доставку 100 сообщений в среднем уходит:

test_std_thread execution_time microsec per 100 tasks: 18

test_highway_channel_block_on_wait_holder execution_time microsec per 100 tasks: 89

test_highway_channel_not_block execution_time microsec per 100 tasks: 75

test_highway_channel_direct_send execution_time microsec per 100 tasks: 5

За BaseLine можно брать test_std_thread - хайвеи доставляют помедленней, но всё ещё меньше микросекунды. Я посмотрел где можно было бы урезать фичи в угоду производительности - рука не поднялась :). Добавил возможность чтобы через каналы можно было слать сообщения подписчикам без перепланирования отправки на другой поток - так быстрее всего получается. Спасибо за инфу про AKKA actors/streams - посмотрел, записал себе в TODO.

Не думаю чтобы Заказчик это одобрил… и это не результат труда в домашних хобби-проектах (как то FastMemPool). На публику выношу только то что я в свободное от основной работы делаю дома… Из хобби нести на работу можно, а с работы в хобби уже воровство…
Вот я и пишу, что ничего страшного: никто не начал работать с деаллоцированными страницами — просто из пула выбыл один лист. Кроме этого вероятность описанной Вами драмы крайне мала:
Кто-то должен суметь успеть между compare_exchange_strong и следующей операцией sequentially consistent ordering(которая всех тормозит и выравнивает) сделать следующее:
1) Получить кусок памяти и зафиксироваться в available сделав минимум 2 atomic операции
2) Не совершая никакой полезной работы сразу пойти возвращать полученную память (добавление вызова в call stack, проверки аллокации всякой математикой)
3) Сделать ещё 2 atomic вызова получив текущие deallocated и available,
и при этом обогнать того кто ещё до пп1) стоит всё ждёт со своим sequentially consistent ordering (на deallocated -= deallocated; )

Конечно в бесконечной вселенной на бесконечном отрезке времени любую ненулевую вероятность если умножить на бесконечность — то мы получим гарантированное событие, но на этой же бесконечности ещё столько других багов — что потеря одного листа памяти из пула это самое безобидное что может произойти в этой жизни…
Получаем карту из uint16_t где каждое значение коррелирует с высотой = карта высот. Визуализировать можно по разному — основная сложность в том что градаций серого/цвета на экране всего 256 => либо идёшь окном визуализации по диапазону высот, либо всё ужимаешь в 256 делением теряя детали. Для Детектора это не имеет значения так как он работает с raw uint16_t (т.е. какая картинка в RGB\BGR ему не важно). Видим все изломы/вмятины/неровности поверхности — это и является результатом детекции. Конкурентные иностранные подобные решения работают с очень небольшой площадью и стоят очень дорого.
«Что с другими архитектурами?» — у меня была возможность на ARM померить:
image

Исходник теста тут: FastMemPool on Android
Спасибо за комментарий.
Шаблоны в работе использую достаточно редко, когда реально какой-то код типовой… пробелы есть.
Почитал про std::pmr::polymorphic_allocator, кажется штука полезная если есть проблемы с сопоставлением типов разных контейнеров…

Можете привести пример из жизни — как (и какие) изменения в шаблоне FastMemPoolAllocator
могли бы решить решить какую-то реальную задачу?
Ничего страшного:
// тут все синхронизировались и узнают что было вычитание:
      const int  deallocated  =  leaf_array[head->leaf_id].deallocated.fetch_add(real_size, std::memory_order_acq_rel)  +  real_size;
      int  available  =  leaf_array[head->leaf_id].available.load(std::memory_order_acquire);
      if (deallocated  == (Leaf_Size_Bytes - available))
      {  // everything that was allocated is now returned, we will try, carefully, reset the Leaf
        if (leaf_array[head->leaf_id].available.compare_exchange_strong(available,  Leaf_Size_Bytes))
        {
// Это самая жёсткая синхронизация потому как "The default behavior of all atomic operations in the library provides for sequentially consistent ordering":
          leaf_array[head->leaf_id].deallocated  -=  deallocated;
        }
      }

все возможности для неадекватного поведения закрыты согласно en.cppreference.com/w/cpp/atomic/memory_order
опыт показывает что идеального освещения не бывает — для каждого вида детектора приходится колдовать с освещением… мультистерео — это для съёмки спецэффектов в кино?
камера отдаёт кадры в своём потоке, кадры аллоцированны в потоке камеры из буфера камеры который тормозит камеру от того чтобы съесть всю RAM на компьютере — как потокам_обработчикам вернуть память кадров обратно в буфер камеры после того как они закончат обработку?
Эксперименты показали, что malloc работает не так уж медленно:
Windows x64:
image
Linux x64:
image
Всех быстрее однопоточный MemPool (это тот же FastMemPool в котором убрал atomic операции). В многопоточной работе FastMemPool позволил добиться кратного роста производительности + в нём же ещё есть фишки для контроля выхода за пределы аллокации… некая польза есть…
Сам тест тут test_overhead.cpp
Друзья, я протестировал строковые ключи на Android, ARM процессор — всё работает замечательно:

Друзья,
спасибо за активную переписку, которая навела меня на понимание сути ваших сомнений.
Вчера просто утром увидел коммент про проблемы на ARM процессорах (а такие у меня только на телефонах с Android есть), и решил сочетая полезное с полезным поднять стенд для Android (https://github.com/DimaBond174/android_cache — тут пока ещё только заголовок).


Вот, сегодня утром прочитал комментарии… и отвечаю:
Поведение ключа будь то числовой, строковый (или по музыке/видео/изображению/неведомому сферическому коню в вакууме) — это не вопрос библиотеки кеширования. Библиотеке кеширования (которая шаблон в одном .h файле) просто требуется от ключа реализация функций inline int compare (...) и inline uint64_t get_hash(...).


То как ключ себя ведёт со строками — частное личное внутреннее дело ключа и алгоритму кеширования до него дела нет. В частности предложенный расчёт хэш-а строки по uint64_t[3] в файле ikey2.h реализован так что при установке в ключ строки вызывается метод который гарантирует расширение строки до требуемого размера:


static constexpr int key_ElemNSizeKey_size = 3 * sizeof(uint64_t);
    void setKey() {
      len = data.length();
      if (data.capacity()  <  key_ElemNSizeKey_size)  {
        data.reserve(key_ElemNSizeKey_size);
      }
      key.setKey(data.data());
    }

Если по каким-либо причинам компилятор решит оптимизировать расширение строки, то всегда можно сделать memcpy в обнулённый массив uint64_t[3] на минимальное
из текущего размера строки и key_ElemNSizeKey_size.

Информация

В рейтинге
Не участвует
Откуда
Екатеринбург, Свердловская обл., Россия
Дата рождения
Зарегистрирован
Активность