LINUX.ORG.RU

Кодим на ассемблере в рождество

 


1

2

С Рождеством ЛОРчик!

Что мы делаем в Рождество? Кодим на ассемблере конечно же!

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

Я сначала удивился простоте задачи, минут за 5 набросал решение на С++ с использованием remove_if(). Потом подумал, наверное это не совсем честно, ведь нужно самостоятельное решение, а remove_if() - это вариант для ленивых, уж очень напоминает решение с библиотечной функцией. И я переделал код на свой собственный цикл в С++ стиле, на итераторах.

Замерил быстродействие. Команда time выдавала стабильные 0,005 - 0,006 секунды.

Вроде бы и все, задача решена. Я немного отдохнул, сходил попил чаю. И тут меня осенило. А смогу ли я написать то же самое на чистом Си так, чтобы оно работало быстрее?

И я попробовал. Переписал решение на Си, с адресной арифметикой во все поля. Очень старался не делать ничего лишнего, только самое необходимое и все в одном цикле. Замерил быстродействие. Оно оказалось те же 0,005 секунды. Но 0,006 уже не появлялось никогда. Т.е. может быть мы немножко выиграли, какую-нибудь половину тысячной доли секунды.

Но! Я бы не стал писать пост ради этого. Как вы понимаете, потом меня понесло! :-)

Я попил еще чаю. Поел маминого супа. И решил написать все то же самое, только на ассемблере! Мне было интересно, смогу ли я переплюнуть результаты Си-шного кода.

Вот тут пришлось серьезно постараться. Кажется, это будет мое первое приложение на ассемблере под мак.

Сколько раз мое приложение падало, наверное не сосчитать. Я убил на него полдня! Сломал себе весь мозг. Едва не навернул всю систему, особенно пока искал нужный системный вызов. Но я, таки, справился.

Короче, идея, в общем-то, проста. У нас есть некая строка в секции данных приложения. Там же мы себе оставили буфер для будущей новой строки. Ну и переписываем в этот буфер по байтам старую строчку. Если нам встречается пробел, мы его не переписываем. Для этого в ассемблере есть специальные команды, lods и stos, которые сами увеличат и уменьшат нужные заначения в регистрах. Нам для них нужно только подготовить начальные данные. Командой cmp мы сравниваем байты. Командой je (jump if equivalent) прыгаем на нужную инструкцию по результату сравнения. Регистр r10 я использовал, чтобы сохранить длину нашей новой строки. Почему r10? Не знаю, вроде он был следующий по конвенции вызовов, остальные, предыдущие мы уже использовали.

Чтобы напечать строки, дергаем системный вызов write(). Здесь я тоже надеялся немного выиграть, за счет того, что не использую библиотечные функции, а напрямую прося операционную систему печтать в stdout. Так как операционка - macos, системные вызовы у нее оказались под другими номерами, не как в Linux. Пришлось изрядно постараться, чтобы найти наконец нужный. Хорошо хоть параметры передавались в тех же регистрах, что и в Linux. Видимо соблюдалась конвенция о системных вызовах.

Другой новостью оказалсь RIP-related адресация. Может быть я забыл, но это было несколько неожиданно. Когда последний рза писал на ассемблере для Linux, вроде не сталкивался с этим. В общем, теперь мы не можем просто передавать адрес объекта внутри бинарника куда-нибудь еще. Этот адрес нужно вычислять относительно RIP - register instruction pointer. По-идее, это хорошее нововведение, потому что объекты встроенные в бинарник более не зависят от адреса загрузки этого бинарника поскольку вычисляются из адреса текущей исполняемой инструкции.

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

Нет, те же 0,005 тысячных долей секунды. Я не смог переплюнуть в написании кода Си-шный компилятор. Иногда, в некоторых, очень редких замерах проскакивает отметка 0,004 секунды. С натяжкой можно предположить, что мы обошли на 0,001 тысяную доли компиляторный код. Но это настолько мало, что можно списать на погрешность измерений.

ЛОР, скажи, а был ли у меня шанс обогнать компилятор? Можно ли как-то улучшить код?

Вот мой код ниже. Только номер системного вызова write стоит маковский, при компиляции под линукс, нужно будет подставить линуксовый номер. В линукс он, вроде 1 (единица).

.bss
str_out:
    .space 256

.global _main

.text

# rsi: msg, rdx: len
_print:
    movq $0x2000004, %rax            # system call write
    movq $1, %rdi                    # id handler 1 is stdout
    syscall
    ret

# rsi: from, rdi: to, rcx: count
# r10: current index
_copy:
    lodsb 
    cmpb $32, %al
    je _cpe
    stosb
    inc %r10
_cpe:
    loop _copy
    ret

_main:

    movq msg@GOTPCREL(%rip), %rsi    # address of string to output
    movq $msg_len, %rdx              # number of bytes
    call _print

    movq msg@GOTPCREL(%rip), %rsi
    movq str_out@GOTPCREL(%rip), %rdi
    movq $msg_len, %rcx
    xor %r10, %r10
    call _copy
    
    movq str_out@GOTPCREL(%rip), %rsi
    movq %r10, %rdx                  # number of bytes
    call _print

    ret

.cstring
msg:
    .ascii "String spaces remover\n"
    msg_len = . - msg


Перемещено Zhbert из linux-org-ru

★★★★★

Запуская код с короткой захардкоженной строкой «String spaces remover\n» ты хочешь там что-то намерять? Серьезно?

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

Не, ну с одной стороны да, но результат-то всё равно не кэшируется же. Каждый раз вычисляется заново. И я пробовал килобайтные тексты, разница с сишным вариантом невелика. Да и интересны не сами измерения, а чисто в теории, как можно бы обогнать сишку?

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

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

anonymous
()

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

Интересно глянуть на выхлоп компилятора с флагом -S.

И для замеров наверное имеет смысл взять более длинную строку.

luke ★★★★★
()

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

алсо перепиши на simd, с каким-нибудь pcmpeq + maskmov.

anonymous
()
Ответ на: комментарий от luke

Ассемблерный выхлоп сишки. По-моему, мой код получился красивее и меньше )))

	.section	__TEXT,__text,regular,pure_instructions
	.build_version macos, 10, 15	sdk_version 11, 1
	.globl	_main                   ## -- Begin function main
	.p2align	4, 0x90
_main:                                  ## @main
	.cfi_startproc
## %bb.0:
	pushq	%rbp
	.cfi_def_cfa_offset 16
	.cfi_offset %rbp, -16
	movq	%rsp, %rbp
	.cfi_def_cfa_register %rbp
	subq	$304, %rsp              ## imm = 0x130
	movq	___stack_chk_guard@GOTPCREL(%rip), %rax
	movq	(%rax), %rax
	movq	%rax, -8(%rbp)
	movl	$0, -276(%rbp)
	leaq	L_.str(%rip), %rax
	movq	%rax, -288(%rbp)
	movq	-288(%rbp), %rsi
	leaq	L_.str.1(%rip), %rdi
	movb	$0, %al
	callq	_printf
	leaq	-272(%rbp), %rsi
	movq	-288(%rbp), %rdi
	movl	%eax, -292(%rbp)        ## 4-byte Spill
	callq	_remove_spaces
	leaq	-272(%rbp), %rsi
	leaq	L_.str.1(%rip), %rdi
	movb	$0, %al
	callq	_printf
	movq	___stack_chk_guard@GOTPCREL(%rip), %rcx
	movq	(%rcx), %rcx
	movq	-8(%rbp), %rdx
	cmpq	%rdx, %rcx
	jne	LBB0_2
## %bb.1:
	xorl	%eax, %eax
	addq	$304, %rsp              ## imm = 0x130
	popq	%rbp
	retq
LBB0_2:
	callq	___stack_chk_fail
	ud2
	.cfi_endproc
                                        ## -- End function
	.p2align	4, 0x90         ## -- Begin function remove_spaces
_remove_spaces:                         ## @remove_spaces
	.cfi_startproc
## %bb.0:
	pushq	%rbp
	.cfi_def_cfa_offset 16
	.cfi_offset %rbp, -16
	movq	%rsp, %rbp
	.cfi_def_cfa_register %rbp
	movq	%rdi, -8(%rbp)
	movq	%rsi, -16(%rbp)
	movq	-8(%rbp), %rax
	movq	%rax, -24(%rbp)
	movq	-16(%rbp), %rax
	movq	%rax, -32(%rbp)
LBB1_1:                                 ## =>This Inner Loop Header: Depth=1
	movq	-24(%rbp), %rax
	movsbl	(%rax), %ecx
	cmpl	$0, %ecx
	je	LBB1_5
## %bb.2:                               ##   in Loop: Header=BB1_1 Depth=1
	movq	-24(%rbp), %rax
	movsbl	(%rax), %ecx
	cmpl	$32, %ecx
	je	LBB1_4
## %bb.3:                               ##   in Loop: Header=BB1_1 Depth=1
	movq	-24(%rbp), %rax
	movb	(%rax), %cl
	movq	-32(%rbp), %rax
	movb	%cl, (%rax)
	movq	-32(%rbp), %rax
	addq	$1, %rax
	movq	%rax, -32(%rbp)
LBB1_4:                                 ##   in Loop: Header=BB1_1 Depth=1
	movq	-24(%rbp), %rax
	addq	$1, %rax
	movq	%rax, -24(%rbp)
	jmp	LBB1_1
LBB1_5:
	popq	%rbp
	retq
	.cfi_endproc
                                        ## -- End function
	.section	__TEXT,__cstring,cstring_literals
L_.str:                                 ## @.str
	.asciz	"String spaces remover"

L_.str.1:                               ## @.str.1
	.asciz	"%s\n"

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

Хотел еще сюда код плюсового выхлопа присобачить, но по-моему так не ругаются. :-)

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

И тебя с Рождеством!

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

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

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

	.section	__TEXT,__text,regular,pure_instructions
	.build_version macos, 10, 15	sdk_version 11, 1
	.globl	_main                   ## -- Begin function main
	.p2align	4, 0x90
_main:                                  ## @main
	.cfi_startproc
## %bb.0:
	pushq	%rbp
	.cfi_def_cfa_offset 16
	.cfi_offset %rbp, -16
	movq	%rsp, %rbp
	.cfi_def_cfa_register %rbp
	pushq	%r14
	pushq	%rbx
	subq	$272, %rsp              ## imm = 0x110
	.cfi_offset %rbx, -32
	.cfi_offset %r14, -24
	movq	___stack_chk_guard@GOTPCREL(%rip), %rax
	movq	(%rax), %rax
	movq	%rax, -24(%rbp)
	leaq	-288(%rbp), %r14
	leaq	L_.str(%rip), %rbx
	movq	%rbx, %rdi
	callq	_puts
	jmp	LBB0_1
	.p2align	4, 0x90
LBB0_4:                                 ##   in Loop: Header=BB0_1 Depth=1
	incq	%rbx
LBB0_1:                                 ## =>This Inner Loop Header: Depth=1
	movzbl	(%rbx), %eax
	cmpb	$32, %al
	je	LBB0_4
## %bb.2:                               ##   in Loop: Header=BB0_1 Depth=1
	testb	%al, %al
	je	LBB0_5
## %bb.3:                               ##   in Loop: Header=BB0_1 Depth=1
	movb	%al, (%r14)
	incq	%r14
	jmp	LBB0_4
LBB0_5:
	leaq	-288(%rbp), %rdi
	callq	_puts
	movq	___stack_chk_guard@GOTPCREL(%rip), %rax
	movq	(%rax), %rax
	cmpq	-24(%rbp), %rax
	jne	LBB0_7
## %bb.6:
	xorl	%eax, %eax
	addq	$272, %rsp              ## imm = 0x110
	popq	%rbx
	popq	%r14
	popq	%rbp
	retq
LBB0_7:
	callq	___stack_chk_fail
	.cfi_endproc
                                        ## -- End function
	.section	__TEXT,__cstring,cstring_literals
L_.str:                                 ## @.str
	.asciz	"String spaces remover"

hibou ★★★★★
() автор топика
Ответ на: комментарий от MOPKOBKA
#include <stdio.h>
#include <string.h>

static void remove_spaces(char *s, char *out)
{
    char *ptr = s;
    char *out_ptr = out;

    while (*ptr != '\0')
    {
        if (*ptr != ' ')
        {
            *out_ptr = *ptr;
            out_ptr++;
        }
        ptr++;
    }
    *out_ptr = '\0';
}

int main() 
{
    char buf[256];
    char *str = "String spaces remover";

    printf("%s\n", str);
    remove_spaces(str, buf);
    printf("%s\n", buf);

    return 0;
}


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

Вызов printf был заменён на puts, из очевидного. Кстати если бы puts был бы в коде, то он бы ставил \n сам, и в строке это не потребовалось бы.

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

Наверное все равно, на каком уровне \n ставить. Главное, что не в цикле. Оно же все-равно считается time.

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

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

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

Посмотрел твой код на асме, со счетчиком не честно %)

void delspace(const char *in, char *out) {
    for(;*in;in++) if(*in != ' ') *out++ = *in;
    *out = 0;
}
delspace:                               # @delspace
.LBB0_1:                                # =>This Inner Loop Header: Depth=1
        mov     al, byte ptr [rdi]
        cmp     al, 32
        je      .LBB0_4
        test    al, al
        je      .LBB0_5
        mov     byte ptr [rsi], al
        inc     rsi
.LBB0_4:                                #   in Loop: Header=BB0_1 Depth=1
        inc     rdi
        jmp     .LBB0_1
.LBB0_5:
        mov     byte ptr [rsi], 0
        ret

MOPKOBKA ★★★★
()

Поел маминого супа.

Лайк за чувство юмора!

anonymous
()

Имхо, все лажа, надо запретить прерывания и измерения 0.0 и хрен в периоде ваще не нравится.

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

Все варианты компилятся в одинаковый асм-код при -O2 (и выше). При -O1 тоже, но другой по сравнению с -O2

anonymous
()

https://godbolt.org/z/3nzMbj

void remove_spaces(char *src, char *out) {
    for(; *src; src++ ) {
        if (*src != ' ') {
            *out++ = *src;
        }
    }
    *out = '\0';
}
remove_spaces(char*, char*):
        jmp     .L12
.L4:
        cmp     al, 32
        je      .L3
        mov     BYTE PTR [rsi], al
        add     rsi, 1
.L3:
        add     rdi, 1
.L12:
        movzx   eax, BYTE PTR [rdi]
        test    al, al
        jne     .L4
        mov     BYTE PTR [rsi], 0
        ret
anonymous
()
Ответ на: комментарий от anonymous

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

И лишние add. У процессора есть специальные команды, которые сами все сделают. Я их как раз и использовал.

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

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

Если ты что-то и выиграл, то пару тактиков time не покажет.

Ещё ты выиграл пару тактиков на старте программы, потому что избежал динамической линковки и напрямую попал в _main. Но time это тоже не покажет.

А сам алгоритм слишком тривиальный, там нечего оптимизировать именно ассемблером. Можно попробовать подумать над векторизацией и утилизацией кеша, но тут не будет разницы между асмом и с/с++.

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

Многовато джампов.

void spaces(const char *str, char *out)
{
    static const unsigned char table[256] =
    {
        1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,
        0,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,
        1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,
        1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,
        1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,
        1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,
        1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,
        1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1
    };
    for (unsigned char ch; (ch = *str++);)
    {
        *out = ch;
        out += table[ch];
    }
}
anonymous
()
Ответ на: комментарий от Siborgium

Можно просто

Так даже лучше, не знаю почему я так зациклился именно на табличном подходе.

anonymous
()
Ответ на: комментарий от Siborgium

char table[256] = { 1 };

Это же обнулит весь массив, кроме первого элемента.

Плюс оно как-то странно работает, даже замеряя spaces2 против самой себя на этом сайте, одна из них оказывается в два раза быстрее.

anonymous
()
Ответ на: комментарий от Siborgium

Так массив с рандомными данными в функцию передаётся. А данные разные.

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

Поспешишь – людей насмешишь. Два косяка, и оба мои.

Это же обнулит весь массив, кроме первого элемента.

Да.

даже замеряя spaces2 против самой себя на этом сайте, одна из них оказывается в два раза быстрее.

Я не учел того, что rand() возвращает еще и 0.

Так больше похоже на правду. Впрочем, первая все еще впереди – но ненамного.

https://quick-bench.com/q/SMiRp26gsxa92luIMrKcSBlx-rI

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

Да.

Я современный C++ давно в глаза не видел, поэтому сразу засомневался, думал, может, они изменили чего. Тут бы пригодился гнушный синтаксис { [0 ... 31] = 1, [33 ... 255] = 1 }. Или, может, constexpr тут может помочь?

Так больше похоже на правду. Впрочем, первая все еще впереди – но ненамного.

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

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

Тут бы пригодился гнушный синтаксис

Да можно и memset’ом.

Я думаю, тут можно сойтись на том, что очевидное преимущество табличного подхода

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

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

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

Странно, что оптимизатор автоматически такие таблички не добавляет.

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

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

void remove_spaces(char *src, char *out) {
    for( ;*src; src++ )     {
        *out = *src;
        out+= *src != ' ';
    }
    *out = '\0';
}
remove_spaces(char*, char*):
        jmp     .L8
.L3:
        mov     BYTE PTR [rsi], al
        xor     eax, eax
        cmp     BYTE PTR [rdi], 32
        setne   al
        add     rdi, 1
        add     rsi, rax
.L8:
        movzx   eax, BYTE PTR [rdi]
        test    al, al
        jne     .L3
        mov     BYTE PTR [rsi], 0
        ret
Psilocybe ★★★★
()
Последнее исправление: Psilocybe (всего исправлений: 1)

На машинном коде, тебе уже предлагали писать это?

anonymous
()

автобиографично.. но лор - не блог

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

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

Удваиваю. Хотя бы зацикли всё это на серьёзное число итераций.

hobbit ★★★★★
()

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

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

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

Рождество было 2 недели назад. Верни машину времени на место. :D

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

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

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

Нет, те же 0,005 тысячных долей секунды. Я не смог переплюнуть в написании кода Си-шный компилятор. Иногда, в некоторых, очень редких замерах проскакивает отметка 0,004 секунды. С натяжкой можно предположить, что мы обошли на 0,001 тысяную доли компиляторный код. Но это настолько мало, что можно списать на погрешность измерений.

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

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

Тем временем этот вариант «в лоб» самый быстрый. Быстрее в 1.3 раза «табличного», который не записывает 0 в конце и с сравнением в цикле.

anonymous
()

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

alysnix ★★★
()

Чудеса оптимизации gcc.

-O3

https://quick-bench.com/q/4B0qyuRzUWdZAehuJynoz-phhTQ

void remove_spaces0(const char* str, char* out) {
    while (const char c = *str++) {
        if (c != ' ')
            *out++ = c;
    }
    *out = '\0';
}

void remove_spaces1(const char* str, char* out) {
    while (const char c = *str++) {
        if (c != ' ') {
            // заменил *out++ = c; на две последовательные операции
            // скорость выросла
            *out = c;
            ++out;
        }
    }
    *out = '\0';
}

-O2

https://quick-bench.com/q/DajE9XiZ9i7SO7Eeyzt_SlaB7mQ

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

Локально у себя для gcc-10.2 те же тесты показывают более драматичные результаты - разница доходить до 1.4 раза

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

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

где avx-инструкции?

anonymous
()
Вы не можете добавлять комментарии в эту тему. Тема перемещена в архив.