LINUX.ORG.RU

Для чего alignas?

 ,


0

2

Приветствую.

Про определение о требовании выравнивания читал конечно. Но таки как выбирается аргумент??? чтобы получить кучи прироста производительности )

Т.е. я например объявляю объекты как

alignas(64) std::atomic<size_t> tail;
alignas(64) std::atomic<size_t> head;

на xeon прирост в 2 раза, а на виртуалке можно считать погрешностью, но при 16 уже на виртуалке хоть и маленький, но таки -5% времени выполнения, при этом на процессоре падение.

★★★
Ответ на: комментарий от alysnix

все это прекрасно, но не по одной ссылке не написано условно почему 64 лучше 16 и наоборот кроме строки

it’s better to align it to 64 bytes since on most processor the cache line size is 64 bytes now

и если взять ее за основу, то на хеон надо выравнивать адрес по 64 байтам для 8 байтовой переменной

wolverin ★★★
() автор топика
Последнее исправление: wolverin (всего исправлений: 2)
Ответ на: комментарий от wolverin

все это прекрасно, но не по одной ссылке не написано условно почему 64 лучше 16 и наоборот кроме строки

Всё просто: если твоя переменная попадает между двумя линиями кэша, то доступ к ней будет дороже. Для всяких локов и прочих мютексов это важно, потому что доступ к ним предусматривает синхронизацию между ядрами.

Можешь тут почитать: https://en.algorithmica.org/hpc/cpu-cache/alignment/

hateyoufeel ★★★★★
()
Последнее исправление: hateyoufeel (всего исправлений: 1)
Ответ на: комментарий от hateyoufeel

Всё просто

если это так, то почему при 64 в виртуалке нет никакого эффекта, хотя там то же самое ядро стоит, а при 16 уже наблюдается увеличение быстродействия?

wolverin ★★★
() автор топика
Ответ на: комментарий от wolverin

сначала приведи код теста, и разрядность модели памяти(64 бита что-ли?)

потом посмотри как устроен твой атомик, и что там за поля. размер атомика не гарантируется равным размеру подставляемого в него типа.

ты выровнял некую фигню на 64 бит при неизвестной модели памяти, и при неизвестном коде теста получил прирост в 2 раза, и задаешь вопрос. так блондинки вопросы задают.

по идее, там и так должно быть все выровнено на 64, если модель 64 битная.

может оно там стало хорошо в кеш попадать, все зависит от кода теста

alysnix ★★★
()
Последнее исправление: alysnix (всего исправлений: 3)
Ответ на: комментарий от alysnix

да стандартные и там и сям 64 бита пример выше из бууста, только в варианте на std и 100500 раз в цикле записываются переменные в одном потоке и читаются в другом.

wolverin ★★★
() автор топика
Ответ на: комментарий от wolverin

Всё просто

если это так, то почему при 64 в виртуалке нет никакого эффекта, хотя там то же самое ядро стоит, а при 16 уже наблюдается увеличение быстродействия?

А хер его знает. Без кода и прочего не скажешь. Возможно, твой гипервизор режет доступ к кэшу для виртуалок или ещё что.

hateyoufeel ★★★★★
()
Ответ на: комментарий от wolverin

попробуй в коде своем

boost::atomic<size_t> head_, tail_;

это положить перед буфером в рингбуфере.

alysnix ★★★
()
Ответ на: комментарий от hateyoufeel

код

#include <atomic>
#include <vector>
#include <iostream>
#include <thread>
#include <chrono>

template <typename T> class ring_buffer
{
public:
    ringbuffer(size_t capacity):
        storage(capacity + 1), tail(0), head(0)
    {}

    bool push(T value)
    {
        size_t curr_tail = tail.load(std::memory_order_relaxed);
        size_t curr_head = head.load(std::memory_order_acquire);

        if (get_next(curr_tail) == curr_head)
        {
            return false;
        }

        storage[curr_tail] = std::move(value);
        tail.store(get_next(curr_tail), std::memory_order_release);

        return true;
    }

    bool pop(T &value)
    {
        size_t curr_head = head.load(std::memory_order_relaxed);
        size_t curr_tail = tail.load(std::memory_order_acquire);

        if (curr_head == curr_tail)
        {
            return false;
        }

        value = std::move(storage[curr_head]);
        head.store(get_next(curr_head), std::memory_order_release);

        return true;
    }

private:
    size_t get_next(size_t slot) const
    {
        return (slot + 1) % storage.size();
    }

private:
    std::vector<T> storage;
    alignas(64) std::atomic<size_t> tail;
    alignas(64) std::atomic<size_t> head;
};

void test()
{
    int count = 10000000;
    ringbuffer<int> buffer(1024);
    auto start = std::chrono::steady_clock::now();

    std::thread producer([&]() {
        for (int i = 0; i < count; ++i)
        {
            while (!buffer.push(i))
                std::this_thread::yield();
        }
    });

    uint64_t sum = 0;
    std::thread consumer([&]() {
        for (int i = 0; i < count; ++i)
        {
            int value;

            while (!buffer.pop(value))
                std::this_thread::yield();

            sum += value;
        }
    });

    producer.join();
    consumer.join();

    auto finish = std::chrono::steady_clock::now();
    auto ms = std::chrono::duration_cast<std::chrono::milliseconds>(finish - start).count();

    std::cout << "time: " << ms << "ms sum: " << sum << std::endl;
}

int main()
{
    while (true) test();
    return 0;
};

wolverin ★★★
() автор топика
Ответ на: комментарий от wolverin
 std::vector<T> storage;
 alignas(64) std::atomic<size_t> tail;
 alignas(64) std::atomic<size_t> head;

без выравнивания положи tail и head перед буфером storage. сравни со старым вариантом без выравнивания

alysnix ★★★
()
Последнее исправление: alysnix (всего исправлений: 1)
Ответ на: комментарий от alysnix

так я уже сравнивал, написал при каких числах быстрее, допустим 64 особенность процессора (условно якобы самая распространенная длина строки кеша), но вот на ARM H3 тоже методом тыка подбирать? )

wolverin ★★★
() автор топика
Ответ на: комментарий от wolverin

для чего там yield’ы? то, что ты нарисовал это классическая тредсейф очередь, навроде канала golang, только синхронизация должна делаться мьютексами и cond var. код такой тредсейф очереди есть везде.

ищи «с++ thread safe queue»

alysnix ★★★
()
Последнее исправление: alysnix (всего исправлений: 2)
Ответ на: комментарий от wolverin

так я уже сравнивал, написал при каких числах быстрее, допустим 64 особенность процессора (условно якобы самая распространенная длина строки кеша), но вот на ARM H3 тоже методом тыка подбирать? )

cat /sys/devices/system/cpu/cpu0/cache/index0/coherency_line_size 
64

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

P.S. на современных асимметричных чипах типа Apple M1 или новых интелов с P- и E-ядрами, у разных ядер может быть разный размер кэша. Типа, не факт, но бывает. Я бы с этой хернёй вообще был максимально аккуратен.

https://www.mono-project.com/news/2016/09/12/arm64-icache/

hateyoufeel ★★★★★
()
Последнее исправление: hateyoufeel (всего исправлений: 2)
Ответ на: комментарий от alysnix

) ну на бусте написано что за очередь и для чего она такая, меня больше интересует как выбирается число.

wolverin ★★★
() автор топика
Ответ на: комментарий от wolverin

просто если тебе понадобится настоящая очередь, то реализована она будет не так, никаких левых йелдов там не будет(их вообще в нормальном коде быть не должно), и все твои алайны полетят. и придется пускаться в новые эксперименты.

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

alysnix ★★★
()
Последнее исправление: alysnix (всего исправлений: 1)
Ответ на: комментарий от hateyoufeel

спасибо, буду разбираться, правда coherency_line_size в моей сборке нет ) видимо в другом месте лежит

wolverin ★★★
() автор топика
Ответ на: комментарий от wolverin

спасибо, буду разбираться, правда coherency_line_size в моей сборке нет ) видимо в другом месте лежит

Чо за сборка?

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

P.S. быстрый гугл нашёл вот это вот

https://en.cppreference.com/w/cpp/thread/hardware_destructive_interference_size

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

P.P.S. ещё есть библиотечка hwloc

https://www.open-mpi.org/projects/hwloc/

hateyoufeel ★★★★★
()
Последнее исправление: hateyoufeel (всего исправлений: 2)
Ответ на: комментарий от wolverin

Чо за сборка?

Linux NanoPi-NEO-Core 4.14.111 #126 SMP Mon Feb 22 17:04:18 CST 2021 armv7l armv7l armv7l GNU/Linux

еще раз спасибо

А.. древнее что-то.

Ещё есть самое тупое:

$ cat /proc/cpuinfo|grep cache_alignment

Опять же, если ядер много, бери самое маленькое число.

hateyoufeel ★★★★★
()
Ответ на: комментарий от hateyoufeel

не просто древнее, а что то очень китайско злоебучее ))

# cat /proc/cpuinfo
processor       : 0..3
model name      : ARMv7 Processor rev 5 (v7l)
BogoMIPS        : 38.85
Features        : half thumb fastmult vfp edsp neon vfpv3 tls vfpv4 idiva idivt vfpd32 lpae evtstrm
CPU implementer : 0x41
CPU architecture: 7
CPU variant     : 0x0
CPU part        : 0xc07
CPU revision    : 5

Hardware        : sun8i
Revision        : 0000
Serial          : 02c0008157e235c4
wolverin ★★★
() автор топика
Последнее исправление: wolverin (всего исправлений: 2)

Но таки как выбирается аргумент???

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

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

то на хеон надо выравнивать адрес по 64 байтам для 8 байтовой переменной

Странный вывод, откуда здесь часть про 8 байтов? Если у тебя узкое место в синхронизации кэшей то переменную любого размера (или группу переменных, обновляемых одновременно) имеет смысл алайнить так чтобы она монопольно занимала один кэшлайн, а конкретно столько байт какова его длина на целевой архитектуре. По-хорошему, под это нужен не хардкод, а дефайны.

И это не универсальное решение. Решая проблему синхронизации кэшей, ты увеличиваешь использование памяти и трафик по шине в 8 раз. Если в одном случае ты получил прирост производительности, в другом ты упрёшься в шину и получишь деградацию. Поэтому, опять таки, сначала нужно глубоко разобраться в том как работает процессор (вообще говоря, не только твой но и любой на котором может работать твой код) и твой lockless алгоритм на всех паттернах нагрузки, и только потом осознанно заниматься тонкой настройкой, всегда держа в голове тот факт что на следующем поколении процессоров все условия могут поменяться на обратные.

slovazap ★★★★★
()

ссылка по теме https://lwn.net/Articles/790464/

Сплитлоки дико тормозят на интелах(новых особенно). Гипервизор может всякие свои ограничения накладывать, особенно на микробенчмарки, где лок в цикле захватывается

phoenix ★★★★
()
Последнее исправление: phoenix (всего исправлений: 1)

На крестах ценность всего этого равна примерно нулю, потому что твоя производительность один хер потонет в куче «умных» указателей, indirect-ссылок (pimpl, std::vector всякие итд), и фрагментации памяти.

lovesan ★★★
()
Ответ на: комментарий от lovesan

в куче «умных» указателей, indirect-ссылок (pimpl, std::vector всякие итд), и фрагментации памяти.

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

фрагментация памяти не влияет на производительность практически никак. она влияет на лишь на то, что в один момент вы можете не получить запрашиваемый кусок памяти, поскольку таких свободных кусков просто нет… тут правда на обычной оси, рантайм запросит новый кусок памяти от ОС, а откуда ОС его возьмет - это дело ОС.

alysnix ★★★
()
Ответ на: комментарий от alysnix

выравнивания это для реализации драйверов, ввода-вывода, разного рода кернела, рантаймов всяких там лиспов и шарпов, менеджеров памяти и те.пе.

Во-первых, не только для этого, во-вторых там кресты не используются, разве что шизиками или из-за легаси - там сишечка везде

фрагментация памяти не влияет на производительность практически никак. она влияет на лишь на то, что в один момент вы можете не получить запрашиваемый кусок памяти, поскольку таких свободных кусков просто нет… тут правда на обычной оси, рантайм запросит новый кусок памяти от ОС, а откуда ОС его возьмет - это дело ОС.

Лол, очередные откровения из крестомирка. У malloc че, по-твоему, константная алгоритмическая сложность? Щаззз.

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

lovesan ★★★
()
Ответ на: комментарий от lovesan

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

в правильном менеджере время взятия свободного блока константное. сам думай как это сделано :) ничего сложного там нет.

и время освобождения блока - констатное

alysnix ★★★
()
Последнее исправление: alysnix (всего исправлений: 1)
Ответ на: комментарий от alysnix

в правильном менеджере время взятия свободного блока константное. сам думай как это сделано :) ничего сложного там нет.

Конечно. Только правильный менеджер памяти называется GC. Там выделение памяти это инкремент указателя в блоке.

В крестах и сишечке невозможен правильный менеджер памяти.

И кстати подумай почему «куча» называется «куча» и какое отношение она имеет к одноименной структуре данных.

lovesan ★★★
()
Ответ на: комментарий от lovesan

В крестах и сишечке невозможен правильный менеджер памяти.

алгоритмы немусоросборочного менеджера кучи не зависят от языка.

ну так как сделать константный менеджер кучи-то?

alysnix ★★★
()
Ответ на: комментарий от alysnix

алгоритмы немусоросборочного менеджера кучи не зависят от языка.

Ну да, они все будут уступать GC, тормозить и фрагментировать память.

ну так как сделать константный менеджер кучи-то?

В общем случае (т.е. в malloc), для языков без сборки мусора - никак.

lovesan ★★★
()
Ответ на: комментарий от lovesan

Конечно. Только правильный менеджер памяти называется GC. Там выделение памяти это инкремент указателя в блоке.

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

alysnix ★★★
()
Ответ на: комментарий от alysnix

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

Зачем огромный? Все современные GC используют регионы.

надо их корректировать при переносе, что есть огромная, побочная работа.

malloc делает куда более огромную побочную работу, просто крестолюбцы об этом не в курсе

lovesan ★★★
()
Ответ на: комментарий от lovesan

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

фрагментация кучи, если нет переноса и уплотнения, будет всегда.

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

alysnix ★★★
()
Ответ на: комментарий от alysnix

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

У разрабов stdlib сишечки видимо тоже проблема, раз там не такой

фрагментация кучи, если нет переноса и уплотнения, будет всегда.

Да, если на говноязыке писать, к которому невозможно GC прикрутить

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

Иди почитай нутро malloc в glibc и посмотри на «побочную работу» там, умник херов. Она там побольше чем в среднем GC в каком-нибудь лиспе, будет. И при этом работает malloc хуже, тормозит и фрагментирует память.

lovesan ★★★
()
Ответ на: комментарий от lovesan

Иди почитай нутро malloc в glibc и посмотри на «побочную работу» там, умник херов. Она там побольше чем в среднем GC в каком-нибудь лиспе, будет. И при этом работает malloc хуже, тормозит и фрагментирует память.

сам почитай. итак. констатного менеджера кучи ты не придумал…. а логарифмический осилишь хоть :) ?

alysnix ★★★
()
Ответ на: комментарий от alysnix

сам почитай. итак. констатного менеджера кучи ты не придумал…. а логарифмический осилишь хоть :) ?

Да оставьте поциента в покое уже :)

Человек явно уверен в том, что все-все знает и все-все умеет. Такими, вроде бы, врачи заниматься должны…

eao197 ★★★★★
()
Ответ на: комментарий от wolverin

опять людей обижаете

Этот персонаж, можно сказать, звезда LOR-а. Неужели недавний мегафлейм про Go и Lisp, который lovesan организовал, прошел мимо вас?

Касательно темы с malloc-ом и фрагментацией, то складывается впечатление, что lovesan не слышал ни про jemalloc, ни про tcmalloc, ни про кучу всякого другого разного. Вроде как уже несколько десятилетий норма использовать пулы страниц для объектов небольшого размера, поиск свободного места в которых для маленьких объектов происходит со сложностью O(1).

Для больших объектов (от 4K, а то и от 16K) да, все сложнее. Но для маленьких…

eao197 ★★★★★
()

Принудительное выравнивание нужно использовать, когда без него программа работать не будет, например, на x86 выравнивание требуется при использовании SIMD-инструкций. На многих нишевых архитектурах нет поддержки невыровненного доступа, там выравнивать обязательно, если предполагается кастить эту переменную к 64-битному типу (а, если невыровненный доступ есть, как на x86, то уже по желанию, если хочется сэкономить наносекунды)

annulen ★★★★★
()
Ответ на: комментарий от alysnix

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

Зачем? Хачкель просто выделяет сразу терабайт памяти и инкрементирует.

cumvillain
()
Для того чтобы оставить комментарий войдите или зарегистрируйтесь.