LINUX.ORG.RU

Qlibs++ — header-only библиотеки для C++20

 , ,


6

3

Kris Jusiak создал проект Qlibs++ с header-only библиотеками для С++20, без сторонних зависимостей. Часть из них – облегчённые версии библиотек из boost-ext.

На данный момент есть:

Приятного чтения! :)

Дополнение от 26.11.2024: Автор создал ещё два репозитория, пока пустые:

https://github.com/qlibs/uefi – C++ UEFI library.

★★★★★

Последнее исправление: CrX (всего исправлений: 4)
Ответ на: комментарий от unDEFER

Но там набор данных очень маленький:

static constexpr frozen::unordered_set<frozen::string, 32> Keywords{
    "auto",     "break",  "case",    "char",   "const",    "continue",
    "default",  "do",     "double",  "else",   "enum",     "extern",
    "float",    "for",    "goto",    "if",     "int",      "long",
    "register", "return", "short",   "signed", "sizeof",   "static",
    "struct",   "switch", "typedef", "union",  "unsigned", "void",
    "volatile", "while"};

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

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

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

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

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

vcpkg уже существует, и даже поддерживается всеми IDE

А почему люнексоеды его не осилили?

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

Эта скриптуха с нами сейчас в одной комнате?

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

Потому что на VOP, как и любом другом ассемблере, руками нормальный человек до уровня gcc/clang код не дотянет.

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

Ты не понимаешь что такое VOP. Это первое. Второе, писать на ассемблере заведомо предел оптимизаций кода в принципе.

«Нормальный» (т.е. такой как в среднем по больнице) человек на gcc/clang пишет настолько кривое и тормозное говно, обмазавшись умными указателями там и прочим, что ему далеко до «обычного» кода на CL.

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

Ты не понимаешь что такое VOP.

Это тонкая прослойка поверх ассемблера.

Второе, писать на ассемблере заведомо предел оптимизаций кода в принципе.

Нет. Ассемблер — это просто язык. Оптимизировать на нём вручную, особенно на современных процессорах просто заколебёшься.

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

особенно на современных процессорах просто заколебёшься.

Что такого в современных процессорах что нельзя оптимизировать на ассемблере но можно компиляторами C и C++, которые компилируются в ассемблер сначала? Пример?

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

Разворачивание циклов, подстановка тела функций, предвычисления, конвейеризация. Это всё будешь вручную делать?

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

Вот пример.

Неоптимизированный ассемблер:

action:
        push    rbp
        mov     rbp, rsp
        mov     dword ptr [rbp - 4], edi
        mov     qword ptr [rbp - 16], rsi
        mov     dword ptr [rbp - 20], edx
        mov     dword ptr [rbp - 24], 0
.LBB0_1:
        mov     eax, dword ptr [rbp - 24]
        cmp     eax, dword ptr [rbp - 20]
        jge     .LBB0_4
        mov     rax, qword ptr [rbp - 16]
        movsxd  rcx, dword ptr [rbp - 24]
        mov     eax, dword ptr [rax + 4*rcx]
        add     eax, dword ptr [rbp - 4]
        mov     dword ptr [rbp - 4], eax
        mov     eax, dword ptr [rbp - 24]
        add     eax, 1
        mov     dword ptr [rbp - 24], eax
        jmp     .LBB0_1
.LBB0_4:
        mov     eax, dword ptr [rbp - 4]
        pop     rbp
        ret

Оптимизированный:

action:
        mov     eax, edi
        test    edx, edx
        jle     .LBB0_7
        mov     ecx, edx
        cmp     edx, 7
        ja      .LBB0_3
        xor     edx, edx
        jmp     .LBB0_6
.LBB0_3:
        mov     edx, ecx
        and     edx, 2147483640
        movd    xmm0, eax
        mov     eax, ecx
        shr     eax, 3
        and     eax, 268435455
        shl     rax, 5
        pxor    xmm1, xmm1
        xor     edi, edi
.LBB0_4:
        movdqu  xmm2, xmmword ptr [rsi + rdi]
        paddd   xmm0, xmm2
        movdqu  xmm2, xmmword ptr [rsi + rdi + 16]
        paddd   xmm1, xmm2
        add     rdi, 32
        cmp     rax, rdi
        jne     .LBB0_4
        paddd   xmm1, xmm0
        pshufd  xmm0, xmm1, 238
        paddd   xmm0, xmm1
        pshufd  xmm1, xmm0, 85
        paddd   xmm1, xmm0
        movd    eax, xmm1
        cmp     edx, ecx
        je      .LBB0_7
.LBB0_6:
        add     eax, dword ptr [rsi + 4*rdx]
        inc     rdx
        cmp     rcx, rdx
        jne     .LBB0_6
.LBB0_7:
        ret

Сможешь вручную получить из первого второй? 268435455 будешь в уме вычислять?

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

Что значит нельзя писать разворачивание циклов? Ты пишешь на ассемблере, ты сам выбираешь что писать. И у тебя нет никаких циклов в ассемблере, блеать. И ты на нем заведомо пишешь что-то маленькое и отдельное.

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

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

SIMD из воздуха берется, или это специальные инструкции процессора, для которых в ассемблере есть мнемоники?

Поражаюсь упоротости крестанутых, вообще какая-то невероятная она сегодня.

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

Давай так - какого хрена что в ffmpeg и кодеках, что в GMP - оптимизированные части пишутся на ассемблере, а не на сраных крестах или сишечке? Ведь компиляторы так хорошо оптимизируют, что никакой ассемблер не нужен и на нем даже будет хуже!

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

А ты спец по оптимизации SIMD, да? И все мнемоники наизусть знаешь?
Хватит тут уже флудить, найди себе другой тред в толксах.

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

И все мнемоники наизусть знаешь

Примерно представляю классы операций, могу нагуглить нужную операцию нехер делать, и для x86-64 примерно представляю словообразование мнемоник, в зависимости от того что они делают, и дохрена раз с этим работал, да, начиная еще со студенческих времен и тыкания палочкой 3D-графики.

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

Ты пишешь на ассемблере, ты сам выбираешь что писать. И у тебя нет никаких циклов в ассемблере, блеать.

Я же тебе выше привёл два куска кода. Цикл и он же, развёрнутый в SIMD.

И ты на нем заведомо пишешь что-то маленькое и отдельное.

Ну вот. А если надо написать не три строки, а что-то нормальное и быстрое, приходится писать на Си++.

у тебя там во время компиляции доступен вообще весь лисп

Ага. Переписать GCC на лиспе. Ну попробуй.

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

Цикл и он же, развёрнутый в SIMD.

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

а что-то нормальное и быстрое, приходится писать на Си++.

Нет. Это так не работает. Это типичная ошибка крестанутых, типа «возьму C++ и будет быстра». Не будет. Это так не работает. Тебе в любом случае придется вычислять где боттлнеки, и там оптимизировать, желательно, если это числодробилки или что-то такое - на ассемблере. Потому и GMP и ffmpeg, и прочее подобное - имеют ассемблерный код в репозитории, а не ориентируются на выхлоп компилятора сраных крестов. Потому что умные люди пишут, а не веруны в крестовый или сишный компилятор. И кстати написаны они на Си и крестах совсем не затем, что кресты или Си якобы быстрые, а чтобы было ABI.

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

А для того кода, которому к херам оптимизация не нужна, который там на IO завязан или на GUI или на что еще - тебе C++ опять же не нужен, он слишком низкоуровневый и неудобный. Поэтому в частности, я и говорю, что C++ вообще не имеет смысла как язык, не считая поддержки легаси на крестах. И народ из тех кто поумнее крестолюбцев, давно вот этот код пишет хотя бы на питоне. В случае лисп-системы тебе питон нахрен не нужен, потому что лисп куда удобнее питона, опять же.

Ага. Переписать GCC на лиспе

Зачем нужен GCC на лиспе? Уже есть компиляторы лиспа.

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

На лиспе, как я говорю, это делается проще, т.к. у тебя встроенный ассемблер

Так помоги разработчикам SBCL. Их реализация больших целых хуже, чем boost.multiprecision.

Или можем посоревноваться. Реализовать, например, pidigits. Ты на SBCL, я на Си++. Запрещено использовать внешние библиотеки с ручным ассемблером (например, libgmp, в том числе через sb-gmp). Разрешено использовать VOP.

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

А для того кода, которому к херам оптимизация не нужна, который там на IO завязан или на GUI или на что еще - тебе C++ опять же не нужен, он слишком низкоуровневый и неудобный. Поэтому в частности, я и говорю, что C++ вообще не имеет смысла как язык, не считая поддержки легаси на крестах.

// g++ -std=c++23 -march=native simd.cpp -o simd_gcc
// clang++ -std=c++23 -march=native simd.cpp -o simd_clang

#include <array>
#include <cassert>
#include <cstddef>
#include <experimental/simd>
#include <functional>
#include <iostream>
#include <numeric>
#include <print>

namespace stdx = std::experimental;

int main()
{
    using V = stdx::native_simd<double>;

    alignas(stdx::memory_alignment_v<V>) std::array<V::value_type, 1024> data;
    std::iota(data.begin(), data.end(), 0);

    V::value_type acc{};
    for (std::size_t i = 0; i < data.size(); i += V::size())
        acc += stdx::reduce(V(&data[i], stdx::vector_aligned), std::plus{});

    std::println("sum of data = {}", acc);

    using W = stdx::fixed_size_simd<int, 4>;
    alignas(stdx::memory_alignment_v<W>) std::array<int, 4> arr{2, 5, 4, 1};
    auto w = W(&arr[0], stdx::vector_aligned);

    std::println("hmin(w) = {}", stdx::hmin(w));
    std::println("hmax(w) = {}", stdx::hmax(w));
}

Перепиши на асме, сравним.

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

Свиной отблев, а не код.

И кучи экспериментальной срани, которая на stable системах не скомпилируется(и в MSVC++ тоже).

Плюс, такие вещи как SIMD-инструкции писать «в общем стиле» не учитывая специфику хотя бы архитектуры, это смешно. Вот че стоит например fixed_size_simd<int> и последующий hmin/hmax - дело в в том что например на x86(-64) для интов нет аналога SHUFPS. Соответственно, тут будет неявный каст во float4. Это переключение домена, кроме собственно каста. Если такое говно будет происходить в цикле, это удар по производительности. Но видимо, крестолюбцев такое не волнует, у них «C++ эта быстра» и поехали, не думая вообще о деталях.

Давай я тебе на лиспе лучше напишу, чтобы было видно, как бывает по-красивому.

(eval-when (:compile-toplevel :load-toplevel :execute)
  (require 'sb-simd))

(defpackage simd-hello
  (:use #:cl #:sb-simd-avx)
  (:export #:main))

(in-package #:simd-hello)

(defun main ()
  (declare (optimize (speed 3) (safety 0) (debug 0))) ;; optimize
  (let* ((len 1024)
         (data (make-array len :element-type 'f64)))
    (declare (dynamic-extent data))
    (loop :for i :from 0 :below len
          :do (setf (aref data i) (float i 0.0d0)))
    (loop :with sum = (f64.2 0)
          :for i :from 0 :below len :by 2
          :do (f64.2-incf sum (f64.2-aref data i))
          :finally (format t "sum of data = ~a~%"
                           (multiple-value-call #'+ (f64.2-values sum)))))
  (let* ((v (make-s32.4 2 5 4 1))
         (w (f32.4-from-s32.4 v)))
    (format t "hmin(w) = ~a~%" (round (f32.4-horizontal-min w)))
    (format t "hmax(w) = ~a~%" (round (f32.4-horizontal-max w)))))

Если дизассемблировать это, и сравнить с тем что высирает Clang18, то увидим, что сравнение не в пользу Clang (я вообще не понял нахера там высирается куча каких-то всратых левых инструкций, нахера перемешиваются AVX и SSE регистры, почему не используются возможности AVX там где надо, а вместо этого высирается набор из SSE, но особо и не надеялся на то что будет что-то вумное или хотя бы вменяемое, я же не крестолюбец)

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

на лиспе лучше напишу

Что так?
А, ну да:

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

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

inb4 в лиспе, вот эта работа с SSE с помощью sb-simd никакой особой магии не делает, а чуть ли не 1 в 1 отображается на соответствующие инструкции

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

Если дизассемблировать это

Сначала нужно как-то скомпилировать это.
Я был любезен, и не забыл написать ключи компиляции.
Ты же написал на «китайском», не посоветовал какой-нибудь самоучитель по нему, но самое главное – забыл упомянуть, что это «кантонский» диалект.

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

для «создания бинарника»:

echo "(load \"simd.lisp\") (sb-ext:save-lisp-and-die \"simd_sbcl\" :executable t :toplevel 'simd-hello:main)" | sbcl

дизассемблирование делается из лиспа:

(load "simd.lisp")
(disassemble 'simd-hello:main)
lovesan ★★
()
Последнее исправление: lovesan (всего исправлений: 1)
Ответ на: комментарий от dataman

Перепиши на асме, сравним.

А если не выпендриваться, а писать более традиционно, то компилятор оптимизирует в

int main()
{
  printf("sum of data = %i\n", 523776);
  printf("hmin(w) = %i\n", 1);
  printf("hmax(w) = %i\n", 5);
}
monk ★★★★★
()

Напишите пожалуйста сколько будет весить бинарик с лиспа и сколько с С++? А то Столяров, если я правильно помню могу ошибаться, утверждал что любой бинарь с лиспа собирается на 50 мб минимум.

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

Напишите пожалуйста сколько будет весить бинарик с лиспа и сколько с С++?

Так от компилятора зависит. Для Си++ нынче около 2,5МБ, если не использовать динамическую линковку. Для sbcl 36МБ, если не выкидывать компилятор из бинарника.

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

сколько будет весить бинарик с лиспа

$ stat simd_sbcl

Size: 45783288

$ ./simd_sbcl

sum of data = 523776.0d0
hmin(w) = 1
hmax(w) = 5

$ strip simd_sbcl
$ stat simd_sbcl

Size: 456184

$ ./simd_sbcl

fatal error encountered in SBCL pid 12536 tid 12536:
Can’t find sbcl.core

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

core это образ лисп-системы, который добавлен к исполняемому файлу рантайма

лисп работает не как C++ итп, он работает как операционная система. Core-файл это по сути образ ОС в гибернации.

SBCL может его сжимать - если был собран с zstd. Для сжатия образа при дампе надо добавить аргумент :compression 9(или не 9, а другое число) при вызове save-lisp-and-die

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

очевидно можно

для этого есть компиляторы - но главная проблема отсутсвие стандартого варианта

ибо lisp-компиляторы которых было полно в конце 60ых и до начала 80ых это совсем не то бы компилятор того common-lisp что воцарился в ~1984

за отдельные ( и очень не маленькие) гринбеки есть компиляторы полносностью отключающие гомоиконость если по коду её нет - таже алегра

но мало кто в это умеет

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

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

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

Встраиваемые реализации, особенно компилирующиеся через Си, типа ECL, могут предоставлять что-то более похожее на сишную модель создания бинарей. https://ecl.common-lisp.dev/static/manual/System-building.html#Executable

Но в целом, если у тебя Си или что-то наподобие - то у тебя обычно уже есть соответствующая сишная ОС, которая тебе реализует сишную модель исполнения, например это GNU/Linux. Для лиспа нужно свое окружение, которое и хранится в этом образе.

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

Хотя и полезно в эмбедедде и на мобилках.

в целом мало кому надо

qulinxao3 ★★
()
26 ноября 2024 г.
Ответ на: комментарий от hobbit

А, в этом смысле. Тогда да.
И, раз уж ты здесь :), добавь, пожалуйста, uefi и prof в ОП.
Корректоры уже тоже не могут редактировать свои ОП. Я не заметил, когда это поведение изменилось. :(

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

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

ну чтобы быть полностью в своей тарелке.

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

Меня диаметр готовой тарелки не устраивает.

dataman ★★★★★
() автор топика

@hobbit, исправь, пожалуйста:
prof – профайлинг;


Немного попробовал:
$ cat example.cpp

// g++ -std=c++2c example.cpp -o example

#include <print>
#include "prof"

int main() {
  {
    prof::linux_perf profiler{"/dev/shm/perf"};

    profiler.start();

    std::println("Hello, prof!");

    profiler.stop();
  }
}

$ cat test.sh

#!/bin/sh

mkfifo /dev/shm/perf
g++ -std=c++2c example.cpp -o example
perf stat --control=fifo:/dev/shm/perf --delay=-1 ./example
perf record --control=fifo:/dev/shm/perf --delay=-1 ./example

$ ./test.sh

Events disabled
Hello, prof!
Events enabled
Events disabled

 Performance counter stats for './example':

              0.03 msec task-clock:u                     #    0.026 CPUs utilized
                 0      context-switches:u               #    0.000 /sec
                 0      cpu-migrations:u                 #    0.000 /sec
                 6      page-faults:u                    #  177.899 K/sec
            21,627      cycles:u                         #    0.641 GHz
             6,815      instructions:u                   #    0.32  insn per cycle
             1,209      branches:u                       #   35.847 M/sec
     <not counted>      branch-misses:u                                                         (0.00%)

       0.001275341 seconds time elapsed

       0.000000000 seconds user
       0.000000000 seconds sys


Some events weren't counted. Try disabling the NMI watchdog:
        echo 0 > /proc/sys/kernel/nmi_watchdog
        perf stat ...
        echo 1 > /proc/sys/kernel/nmi_watchdog
Events disabled
Hello, prof!
Events enabled
[ perf record: Woken up 1 times to write data ]
[ perf record: Captured and wrote 0.002 MB perf.data ]
dataman ★★★★★
() автор топика
Для того чтобы оставить комментарий войдите или зарегистрируйтесь.