Немного туп(л,)
84 subscribers
179 photos
26 videos
40 links
Маленький бложег С++ программиста, увлекающегося Rust'ом. Не столько про пргрмрвне, а вообще.
Download Telegram
#c #prog

Пост специально для Вафеля.

Преамбула:
У нас в кодах есть очередь. Она предназначена для работы из двух потоков: один поток пишет в очередь, другой из неё читает.
При этом в ней не используются никакие примитивы синхронизации. И вот не все верят в то, что это может работать гарантированно и безопасно.
Сейчас я покажу, что это за очередь и объясню как она работает, и почему это безопасно. Единственное, чтобы соблюсти все правовые аспекты, я рассмотрю не конкретно ту очередь, что используется у нас, а напишу вольную интерпретацию идеи на чистом Си.
К слову, идея очень просто и элегантна и очень мне нравится. Вполне вероятно, что этот код изначально был взят из книг (Александреску, или что-то подобное), но уверенности у меня в этом нет, только подозрение.

Так как переписываем на Си, и шаблонов тут нема, то в качестве данных будем использовать обычный int.
Итак, нам нужна очередь. Простейший способ оформить очередь - односвязный список. Так и сделаем:
struct Item {
int data;
struct Item* next;
};

Так же добавим для удобства методы для создания и удаления Item'а:
struct Item* new_item(int data) {
struct Item* item = (struct Item*)calloc(1, sizeof(struct Item));
item->data = data;
return item;
}
void free_item(struct Item* item) {
free(item);
}

Обращаю внимание, что мы не следим за корректностью выделения памяти и проверкой входных данных на NULL, ибо это всего лишь пример.

Что же, вроде бы очередь фактически готова, но пока не понятно, как оно может предоставлять гарантии безопасности и отсутствия гонок данных при конкурентном доступе. А всё потому, что чтобы магия заработала, нам потребуется ещё одна небольшая структурка:
struct Queue {
struct Item* head;
struct Item* tail;
}
struct Queue* queue_init() {
struct Queue* queue = (struct Queue*)malloc(sizeof(struct Queue));
struct Item* item = new_item(0);
queue->head = item;
queue->tail = item;
return queue;
}

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

#prog #rust
Решил я что-нибудь простенькое растовое запилить для наполнения портфолио. Чисто чтобы хоть что-то показать.
Сделал я две штуки:

1. Есть одна нерешённая математическая проблема (одна из, их на самом деле целое море) - гипотеза Коллатца. Гипотеза гласит, что абсолютно любое натуральное число n сведётся к 1, если применять к нему следующие преобразования: если число чётное - делим его на 2, в противном случае - умножаем на 3 и прибавляем 1. Так же эта гипотеза называется "3n+1 дилемма", или "сиракузская проблема" (потому что такая последовательность чисел, построенная в результате этих преобразований, называется "сиракузской").
Это нерешённая проблема математики, потому что не получается математически доказать, что она верна, и не получается найти контр-пример, чтобы доказать, что она не верна.

Сопсна, что сделал я? Да особо ничего выдающегося, простенький трейт-итератор, который позволяет для любого числа вызвать метод collatz_iter(), по которому можно проитерироваться по всей сиракузской последовательности для этого изначального числа. То есть написать что-то такое:
for n in 8.collatz_iter() {
print!("{} ", n);
}

что выведет:
8 4 2 1

Чтобы было поинтереснее, я посыпал сверху немного macro_rules!, cargo-фичи, Resultы, ну и сопсна всё.

Полистать код (его совсем не много) можно здесь.
#rust #prog

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

https://habr.com/ru/company/timeweb/blog/697882/
Сейчас я вам расскажу про одну интересную оптимизацию. Нашёл её не я, а мой коллега, но интересной от этого она быть не перестаёт.

#jobbing #cpp #prog

Итак, имелся примерно следующий код на С++:
struct MyType;
using std::vector<MyType> = MyVec;

MyVec storage;

void foo(MyVec& new_elements) {
auto new_capacity = storage.size() + new_elements.size();
storage.reserve(new_capacity);

for (const auto& elem : new_elements) {
// do something with elem
storage.push_back(elem);
}
}

И этот код работал медленно. Даже нет, он работал оооооочень медленно. И мы уверены, что проблема именно тут, на эту функцию показали все анализаторы и профилировщики.

Коллега ускорил это дело примерно в 1000 (sic!) раз, удалив одну строчку.
Угадаете какую?

Правильный ответ будет в следующем посте с объяснением, почему так произошло :)
💩1
Попытался зайти на любимый онлайн-компилятор, опечатался и попал на https://dogbolt.org/
Интересная штука, надо будет попинать.

#prog
Я не мог этим не поделиться 🤣

#jobbing #haha #prog

Все события вымышлены, а совпадения с реальным кодом случайны.
И вообще это нарисовано в фотошопе.
🙈2
Преамбула: я начинал эту заметку ещё несколько месяцев назад, но не дописал совсем чуть-чуть в конце. Сейчас вот наткнулся снова и решил дописать. Всё, что ниже - это текст от "прошлого меня", за исключением последних трёх абзацев.
--------------------
У меня бомбит, поэтому сейчас будет эмоциональная история про пргрмвне (история про пргрмрвне на канале про пргрмрвне!)

#jobbing #prog #cpp #hate

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

Начиная с С++11 существует такая интересная штука, как std::reference_wrapper.
Если в двух словах, то это хреновина, которая позволяет использовать ссылки так, как обычно их использовать нельзя.
Например: класть в std::vector, или переприсваивать именно "значения ссылок", а не "значения куда указывают ссылки" (актуально для шаблонных алгоритмов).

Зачем вообще это нужно? Чтобы избегать ненужного копирования, как минимум.
Например, очень удобно написать что-то такое:
struct Some {
Some() = default;
~Some() = default;
Some(const Some&) = delete;
Some(Some&&) = delete;
Some& operator=(const Some&) = delete;
Some& operator=(Some&&) = delete;
};

Some global_object;
thread_local Some thread_object;

struct A {
Some m_object;

void foo(Some& object) {
std::vector<std::reference_wrapper<Some>> objects;
objects.emplace_back(object);
objects.emplace_back(m_object);
objects.emplace_back(thread_object);
objects.emplace_back(global_object);

for (const auto& o : objects) {
// some process without copying
}
}
};

Тем самым мы можем обработать обработать кучу данных из разных источников без необходимости их копирования; накладные расходы будут только на внутряночку std::reference_wrapper (а это обычный указатель) и на сам std::vector.

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

Вот буквально сегодня я увидел примерно вот такое:
struct Context {
std::optional<std::reference_wrapper<std::string>> m_id = std::nullopt;
}

Context createContext() {
Context ctx;
std::string guid = generateGUID();
ctx.m_id = guid;
return ctx;
}

И всё, пиздец котёнку. Успело попасть в релизную ветку, благо на тестировании стали ловить странные падения.

Как программист вообще мог такое написать и так облажаться? Очень просто: это было два программиста.
Первый - внедрил использование std::optional<std::reference_wrapper<std::string>>, второй не понял что такое этот ваш std::reference_wrapper и поместил туда объект с меньшим временем жизни, чем контекст.